mirror of https://github.com/mitsuhiko/flask.git
David Lord
8 years ago
6 changed files with 4 additions and 495 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,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,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