mirror of https://github.com/mitsuhiko/flask.git
Ben Congdon
7 years ago
committed by
GitHub
28 changed files with 120 additions and 1032 deletions
@ -1,29 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
flask.ext |
|
||||||
~~~~~~~~~ |
|
||||||
|
|
||||||
Redirect imports for extensions. This module basically makes it possible |
|
||||||
for us to transition from flaskext.foo to flask_foo without having to |
|
||||||
force all extensions to upgrade at the same time. |
|
||||||
|
|
||||||
When a user does ``from flask.ext.foo import bar`` it will attempt to |
|
||||||
import ``from flask_foo import bar`` first and when that fails it will |
|
||||||
try to import ``from flaskext.foo import bar``. |
|
||||||
|
|
||||||
We're switching from namespace packages because it was just too painful for |
|
||||||
everybody involved. |
|
||||||
|
|
||||||
:copyright: (c) 2015 by Armin Ronacher. |
|
||||||
:license: BSD, see LICENSE for more details. |
|
||||||
""" |
|
||||||
|
|
||||||
|
|
||||||
def setup(): |
|
||||||
from ..exthook import ExtensionImporter |
|
||||||
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__) |
|
||||||
importer.install() |
|
||||||
|
|
||||||
|
|
||||||
setup() |
|
||||||
del setup |
|
@ -1,143 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
flask.exthook |
|
||||||
~~~~~~~~~~~~~ |
|
||||||
|
|
||||||
Redirect imports for extensions. This module basically makes it possible |
|
||||||
for us to transition from flaskext.foo to flask_foo without having to |
|
||||||
force all extensions to upgrade at the same time. |
|
||||||
|
|
||||||
When a user does ``from flask.ext.foo import bar`` it will attempt to |
|
||||||
import ``from flask_foo import bar`` first and when that fails it will |
|
||||||
try to import ``from flaskext.foo import bar``. |
|
||||||
|
|
||||||
We're switching from namespace packages because it was just too painful for |
|
||||||
everybody involved. |
|
||||||
|
|
||||||
This is used by `flask.ext`. |
|
||||||
|
|
||||||
:copyright: (c) 2015 by Armin Ronacher. |
|
||||||
:license: BSD, see LICENSE for more details. |
|
||||||
""" |
|
||||||
import sys |
|
||||||
import os |
|
||||||
import warnings |
|
||||||
from ._compat import reraise |
|
||||||
|
|
||||||
|
|
||||||
class ExtDeprecationWarning(DeprecationWarning): |
|
||||||
pass |
|
||||||
|
|
||||||
warnings.simplefilter('always', ExtDeprecationWarning) |
|
||||||
|
|
||||||
|
|
||||||
class ExtensionImporter(object): |
|
||||||
"""This importer redirects imports from this submodule to other locations. |
|
||||||
This makes it possible to transition from the old flaskext.name to the |
|
||||||
newer flask_name without people having a hard time. |
|
||||||
""" |
|
||||||
|
|
||||||
def __init__(self, module_choices, wrapper_module): |
|
||||||
self.module_choices = module_choices |
|
||||||
self.wrapper_module = wrapper_module |
|
||||||
self.prefix = wrapper_module + '.' |
|
||||||
self.prefix_cutoff = wrapper_module.count('.') + 1 |
|
||||||
|
|
||||||
def __eq__(self, other): |
|
||||||
return self.__class__.__module__ == other.__class__.__module__ and \ |
|
||||||
self.__class__.__name__ == other.__class__.__name__ and \ |
|
||||||
self.wrapper_module == other.wrapper_module and \ |
|
||||||
self.module_choices == other.module_choices |
|
||||||
|
|
||||||
def __ne__(self, other): |
|
||||||
return not self.__eq__(other) |
|
||||||
|
|
||||||
def install(self): |
|
||||||
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] |
|
||||||
|
|
||||||
def find_module(self, fullname, path=None): |
|
||||||
if fullname.startswith(self.prefix) and \ |
|
||||||
fullname != 'flask.ext.ExtDeprecationWarning': |
|
||||||
return self |
|
||||||
|
|
||||||
def load_module(self, fullname): |
|
||||||
if fullname in sys.modules: |
|
||||||
return sys.modules[fullname] |
|
||||||
|
|
||||||
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] |
|
||||||
|
|
||||||
warnings.warn( |
|
||||||
"Importing flask.ext.{x} is deprecated, use flask_{x} instead." |
|
||||||
.format(x=modname), ExtDeprecationWarning, stacklevel=2 |
|
||||||
) |
|
||||||
|
|
||||||
for path in self.module_choices: |
|
||||||
realname = path % modname |
|
||||||
try: |
|
||||||
__import__(realname) |
|
||||||
except ImportError: |
|
||||||
exc_type, exc_value, tb = sys.exc_info() |
|
||||||
# since we only establish the entry in sys.modules at the |
|
||||||
# very this seems to be redundant, but if recursive imports |
|
||||||
# happen we will call into the move import a second time. |
|
||||||
# On the second invocation we still don't have an entry for |
|
||||||
# fullname in sys.modules, but we will end up with the same |
|
||||||
# fake module name and that import will succeed since this |
|
||||||
# one already has a temporary entry in the modules dict. |
|
||||||
# Since this one "succeeded" temporarily that second |
|
||||||
# invocation now will have created a fullname entry in |
|
||||||
# sys.modules which we have to kill. |
|
||||||
sys.modules.pop(fullname, None) |
|
||||||
|
|
||||||
# If it's an important traceback we reraise it, otherwise |
|
||||||
# we swallow it and try the next choice. The skipped frame |
|
||||||
# is the one from __import__ above which we don't care about |
|
||||||
if self.is_important_traceback(realname, tb): |
|
||||||
reraise(exc_type, exc_value, tb.tb_next) |
|
||||||
continue |
|
||||||
module = sys.modules[fullname] = sys.modules[realname] |
|
||||||
if '.' not in modname: |
|
||||||
setattr(sys.modules[self.wrapper_module], modname, module) |
|
||||||
|
|
||||||
if realname.startswith('flaskext.'): |
|
||||||
warnings.warn( |
|
||||||
"Detected extension named flaskext.{x}, please rename it " |
|
||||||
"to flask_{x}. The old form is deprecated." |
|
||||||
.format(x=modname), ExtDeprecationWarning |
|
||||||
) |
|
||||||
|
|
||||||
return module |
|
||||||
raise ImportError('No module named %s' % fullname) |
|
||||||
|
|
||||||
def is_important_traceback(self, important_module, tb): |
|
||||||
"""Walks a traceback's frames and checks if any of the frames |
|
||||||
originated in the given important module. If that is the case then we |
|
||||||
were able to import the module itself but apparently something went |
|
||||||
wrong when the module was imported. (Eg: import of an import failed). |
|
||||||
""" |
|
||||||
while tb is not None: |
|
||||||
if self.is_important_frame(important_module, tb): |
|
||||||
return True |
|
||||||
tb = tb.tb_next |
|
||||||
return False |
|
||||||
|
|
||||||
def is_important_frame(self, important_module, tb): |
|
||||||
"""Checks a single frame if it's important.""" |
|
||||||
g = tb.tb_frame.f_globals |
|
||||||
if '__name__' not in g: |
|
||||||
return False |
|
||||||
|
|
||||||
module_name = g['__name__'] |
|
||||||
|
|
||||||
# Python 2.7 Behavior. Modules are cleaned up late so the |
|
||||||
# name shows up properly here. Success! |
|
||||||
if module_name == important_module: |
|
||||||
return True |
|
||||||
|
|
||||||
# Some python versions will clean up modules so early that the |
|
||||||
# module name at that point is no longer set. Try guessing from |
|
||||||
# the filename then. |
|
||||||
filename = os.path.abspath(tb.tb_frame.f_code.co_filename) |
|
||||||
test_string = os.path.sep + important_module.replace('.', os.path.sep) |
|
||||||
return test_string + '.py' in filename or \ |
|
||||||
test_string + os.path.sep + '__init__.py' in filename |
|
@ -1,298 +0,0 @@ |
|||||||
#!/usr/bin/env python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
flask-07-upgrade |
|
||||||
~~~~~~~~~~~~~~~~ |
|
||||||
|
|
||||||
This command line script scans a whole application tree and attempts to |
|
||||||
output a unified diff with all the changes that are necessary to easily |
|
||||||
upgrade the application to 0.7 and to not yield deprecation warnings. |
|
||||||
|
|
||||||
This will also attempt to find `after_request` functions that don't modify |
|
||||||
the response and appear to be better suited for `teardown_request`. |
|
||||||
|
|
||||||
This application is indeed an incredible hack, but because what it |
|
||||||
attempts to accomplish is impossible to do statically it tries to support |
|
||||||
the most common patterns at least. The diff it generates should be |
|
||||||
hand reviewed and not applied blindly without making backups. |
|
||||||
|
|
||||||
:copyright: (c) Copyright 2015 by Armin Ronacher. |
|
||||||
:license: see LICENSE for more details. |
|
||||||
""" |
|
||||||
from __future__ import print_function |
|
||||||
import re |
|
||||||
import os |
|
||||||
import inspect |
|
||||||
import difflib |
|
||||||
import posixpath |
|
||||||
from optparse import OptionParser |
|
||||||
|
|
||||||
try: |
|
||||||
import ast |
|
||||||
except ImportError: |
|
||||||
ast = None |
|
||||||
|
|
||||||
|
|
||||||
TEMPLATE_LOOKAHEAD = 4096 |
|
||||||
|
|
||||||
_app_re_part = r'((?:[a-zA-Z_][a-zA-Z0-9_]*app)|app|application)' |
|
||||||
_string_re_part = r"('([^'\\]*(?:\\.[^'\\]*)*)'" \ |
|
||||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")' |
|
||||||
|
|
||||||
_from_import_re = re.compile(r'^\s*from flask import\s+') |
|
||||||
_url_for_re = re.compile(r'\b(url_for\()(%s)' % _string_re_part) |
|
||||||
_render_template_re = re.compile(r'\b(render_template\()(%s)' % _string_re_part) |
|
||||||
_after_request_re = re.compile(r'((?:@\S+\.(?:app_)?))(after_request)(\b\s*$)(?m)') |
|
||||||
_module_constructor_re = re.compile(r'([a-zA-Z0-9_][a-zA-Z0-9_]*)\s*=\s*Module' |
|
||||||
r'\(__name__\s*(?:,\s*(?:name\s*=\s*)?(%s))?' % |
|
||||||
_string_re_part) |
|
||||||
_error_handler_re = re.compile(r'%s\.error_handlers\[\s*(\d+)\s*\]' % _app_re_part) |
|
||||||
_mod_route_re = re.compile(r'@([a-zA-Z0-9_][a-zA-Z0-9_]*)\.route') |
|
||||||
_blueprint_related = [ |
|
||||||
(re.compile(r'request\.module'), 'request.blueprint'), |
|
||||||
(re.compile(r'register_module'), 'register_blueprint'), |
|
||||||
(re.compile(r'%s\.modules' % _app_re_part), '\\1.blueprints') |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
def make_diff(filename, old, new): |
|
||||||
for line in difflib.unified_diff(old.splitlines(), new.splitlines(), |
|
||||||
posixpath.normpath(posixpath.join('a', filename)), |
|
||||||
posixpath.normpath(posixpath.join('b', filename)), |
|
||||||
lineterm=''): |
|
||||||
print(line) |
|
||||||
|
|
||||||
|
|
||||||
def looks_like_teardown_function(node): |
|
||||||
returns = [x for x in ast.walk(node) if isinstance(x, ast.Return)] |
|
||||||
if len(returns) != 1: |
|
||||||
return |
|
||||||
return_def = returns[0] |
|
||||||
resp_name = node.args.args[0] |
|
||||||
if not isinstance(return_def.value, ast.Name) or \ |
|
||||||
return_def.value.id != resp_name.id: |
|
||||||
return |
|
||||||
|
|
||||||
for body_node in node.body: |
|
||||||
for child in ast.walk(body_node): |
|
||||||
if isinstance(child, ast.Name) and \ |
|
||||||
child.id == resp_name.id: |
|
||||||
if child is not return_def.value: |
|
||||||
return |
|
||||||
|
|
||||||
return resp_name.id |
|
||||||
|
|
||||||
|
|
||||||
def fix_url_for(contents, module_declarations=None): |
|
||||||
if module_declarations is None: |
|
||||||
skip_module_test = True |
|
||||||
else: |
|
||||||
skip_module_test = False |
|
||||||
mapping = dict(module_declarations) |
|
||||||
annotated_lines = [] |
|
||||||
|
|
||||||
def make_line_annotations(): |
|
||||||
if not annotated_lines: |
|
||||||
last_index = 0 |
|
||||||
for line in contents.splitlines(True): |
|
||||||
last_index += len(line) |
|
||||||
annotated_lines.append((last_index, line)) |
|
||||||
|
|
||||||
def backtrack_module_name(call_start): |
|
||||||
make_line_annotations() |
|
||||||
for idx, (line_end, line) in enumerate(annotated_lines): |
|
||||||
if line_end > call_start: |
|
||||||
for _, line in reversed(annotated_lines[:idx]): |
|
||||||
match = _mod_route_re.search(line) |
|
||||||
if match is not None: |
|
||||||
shortname = match.group(1) |
|
||||||
return mapping.get(shortname) |
|
||||||
|
|
||||||
def handle_match(match): |
|
||||||
if not skip_module_test: |
|
||||||
modname = backtrack_module_name(match.start()) |
|
||||||
if modname is None: |
|
||||||
return match.group(0) |
|
||||||
prefix = match.group(1) |
|
||||||
endpoint = ast.literal_eval(match.group(2)) |
|
||||||
if endpoint.startswith('.'): |
|
||||||
endpoint = endpoint[1:] |
|
||||||
elif '.' not in endpoint: |
|
||||||
endpoint = '.' + endpoint |
|
||||||
else: |
|
||||||
return match.group(0) |
|
||||||
return prefix + repr(endpoint) |
|
||||||
return _url_for_re.sub(handle_match, contents) |
|
||||||
|
|
||||||
|
|
||||||
def fix_teardown_funcs(contents): |
|
||||||
|
|
||||||
def is_return_line(line): |
|
||||||
args = line.strip().split() |
|
||||||
return args and args[0] == 'return' |
|
||||||
|
|
||||||
def fix_single(match, lines, lineno): |
|
||||||
if not lines[lineno + 1].startswith('def'): |
|
||||||
return |
|
||||||
block_lines = inspect.getblock(lines[lineno + 1:]) |
|
||||||
func_code = ''.join(block_lines) |
|
||||||
if func_code[0].isspace(): |
|
||||||
node = ast.parse('if 1:\n' + func_code).body[0].body |
|
||||||
else: |
|
||||||
node = ast.parse(func_code).body[0] |
|
||||||
response_param_name = looks_like_teardown_function(node) |
|
||||||
if response_param_name is None: |
|
||||||
return |
|
||||||
before = lines[:lineno] |
|
||||||
decorator = [match.group(1) + |
|
||||||
match.group(2).replace('after_', 'teardown_') + |
|
||||||
match.group(3)] |
|
||||||
body = [line.replace(response_param_name, 'exception') |
|
||||||
for line in block_lines if |
|
||||||
not is_return_line(line)] |
|
||||||
after = lines[lineno + len(block_lines) + 1:] |
|
||||||
return before + decorator + body + after |
|
||||||
|
|
||||||
content_lines = contents.splitlines(True) |
|
||||||
while 1: |
|
||||||
found_one = False |
|
||||||
for idx, line in enumerate(content_lines): |
|
||||||
match = _after_request_re.match(line) |
|
||||||
if match is None: |
|
||||||
continue |
|
||||||
new_content_lines = fix_single(match, content_lines, idx) |
|
||||||
if new_content_lines is not None: |
|
||||||
content_lines = new_content_lines |
|
||||||
break |
|
||||||
else: |
|
||||||
break |
|
||||||
|
|
||||||
return ''.join(content_lines) |
|
||||||
|
|
||||||
|
|
||||||
def get_module_autoname(filename): |
|
||||||
directory, filename = os.path.split(filename) |
|
||||||
if filename != '__init__.py': |
|
||||||
return os.path.splitext(filename)[0] |
|
||||||
return os.path.basename(directory) |
|
||||||
|
|
||||||
|
|
||||||
def rewrite_from_imports(prefix, fromlist, lineiter): |
|
||||||
import_block = [prefix, fromlist] |
|
||||||
if fromlist[0] == '(' and fromlist[-1] != ')': |
|
||||||
for line in lineiter: |
|
||||||
import_block.append(line) |
|
||||||
if line.rstrip().endswith(')'): |
|
||||||
break |
|
||||||
elif fromlist[-1] == '\\': |
|
||||||
for line in lineiter: |
|
||||||
import_block.append(line) |
|
||||||
if line.rstrip().endswith('\\'): |
|
||||||
break |
|
||||||
|
|
||||||
return ''.join(import_block).replace('Module', 'Blueprint') |
|
||||||
|
|
||||||
|
|
||||||
def rewrite_blueprint_imports(contents): |
|
||||||
new_file = [] |
|
||||||
lineiter = iter(contents.splitlines(True)) |
|
||||||
for line in lineiter: |
|
||||||
match = _from_import_re.search(line) |
|
||||||
if match is not None: |
|
||||||
new_file.extend(rewrite_from_imports(match.group(), |
|
||||||
line[match.end():], |
|
||||||
lineiter)) |
|
||||||
else: |
|
||||||
new_file.append(line) |
|
||||||
return ''.join(new_file) |
|
||||||
|
|
||||||
|
|
||||||
def rewrite_for_blueprints(contents, filename): |
|
||||||
modules_declared = [] |
|
||||||
def handle_match(match): |
|
||||||
target = match.group(1) |
|
||||||
name_param = match.group(2) |
|
||||||
if name_param is None: |
|
||||||
modname = get_module_autoname(filename) |
|
||||||
else: |
|
||||||
modname = ast.literal_eval(name_param) |
|
||||||
modules_declared.append((target, modname)) |
|
||||||
return '%s = %s' % (target, 'Blueprint(%r, __name__' % modname) |
|
||||||
new_contents = _module_constructor_re.sub(handle_match, contents) |
|
||||||
|
|
||||||
if modules_declared: |
|
||||||
new_contents = rewrite_blueprint_imports(new_contents) |
|
||||||
|
|
||||||
for pattern, replacement in _blueprint_related: |
|
||||||
new_contents = pattern.sub(replacement, new_contents) |
|
||||||
return new_contents, dict(modules_declared) |
|
||||||
|
|
||||||
|
|
||||||
def upgrade_python_file(filename, contents, teardown): |
|
||||||
new_contents = contents |
|
||||||
if teardown: |
|
||||||
new_contents = fix_teardown_funcs(new_contents) |
|
||||||
new_contents, modules = rewrite_for_blueprints(new_contents, filename) |
|
||||||
new_contents = fix_url_for(new_contents, modules) |
|
||||||
new_contents = _error_handler_re.sub('\\1.error_handler_spec[None][\\2]', |
|
||||||
new_contents) |
|
||||||
make_diff(filename, contents, new_contents) |
|
||||||
|
|
||||||
|
|
||||||
def upgrade_template_file(filename, contents): |
|
||||||
new_contents = fix_url_for(contents, None) |
|
||||||
make_diff(filename, contents, new_contents) |
|
||||||
|
|
||||||
|
|
||||||
def walk_path(path): |
|
||||||
this_file = os.path.realpath(__file__).rstrip('c') |
|
||||||
for dirpath, dirnames, filenames in os.walk(path): |
|
||||||
dirnames[:] = [x for x in dirnames if not x.startswith('.')] |
|
||||||
for filename in filenames: |
|
||||||
filename = os.path.join(dirpath, filename) |
|
||||||
if os.path.realpath(filename) == this_file: |
|
||||||
continue |
|
||||||
if filename.endswith('.py'): |
|
||||||
yield filename, 'python' |
|
||||||
# skip files that are diffs. These might be false positives |
|
||||||
# when run multiple times. |
|
||||||
elif not filename.endswith(('.diff', '.patch', '.udiff')): |
|
||||||
with open(filename) as f: |
|
||||||
contents = f.read(TEMPLATE_LOOKAHEAD) |
|
||||||
if '{% for' or '{% if' or '{{ url_for' in contents: |
|
||||||
yield filename, 'template' |
|
||||||
|
|
||||||
|
|
||||||
def scan_path(path=None, teardown=True): |
|
||||||
for filename, type in walk_path(path): |
|
||||||
with open(filename) as f: |
|
||||||
contents = f.read() |
|
||||||
if type == 'python': |
|
||||||
upgrade_python_file(filename, contents, teardown) |
|
||||||
elif type == 'template': |
|
||||||
upgrade_template_file(filename, contents) |
|
||||||
|
|
||||||
|
|
||||||
def main(): |
|
||||||
"""Entrypoint""" |
|
||||||
parser = OptionParser(usage='%prog [options] [paths]') |
|
||||||
parser.add_option('-T', '--no-teardown-detection', dest='no_teardown', |
|
||||||
action='store_true', help='Do not attempt to ' |
|
||||||
'detect teardown function rewrites.') |
|
||||||
parser.add_option('-b', '--bundled-templates', dest='bundled_tmpl', |
|
||||||
action='store_true', help='Indicate to the system ' |
|
||||||
'that templates are bundled with modules. Default ' |
|
||||||
'is auto detect.') |
|
||||||
options, args = parser.parse_args() |
|
||||||
if not args: |
|
||||||
args = ['.'] |
|
||||||
|
|
||||||
if ast is None: |
|
||||||
parser.error('Python 2.6 or later is required to run the upgrade script.') |
|
||||||
|
|
||||||
for path in args: |
|
||||||
scan_path(path, teardown=not options.no_teardown) |
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': |
|
||||||
main() |
|
@ -1,125 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
flaskext_compat |
|
||||||
~~~~~~~~~~~~~~~ |
|
||||||
|
|
||||||
Implements the ``flask.ext`` virtual package for versions of Flask |
|
||||||
older than 0.7. This module is a noop if Flask 0.8 was detected. |
|
||||||
|
|
||||||
Usage:: |
|
||||||
|
|
||||||
import flaskext_compat |
|
||||||
flaskext_compat.activate() |
|
||||||
from flask.ext import foo |
|
||||||
|
|
||||||
:copyright: (c) 2015 by Armin Ronacher. |
|
||||||
:license: BSD, see LICENSE for more details. |
|
||||||
""" |
|
||||||
import types |
|
||||||
import sys |
|
||||||
import os |
|
||||||
|
|
||||||
|
|
||||||
class ExtensionImporter(object): |
|
||||||
"""This importer redirects imports from this submodule to other locations. |
|
||||||
This makes it possible to transition from the old flaskext.name to the |
|
||||||
newer flask_name without people having a hard time. |
|
||||||
""" |
|
||||||
|
|
||||||
def __init__(self, module_choices, wrapper_module): |
|
||||||
self.module_choices = module_choices |
|
||||||
self.wrapper_module = wrapper_module |
|
||||||
self.prefix = wrapper_module + '.' |
|
||||||
self.prefix_cutoff = wrapper_module.count('.') + 1 |
|
||||||
|
|
||||||
def __eq__(self, other): |
|
||||||
return self.__class__.__module__ == other.__class__.__module__ and \ |
|
||||||
self.__class__.__name__ == other.__class__.__name__ and \ |
|
||||||
self.wrapper_module == other.wrapper_module and \ |
|
||||||
self.module_choices == other.module_choices |
|
||||||
|
|
||||||
def __ne__(self, other): |
|
||||||
return not self.__eq__(other) |
|
||||||
|
|
||||||
def install(self): |
|
||||||
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] |
|
||||||
|
|
||||||
def find_module(self, fullname, path=None): |
|
||||||
if fullname.startswith(self.prefix): |
|
||||||
return self |
|
||||||
|
|
||||||
def load_module(self, fullname): |
|
||||||
if fullname in sys.modules: |
|
||||||
return sys.modules[fullname] |
|
||||||
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] |
|
||||||
for path in self.module_choices: |
|
||||||
realname = path % modname |
|
||||||
try: |
|
||||||
__import__(realname) |
|
||||||
except ImportError: |
|
||||||
exc_type, exc_value, tb = sys.exc_info() |
|
||||||
# since we only establish the entry in sys.modules at the |
|
||||||
# end this seems to be redundant, but if recursive imports |
|
||||||
# happen we will call into the move import a second time. |
|
||||||
# On the second invocation we still don't have an entry for |
|
||||||
# fullname in sys.modules, but we will end up with the same |
|
||||||
# fake module name and that import will succeed since this |
|
||||||
# one already has a temporary entry in the modules dict. |
|
||||||
# Since this one "succeeded" temporarily that second |
|
||||||
# invocation now will have created a fullname entry in |
|
||||||
# sys.modules which we have to kill. |
|
||||||
sys.modules.pop(fullname, None) |
|
||||||
|
|
||||||
# If it's an important traceback we reraise it, otherwise |
|
||||||
# we swallow it and try the next choice. The skipped frame |
|
||||||
# is the one from __import__ above which we don't care about. |
|
||||||
if self.is_important_traceback(realname, tb): |
|
||||||
raise exc_type, exc_value, tb.tb_next |
|
||||||
continue |
|
||||||
module = sys.modules[fullname] = sys.modules[realname] |
|
||||||
if '.' not in modname: |
|
||||||
setattr(sys.modules[self.wrapper_module], modname, module) |
|
||||||
return module |
|
||||||
raise ImportError('No module named %s' % fullname) |
|
||||||
|
|
||||||
def is_important_traceback(self, important_module, tb): |
|
||||||
"""Walks a traceback's frames and checks if any of the frames |
|
||||||
originated in the given important module. If that is the case then we |
|
||||||
were able to import the module itself but apparently something went |
|
||||||
wrong when the module was imported. (Eg: import of an import failed). |
|
||||||
""" |
|
||||||
while tb is not None: |
|
||||||
if self.is_important_frame(important_module, tb): |
|
||||||
return True |
|
||||||
tb = tb.tb_next |
|
||||||
return False |
|
||||||
|
|
||||||
def is_important_frame(self, important_module, tb): |
|
||||||
"""Checks a single frame if it's important.""" |
|
||||||
g = tb.tb_frame.f_globals |
|
||||||
if '__name__' not in g: |
|
||||||
return False |
|
||||||
|
|
||||||
module_name = g['__name__'] |
|
||||||
|
|
||||||
# Python 2.7 Behavior. Modules are cleaned up late so the |
|
||||||
# name shows up properly here. Success! |
|
||||||
if module_name == important_module: |
|
||||||
return True |
|
||||||
|
|
||||||
# Some python versions will clean up modules so early that the |
|
||||||
# module name at that point is no longer set. Try guessing from |
|
||||||
# the filename then. |
|
||||||
filename = os.path.abspath(tb.tb_frame.f_code.co_filename) |
|
||||||
test_string = os.path.sep + important_module.replace('.', os.path.sep) |
|
||||||
return test_string + '.py' in filename or \ |
|
||||||
test_string + os.path.sep + '__init__.py' in filename |
|
||||||
|
|
||||||
|
|
||||||
def activate(): |
|
||||||
import flask |
|
||||||
ext_module = types.ModuleType('flask.ext') |
|
||||||
ext_module.__path__ = [] |
|
||||||
flask.ext = sys.modules['flask.ext'] = ext_module |
|
||||||
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], 'flask.ext') |
|
||||||
importer.install() |
|
@ -1,4 +1,4 @@ |
|||||||
{ |
{ |
||||||
"TEST_KEY": "foo", |
"TEST_KEY": "foo", |
||||||
"SECRET_KEY": "devkey" |
"SECRET_KEY": "config" |
||||||
} |
} |
||||||
|
@ -1,39 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
tests.deprecations |
|
||||||
~~~~~~~~~~~~~~~~~~ |
|
||||||
|
|
||||||
Tests deprecation support. Not used currently. |
|
||||||
|
|
||||||
:copyright: (c) 2015 by Armin Ronacher. |
|
||||||
:license: BSD, see LICENSE for more details. |
|
||||||
""" |
|
||||||
|
|
||||||
import pytest |
|
||||||
|
|
||||||
import flask |
|
||||||
|
|
||||||
|
|
||||||
class TestRequestDeprecation(object): |
|
||||||
def test_request_json(self, recwarn, app, client): |
|
||||||
"""Request.json is deprecated""" |
|
||||||
|
|
||||||
@app.route('/', methods=['POST']) |
|
||||||
def index(): |
|
||||||
assert flask.request.json == {'spam': 42} |
|
||||||
print(flask.request.json) |
|
||||||
return 'OK' |
|
||||||
|
|
||||||
client.post('/', data='{"spam": 42}', content_type='application/json') |
|
||||||
recwarn.pop(DeprecationWarning) |
|
||||||
|
|
||||||
def test_request_module(self, recwarn, app, client): |
|
||||||
"""Request.module is deprecated""" |
|
||||||
|
|
||||||
@app.route('/') |
|
||||||
def index(): |
|
||||||
assert flask.request.module is None |
|
||||||
return 'OK' |
|
||||||
|
|
||||||
client.get('/') |
|
||||||
recwarn.pop(DeprecationWarning) |
|
@ -1,197 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
tests.ext |
|
||||||
~~~~~~~~~~~~~~~~~~~ |
|
||||||
|
|
||||||
Tests the extension import thing. |
|
||||||
|
|
||||||
:copyright: (c) 2015 by Armin Ronacher. |
|
||||||
:license: BSD, see LICENSE for more details. |
|
||||||
""" |
|
||||||
|
|
||||||
import sys |
|
||||||
import pytest |
|
||||||
|
|
||||||
try: |
|
||||||
from imp import reload as reload_module |
|
||||||
except ImportError: |
|
||||||
reload_module = reload |
|
||||||
|
|
||||||
from flask._compat import PY2 |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True) |
|
||||||
def disable_extwarnings(recwarn): |
|
||||||
from flask.exthook import ExtDeprecationWarning |
|
||||||
|
|
||||||
yield |
|
||||||
|
|
||||||
assert set(w.category for w in recwarn.list) \ |
|
||||||
<= set([ExtDeprecationWarning]) |
|
||||||
recwarn.clear() |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True) |
|
||||||
def importhook_setup(monkeypatch): |
|
||||||
# we clear this out for various reasons. The most important one is |
|
||||||
# that a real flaskext could be in there which would disable our |
|
||||||
# fake package. Secondly we want to make sure that the flaskext |
|
||||||
# import hook does not break on reloading. |
|
||||||
for entry, value in list(sys.modules.items()): |
|
||||||
if ( |
|
||||||
entry.startswith('flask.ext.') or |
|
||||||
entry.startswith('flask_') or |
|
||||||
entry.startswith('flaskext.') or |
|
||||||
entry == 'flaskext' |
|
||||||
) and value is not None: |
|
||||||
monkeypatch.delitem(sys.modules, entry) |
|
||||||
from flask import ext |
|
||||||
reload_module(ext) |
|
||||||
|
|
||||||
# reloading must not add more hooks |
|
||||||
import_hooks = 0 |
|
||||||
for item in sys.meta_path: |
|
||||||
cls = type(item) |
|
||||||
if cls.__module__ == 'flask.exthook' and \ |
|
||||||
cls.__name__ == 'ExtensionImporter': |
|
||||||
import_hooks += 1 |
|
||||||
assert import_hooks == 1 |
|
||||||
|
|
||||||
yield |
|
||||||
|
|
||||||
from flask import ext |
|
||||||
for key in ext.__dict__: |
|
||||||
assert '.' not in key |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture |
|
||||||
def newext_simple(modules_tmpdir): |
|
||||||
x = modules_tmpdir.join('flask_newext_simple.py') |
|
||||||
x.write('ext_id = "newext_simple"') |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture |
|
||||||
def oldext_simple(modules_tmpdir): |
|
||||||
flaskext = modules_tmpdir.mkdir('flaskext') |
|
||||||
flaskext.join('__init__.py').write('\n') |
|
||||||
flaskext.join('oldext_simple.py').write('ext_id = "oldext_simple"') |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture |
|
||||||
def newext_package(modules_tmpdir): |
|
||||||
pkg = modules_tmpdir.mkdir('flask_newext_package') |
|
||||||
pkg.join('__init__.py').write('ext_id = "newext_package"') |
|
||||||
pkg.join('submodule.py').write('def test_function():\n return 42\n') |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture |
|
||||||
def oldext_package(modules_tmpdir): |
|
||||||
flaskext = modules_tmpdir.mkdir('flaskext') |
|
||||||
flaskext.join('__init__.py').write('\n') |
|
||||||
oldext = flaskext.mkdir('oldext_package') |
|
||||||
oldext.join('__init__.py').write('ext_id = "oldext_package"') |
|
||||||
oldext.join('submodule.py').write('def test_function():\n' |
|
||||||
' return 42') |
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture |
|
||||||
def flaskext_broken(modules_tmpdir): |
|
||||||
ext = modules_tmpdir.mkdir('flask_broken') |
|
||||||
ext.join('b.py').write('\n') |
|
||||||
ext.join('__init__.py').write('import flask.ext.broken.b\n' |
|
||||||
'import missing_module') |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_simple_import_normal(newext_simple): |
|
||||||
from flask.ext.newext_simple import ext_id |
|
||||||
assert ext_id == 'newext_simple' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_simple_import_module(newext_simple): |
|
||||||
from flask.ext import newext_simple |
|
||||||
assert newext_simple.ext_id == 'newext_simple' |
|
||||||
assert newext_simple.__name__ == 'flask_newext_simple' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_package_import_normal(newext_package): |
|
||||||
from flask.ext.newext_package import ext_id |
|
||||||
assert ext_id == 'newext_package' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_package_import_module(newext_package): |
|
||||||
from flask.ext import newext_package |
|
||||||
assert newext_package.ext_id == 'newext_package' |
|
||||||
assert newext_package.__name__ == 'flask_newext_package' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_package_import_submodule_function(newext_package): |
|
||||||
from flask.ext.newext_package.submodule import test_function |
|
||||||
assert test_function() == 42 |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_new_package_import_submodule(newext_package): |
|
||||||
from flask.ext.newext_package import submodule |
|
||||||
assert submodule.__name__ == 'flask_newext_package.submodule' |
|
||||||
assert submodule.test_function() == 42 |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_simple_import_normal(oldext_simple): |
|
||||||
from flask.ext.oldext_simple import ext_id |
|
||||||
assert ext_id == 'oldext_simple' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_simple_import_module(oldext_simple): |
|
||||||
from flask.ext import oldext_simple |
|
||||||
assert oldext_simple.ext_id == 'oldext_simple' |
|
||||||
assert oldext_simple.__name__ == 'flaskext.oldext_simple' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_package_import_normal(oldext_package): |
|
||||||
from flask.ext.oldext_package import ext_id |
|
||||||
assert ext_id == 'oldext_package' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_package_import_module(oldext_package): |
|
||||||
from flask.ext import oldext_package |
|
||||||
assert oldext_package.ext_id == 'oldext_package' |
|
||||||
assert oldext_package.__name__ == 'flaskext.oldext_package' |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_package_import_submodule(oldext_package): |
|
||||||
from flask.ext.oldext_package import submodule |
|
||||||
assert submodule.__name__ == 'flaskext.oldext_package.submodule' |
|
||||||
assert submodule.test_function() == 42 |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_old_package_import_submodule_function(oldext_package): |
|
||||||
from flask.ext.oldext_package.submodule import test_function |
|
||||||
assert test_function() == 42 |
|
||||||
|
|
||||||
|
|
||||||
def test_flaskext_broken_package_no_module_caching(flaskext_broken): |
|
||||||
for x in range(2): |
|
||||||
with pytest.raises(ImportError): |
|
||||||
import flask.ext.broken |
|
||||||
|
|
||||||
|
|
||||||
def test_no_error_swallowing(flaskext_broken): |
|
||||||
with pytest.raises(ImportError) as excinfo: |
|
||||||
import flask.ext.broken |
|
||||||
# python3.6 raises a subclass of ImportError: 'ModuleNotFoundError' |
|
||||||
assert issubclass(excinfo.type, ImportError) |
|
||||||
if PY2: |
|
||||||
message = 'No module named missing_module' |
|
||||||
else: |
|
||||||
message = 'No module named \'missing_module\'' |
|
||||||
assert str(excinfo.value) == message |
|
||||||
assert excinfo.tb.tb_frame.f_globals is globals() |
|
||||||
|
|
||||||
# reraise() adds a second frame so we need to skip that one too. |
|
||||||
# On PY3 we even have another one :( |
|
||||||
next = excinfo.tb.tb_next.tb_next |
|
||||||
if not PY2: |
|
||||||
next = next.tb_next |
|
||||||
|
|
||||||
import os.path |
|
||||||
assert os.path.join('flask_broken', '__init__.py') in \ |
|
||||||
next.tb_frame.f_code.co_filename |
|
Loading…
Reference in new issue