Browse Source

Merge pull request #2395 from davidism/remove-ext

Remove deprecated flask.ext
pull/2396/head
David Lord 7 years ago committed by GitHub
parent
commit
bce8ec75eb
  1. 3
      CHANGES
  2. 29
      flask/ext/__init__.py
  3. 143
      flask/exthook.py
  4. 125
      scripts/flaskext_compat.py
  5. 2
      setup.py
  6. 197
      tests/test_ext.py

3
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``,

29
flask/ext/__init__.py

@ -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

143
flask/exthook.py

@ -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

125
scripts/flaskext_compat.py

@ -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()

2
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',

197
tests/test_ext.py

@ -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…
Cancel
Save