diff --git a/CHANGES b/CHANGES index 18ccf0b9..7280e469 100644 --- a/CHANGES +++ b/CHANGES @@ -84,6 +84,9 @@ Major release, unreleased if ``app.jinja_env`` was already accessed. (`#2373`_) - The following old deprecated code was removed. (`#2385`_) + - ``flask.ext`` - import extensions directly by their name instead of + through the ``flask.ext`` namespace. For example, + ``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``. - ``Flask.init_jinja_globals`` - extend ``Flask.create_jinja_environment`` instead. - ``Flask.error_handlers`` - tracked by ``Flask.error_handler_spec``, diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py deleted file mode 100644 index 051f44ac..00000000 --- a/flask/ext/__init__.py +++ /dev/null @@ -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 diff --git a/flask/exthook.py b/flask/exthook.py deleted file mode 100644 index d8842802..00000000 --- a/flask/exthook.py +++ /dev/null @@ -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 diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py deleted file mode 100644 index 77d38c20..00000000 --- a/scripts/flaskext_compat.py +++ /dev/null @@ -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() diff --git a/setup.py b/setup.py index a829dd8a..acade846 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ setup( description='A microframework based on Werkzeug, Jinja2 ' 'and good intentions', long_description=__doc__, - packages=['flask', 'flask.ext', 'flask.json'], + packages=['flask', 'flask.json'], include_package_data=True, zip_safe=False, platforms='any', diff --git a/tests/test_ext.py b/tests/test_ext.py deleted file mode 100644 index 48214905..00000000 --- a/tests/test_ext.py +++ /dev/null @@ -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