Browse Source

Merge branch 'master' into feature/fix-flask-app-py-extension

pull/2383/head
Ben Congdon 7 years ago committed by GitHub
parent
commit
106be88952
  1. 20
      CHANGES
  2. 12
      docs/api.rst
  3. 11
      docs/config.rst
  4. 4
      docs/patterns/distribute.rst
  5. 2
      docs/patterns/flashing.rst
  6. 23
      docs/quickstart.rst
  7. 8
      docs/tutorial/setup.rst
  8. 2
      examples/flaskr/flaskr/factory.py
  9. 2
      examples/minitwit/minitwit/minitwit.py
  10. 90
      flask/app.py
  11. 7
      flask/cli.py
  12. 29
      flask/ext/__init__.py
  13. 143
      flask/exthook.py
  14. 38
      flask/helpers.py
  15. 25
      flask/wrappers.py
  16. 298
      scripts/flask-07-upgrade.py
  17. 125
      scripts/flaskext_compat.py
  18. 2
      setup.py
  19. 7
      tests/conftest.py
  20. 2
      tests/static/config.json
  21. 38
      tests/test_basic.py
  22. 13
      tests/test_config.py
  23. 39
      tests/test_deprecations.py
  24. 197
      tests/test_ext.py
  25. 5
      tests/test_signals.py
  26. 2
      tests/test_templating.py
  27. 6
      tests/test_testing.py
  28. 2
      tox.ini

20
CHANGES

@ -84,7 +84,24 @@ Major release, unreleased
if ``app.jinja_env`` was already accessed. (`#2373`_)
- The ``flask`` command no longer requires that the ``FLASK_APP`` environment
variable have a ``.py`` extension. (`#2383`_)
- 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``,
use ``@app.errorhandler`` to register handlers.
- ``Flask.request_globals_class`` - use ``Flask.app_ctx_globals_class``
instead.
- ``Flask.static_path`` - use ``Flask.static_url_path`` instead.
- ``Request.module`` - use ``Request.blueprint`` instead.
- The ``request.json`` property is no longer deprecated. (`#1421`_)
.. _#1421: https://github.com/pallets/flask/issues/1421
.. _#1489: https://github.com/pallets/flask/pull/1489
.. _#1621: https://github.com/pallets/flask/pull/1621
.. _#1898: https://github.com/pallets/flask/pull/1898
@ -110,6 +127,7 @@ Major release, unreleased
.. _#2374: https://github.com/pallets/flask/pull/2374
.. _#2373: https://github.com/pallets/flask/pull/2373
.. _#2383: https://github.com/pallets/flask/pull/2383
.. _#2385: https://github.com/pallets/flask/issues/2385
Version 0.12.2
--------------

12
docs/api.rst

@ -103,12 +103,12 @@ Response Objects
Sessions
--------
If you have the :attr:`Flask.secret_key` set you can use sessions in Flask
applications. A session basically makes it possible to remember
information from one request to another. The way Flask does this is by
using a signed cookie. So the user can look at the session contents, but
not modify it unless they know the secret key, so make sure to set that
to something complex and unguessable.
If you have set :attr:`Flask.secret_key` (or configured it from
:data:`SECRET_KEY`) you can use sessions in Flask applications. A session makes
it possible to remember information from one request to another. The way Flask
does this is by using a signed cookie. The user can look at the session
contents, but can't modify it unless they know the secret key, so make sure to
set that to something complex and unguessable.
To access the current session you can use the :class:`session` object:

11
docs/config.rst

@ -39,7 +39,7 @@ method::
app.config.update(
DEBUG=True,
SECRET_KEY='...'
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/'
)
.. admonition:: Debug Mode with the ``flask`` Script
@ -121,7 +121,8 @@ The following configuration values are used internally by Flask:
application. It should be a long random string of bytes, although unicode
is accepted too. For example, copy the output of this to your config::
python -c 'import os; print(os.urandom(32))'
python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
**Do not reveal the secret key when posting questions or committing code.**
@ -367,7 +368,7 @@ Here is an example of a configuration file::
# Example configuration
DEBUG = False
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
Make sure to load the configuration very early on, so that extensions have
the ability to access the configuration when starting up. There are other
@ -385,7 +386,7 @@ from the environment.
Environment variables can be set on Linux or OS X with the export command in
the shell before starting the server::
$ export SECRET_KEY='?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
$ export SECRET_KEY='5f352379324c22463451387a0aec5d2f'
$ export DEBUG=False
$ python run-app.py
* Running on http://127.0.0.1:5000/
@ -393,7 +394,7 @@ the shell before starting the server::
On Windows systems use the `set` builtin instead::
>set SECRET_KEY='?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
>set SECRET_KEY='5f352379324c22463451387a0aec5d2f'
>set DEBUG=False
While this approach is straightforward to use, it is important to remember that

4
docs/patterns/distribute.rst

@ -88,8 +88,8 @@ support them and they make debugging a lot harder.
Tagging Builds
--------------
It is useful to distinguish between release and development builds. Add a
:file:`setup.cfg` file to configure these options.
It is useful to distinguish between release and development builds. Add a
:file:`setup.cfg` file to configure these options. ::
[egg_info]
tag_build = .dev

2
docs/patterns/flashing.rst

@ -22,7 +22,7 @@ So here is a full example::
request, url_for
app = Flask(__name__)
app.secret_key = 'some_secret'
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():

23
docs/quickstart.rst

@ -160,7 +160,7 @@ Screenshot of the debugger in action:
:class: screenshot
:alt: screenshot of debugger in action
More information on using the debugger can be found in the `Werkzeug
More information on using the debugger can be found in the `Werkzeug
documentation`_.
.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger
@ -724,6 +724,9 @@ sessions work::
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
if 'username' in session:
@ -748,24 +751,18 @@ sessions work::
session.pop('username', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
The :func:`~flask.escape` mentioned here does escaping for you if you are
not using the template engine (as in this example).
.. admonition:: How to generate good secret keys
The problem with random is that it's hard to judge what is truly random. And
a secret key should be as random as possible. Your operating system
has ways to generate pretty random stuff based on a cryptographic
random generator which can be used to get such a key::
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
A secret key should be as random as possible. Your operating system has
ways to generate pretty random data based on a cryptographic random
generator. Use the following command to quickly generate a value for
:attr:`Flask.secret_key` (or :data:`SECRET_KEY`)::
Just take that thing and copy/paste it into your code and you're done.
$ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
A note on cookie-based sessions: Flask will take the values you put into the
session object and serialize them into a cookie. If you are finding some

8
docs/tutorial/setup.rst

@ -33,12 +33,12 @@ initialize it with the config from the same file in :file:`flaskr.py`::
app.config.from_object(__name__) # load config from this file , flaskr.py
# Load default config and override config from an environment variable
app.config.update(dict(
app.config.update(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
SECRET_KEY='development key',
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/',
USERNAME='admin',
PASSWORD='default'
))
)
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
In the above code, the :class:`~flask.Config` object works similarly to a
@ -77,7 +77,7 @@ method on the config object and provide it with an import name of a
module. Flask will then initialize the variable from that module. Note
that in all cases, only variable names that are uppercase are considered.
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
The :data:`SECRET_KEY` is needed to keep the client-side sessions secure.
Choose that key wisely and as hard to guess and complex as possible.
Lastly, add a method that allows for easy connections to the specified

2
examples/flaskr/flaskr/factory.py

@ -22,7 +22,7 @@ def create_app(config=None):
app.config.update(dict(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
DEBUG=True,
SECRET_KEY='development key',
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/',
USERNAME='admin',
PASSWORD='default'
))

2
examples/minitwit/minitwit/minitwit.py

@ -22,7 +22,7 @@ from werkzeug import check_password_hash, generate_password_hash
DATABASE = '/tmp/minitwit.db'
PER_PAGE = 30
DEBUG = True
SECRET_KEY = 'development key'
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
# create our little application :)
app = Flask('minitwit')

90
flask/app.py

@ -188,18 +188,6 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.10
app_ctx_globals_class = _AppCtxGlobals
# Backwards compatibility support
def _get_request_globals_class(self):
return self.app_ctx_globals_class
def _set_request_globals_class(self, value):
from warnings import warn
warn(DeprecationWarning('request_globals_class attribute is now '
'called app_ctx_globals_class'))
self.app_ctx_globals_class = value
request_globals_class = property(_get_request_globals_class,
_set_request_globals_class)
del _get_request_globals_class, _set_request_globals_class
#: The class that is used for the ``config`` attribute of this app.
#: Defaults to :class:`~flask.Config`.
#:
@ -233,11 +221,11 @@ class Flask(_PackageBoundObject):
testing = ConfigAttribute('TESTING')
#: If a secret key is set, cryptographic components can use this to
#: sign cookies and other things. Set this to a complex random value
#: sign cookies and other things. Set this to a complex random value
#: when you want to use the secure cookie for instance.
#:
#: This attribute can also be configured from the config with the
#: ``SECRET_KEY`` configuration key. Defaults to ``None``.
#: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
secret_key = ConfigAttribute('SECRET_KEY')
#: The secure cookie uses this for the name of the session cookie.
@ -361,29 +349,38 @@ class Flask(_PackageBoundObject):
#: resources contained in the package.
root_path = None
def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', static_host=None,
host_matching=False, template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
_PackageBoundObject.__init__(self, import_name,
template_folder=template_folder,
root_path=root_path)
if static_path is not None:
from warnings import warn
warn(DeprecationWarning('static_path is now called '
'static_url_path'), stacklevel=2)
static_url_path = static_path
def __init__(
self,
import_name,
static_url_path=None,
static_folder='static',
static_host=None,
host_matching=False,
template_folder='templates',
instance_path=None,
instance_relative_config=False,
root_path=None
):
_PackageBoundObject.__init__(
self,
import_name,
template_folder=template_folder,
root_path=root_path
)
if static_url_path is not None:
self.static_url_path = static_url_path
if static_folder is not None:
self.static_folder = static_folder
if instance_path is None:
instance_path = self.auto_find_instance_path()
elif not os.path.isabs(instance_path):
raise ValueError('If an instance path is provided it must be '
'absolute. A relative path was given instead.')
raise ValueError(
'If an instance path is provided it must be absolute.'
' A relative path was given instead.'
)
#: Holds the path to the instance folder.
#:
@ -405,10 +402,6 @@ class Flask(_PackageBoundObject):
#: To register a view function, use the :meth:`route` decorator.
self.view_functions = {}
# support for the now deprecated `error_handlers` attribute. The
# :attr:`error_handler_spec` shall be used now.
self._error_handlers = {}
#: A dictionary of all registered error handlers. The key is ``None``
#: for error handlers active on the application, otherwise the key is
#: the name of the blueprint. Each key points to another dictionary
@ -419,7 +412,7 @@ class Flask(_PackageBoundObject):
#:
#: To register an error handler, use the :meth:`errorhandler`
#: decorator.
self.error_handler_spec = {None: self._error_handlers}
self.error_handler_spec = {}
#: A list of functions that are called when :meth:`url_for` raises a
#: :exc:`~werkzeug.routing.BuildError`. Each function registered here
@ -563,9 +556,12 @@ class Flask(_PackageBoundObject):
# development). Also, Google App Engine stores static files somewhere
if self.has_static_folder:
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
self.add_url_rule(self.static_url_path + '/<path:filename>',
endpoint='static', host=static_host,
view_func=self.send_static_file)
self.add_url_rule(
self.static_url_path + '/<path:filename>',
endpoint='static',
host=static_host,
view_func=self.send_static_file
)
#: The click command line context for this application. Commands
#: registered here show up in the :command:`flask` command once the
@ -575,17 +571,6 @@ class Flask(_PackageBoundObject):
#: This is an instance of a :class:`click.Group` object.
self.cli = cli.AppGroup(self.name)
def _get_error_handlers(self):
from warnings import warn
warn(DeprecationWarning('error_handlers is deprecated, use the '
'new error_handler_spec attribute instead.'), stacklevel=1)
return self._error_handlers
def _set_error_handlers(self, value):
self._error_handlers = value
self.error_handler_spec[None] = value
error_handlers = property(_get_error_handlers, _set_error_handlers)
del _get_error_handlers, _set_error_handlers
@locked_cached_property
def name(self):
"""The name of the application. This is usually the import name
@ -772,15 +757,6 @@ class Flask(_PackageBoundObject):
"""
return DispatchingJinjaLoader(self)
def init_jinja_globals(self):
"""Deprecated. Used to initialize the Jinja2 globals.
.. versionadded:: 0.5
.. versionchanged:: 0.7
This method is deprecated with 0.7. Override
:meth:`create_jinja_environment` instead.
"""
def select_jinja_autoescape(self, filename):
"""Returns ``True`` if autoescaping should be active for the given
template name. If no template name is given, returns `True`.

7
flask/cli.py

@ -211,9 +211,10 @@ def locate_app(script_info, app_id, raise_if_not_found=True):
)
elif raise_if_not_found:
raise NoAppException(
'The file/path provided (%s) does not appear to exist. Please'
' verify the path is correct. If app is not on PYTHONPATH,'
' ensure the extension is .py.'.format(module=module)
'The file/path provided ({module}) does not appear to exist.'
' Please verify the path is correct. If app is not on'
' PYTHONPATH, ensure the extension is .py.'.format(
module=module)
)
else:
return

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

38
flask/helpers.py

@ -268,40 +268,40 @@ def url_for(endpoint, **values):
"""
appctx = _app_ctx_stack.top
reqctx = _request_ctx_stack.top
if appctx is None:
raise RuntimeError('Attempted to generate a URL without the '
'application context being pushed. This has to be '
'executed when application context is available.')
raise RuntimeError(
'Attempted to generate a URL without the application context being'
' pushed. This has to be executed when application context is'
' available.'
)
# If request specific information is available we have some extra
# features that support "relative" URLs.
if reqctx is not None:
url_adapter = reqctx.url_adapter
blueprint_name = request.blueprint
if not reqctx.request._is_old_module:
if endpoint[:1] == '.':
if blueprint_name is not None:
endpoint = blueprint_name + endpoint
else:
endpoint = endpoint[1:]
else:
# TODO: get rid of this deprecated functionality in 1.0
if '.' not in endpoint:
if blueprint_name is not None:
endpoint = blueprint_name + '.' + endpoint
elif endpoint.startswith('.'):
if endpoint[:1] == '.':
if blueprint_name is not None:
endpoint = blueprint_name + endpoint
else:
endpoint = endpoint[1:]
external = values.pop('_external', False)
# Otherwise go with the url adapter from the appctx and make
# the URLs external by default.
else:
url_adapter = appctx.url_adapter
if url_adapter is None:
raise RuntimeError('Application was not able to create a URL '
'adapter for request independent URL generation. '
'You might be able to fix this by setting '
'the SERVER_NAME config variable.')
raise RuntimeError(
'Application was not able to create a URL adapter for request'
' independent URL generation. You might be able to fix this by'
' setting the SERVER_NAME config variable.'
)
external = values.pop('_external', True)
anchor = values.pop('_anchor', None)

25
flask/wrappers.py

@ -8,8 +8,6 @@
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from warnings import warn
from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
@ -44,13 +42,7 @@ class JSONMixin(object):
"""This will contain the parsed JSON data if the mimetype indicates
JSON (:mimetype:`application/json`, see :meth:`is_json`), otherwise it
will be ``None``.
.. deprecated:: 1.0
Use :meth:`get_json` instead.
"""
warn(DeprecationWarning(
"'json' is deprecated. Use 'get_json()' instead."
), stacklevel=2)
return self.get_json()
def _get_data_for_json(self, cache):
@ -141,10 +133,6 @@ class Request(RequestBase, JSONMixin):
#: something similar.
routing_exception = None
# Switched by the request context until 1.0 to opt in deprecated
# module functionality.
_is_old_module = False
@property
def max_content_length(self):
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
@ -161,19 +149,6 @@ class Request(RequestBase, JSONMixin):
if self.url_rule is not None:
return self.url_rule.endpoint
@property
def module(self):
"""The name of the current module if the request was dispatched
to an actual module. This is deprecated functionality, use blueprints
instead.
"""
from warnings import warn
warn(DeprecationWarning('modules were deprecated in favor of '
'blueprints. Use request.blueprint '
'instead.'), stacklevel=2)
if self._is_old_module:
return self.blueprint
@property
def blueprint(self):
"""The name of the current blueprint"""

298
scripts/flask-07-upgrade.py

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

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

7
tests/conftest.py

@ -18,12 +18,7 @@ from flask import Flask as _Flask
class Flask(_Flask):
testing = True
secret_key = __name__
def make_response(self, rv):
if rv is None:
rv = ''
return super(Flask, self).make_response(rv)
secret_key = 'test key'
@pytest.fixture

2
tests/static/config.json

@ -1,4 +1,4 @@
{
"TEST_KEY": "foo",
"SECRET_KEY": "devkey"
"SECRET_KEY": "config"
}

38
tests/test_basic.py

@ -9,7 +9,6 @@
:license: BSD, see LICENSE for more details.
"""
import pickle
import re
import time
import uuid
@ -220,8 +219,6 @@ def test_endpoint_decorator(app, client):
def test_session(app, client):
app.secret_key = 'testkey'
@app.route('/set', methods=['POST'])
def set():
flask.session['value'] = flask.request.form['value']
@ -237,7 +234,6 @@ def test_session(app, client):
def test_session_using_server_name(app, client):
app.config.update(
SECRET_KEY='foo',
SERVER_NAME='example.com'
)
@ -253,7 +249,6 @@ def test_session_using_server_name(app, client):
def test_session_using_server_name_and_port(app, client):
app.config.update(
SECRET_KEY='foo',
SERVER_NAME='example.com:8080'
)
@ -269,7 +264,6 @@ def test_session_using_server_name_and_port(app, client):
def test_session_using_server_name_port_and_path(app, client):
app.config.update(
SECRET_KEY='foo',
SERVER_NAME='example.com:8080',
APPLICATION_ROOT='/foo'
)
@ -297,7 +291,6 @@ def test_session_using_application_root(app, client):
app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar')
app.config.update(
SECRET_KEY='foo',
APPLICATION_ROOT='/bar'
)
@ -312,7 +305,6 @@ def test_session_using_application_root(app, client):
def test_session_using_session_settings(app, client):
app.config.update(
SECRET_KEY='foo',
SERVER_NAME='www.example.com:8080',
APPLICATION_ROOT='/test',
SESSION_COOKIE_DOMAIN='.example.com',
@ -336,7 +328,6 @@ def test_session_using_session_settings(app, client):
def test_session_localhost_warning(recwarn, app, client):
app.config.update(
SECRET_KEY='testing',
SERVER_NAME='localhost:5000',
)
@ -353,7 +344,6 @@ def test_session_localhost_warning(recwarn, app, client):
def test_session_ip_warning(recwarn, app, client):
app.config.update(
SECRET_KEY='testing',
SERVER_NAME='127.0.0.1:5000',
)
@ -368,8 +358,8 @@ def test_session_ip_warning(recwarn, app, client):
assert 'cookie domain is an IP' in str(w.message)
def test_missing_session():
app = flask.Flask(__name__)
def test_missing_session(app):
app.secret_key = None
def expect_exception(f, *args, **kwargs):
e = pytest.raises(RuntimeError, f, *args, **kwargs)
@ -383,7 +373,6 @@ def test_missing_session():
def test_session_expiration(app, client):
permanent = True
app.secret_key = 'testkey'
@app.route('/')
def index():
@ -415,8 +404,6 @@ def test_session_expiration(app, client):
def test_session_stored_last(app, client):
app.secret_key = 'development-key'
@app.after_request
def modify_session(response):
flask.session['foo'] = 42
@ -431,7 +418,6 @@ def test_session_stored_last(app, client):
def test_session_special_types(app, client):
app.secret_key = 'development-key'
now = datetime.utcnow().replace(microsecond=0)
the_uuid = uuid.uuid4()
@ -463,7 +449,6 @@ def test_session_special_types(app, client):
def test_session_cookie_setting(app):
app.secret_key = 'dev key'
is_permanent = True
@app.route('/bump')
@ -505,8 +490,6 @@ def test_session_cookie_setting(app):
def test_session_vary_cookie(app, client):
app.secret_key = 'testkey'
@app.route('/set')
def set_session():
flask.session['test'] = 'test'
@ -562,8 +545,6 @@ def test_session_vary_cookie(app, client):
def test_flashes(app, req_ctx):
app.secret_key = 'testkey'
assert not flask.session.modified
flask.flash('Zap')
flask.session.modified = False
@ -579,8 +560,6 @@ def test_extended_flashing(app):
# in the view functions will cause a 500 response to the test client
# instead of propagating exceptions.
app.secret_key = 'testkey'
@app.route('/')
def index():
flask.flash(u'Hello World')
@ -1353,19 +1332,6 @@ def test_static_files(app, client):
rv.close()
def test_static_path_deprecated(recwarn):
app = flask.Flask(__name__, static_path='/foo')
recwarn.pop(DeprecationWarning)
app.testing = True
rv = app.test_client().get('/foo/index.html')
assert rv.status_code == 200
rv.close()
with app.test_request_context():
assert flask.url_for('static', filename='index.html') == '/foo/index.html'
def test_static_url_path():
app = flask.Flask(__name__, static_url_path='/foo')
app.testing = True

13
tests/test_config.py

@ -19,11 +19,11 @@ import pytest
# config keys used for the TestConfig
TEST_KEY = 'foo'
SECRET_KEY = 'devkey'
SECRET_KEY = 'config'
def common_object_test(app):
assert app.secret_key == 'devkey'
assert app.secret_key == 'config'
assert app.config['TEST_KEY'] == 'foo'
assert 'TestConfig' not in app.config
@ -50,21 +50,21 @@ def test_config_from_json():
def test_config_from_mapping():
app = flask.Flask(__name__)
app.config.from_mapping({
'SECRET_KEY': 'devkey',
'SECRET_KEY': 'config',
'TEST_KEY': 'foo'
})
common_object_test(app)
app = flask.Flask(__name__)
app.config.from_mapping([
('SECRET_KEY', 'devkey'),
('SECRET_KEY', 'config'),
('TEST_KEY', 'foo')
])
common_object_test(app)
app = flask.Flask(__name__)
app.config.from_mapping(
SECRET_KEY='devkey',
SECRET_KEY='config',
TEST_KEY='foo'
)
common_object_test(app)
@ -81,7 +81,8 @@ def test_config_from_class():
TEST_KEY = 'foo'
class Test(Base):
SECRET_KEY = 'devkey'
SECRET_KEY = 'config'
app = flask.Flask(__name__)
app.config.from_object(Test)
common_object_test(app)

39
tests/test_deprecations.py

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

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

5
tests/test_signals.py

@ -157,10 +157,7 @@ def test_appcontext_signals():
flask.appcontext_popped.disconnect(record_pop, app)
def test_flash_signal():
app = flask.Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
def test_flash_signal(app):
@app.route('/')
def index():
flask.flash('This is a flash message', category='notice')

2
tests/test_templating.py

@ -52,8 +52,6 @@ def test_request_less_rendering(app, app_ctx):
def test_standard_context(app, client):
app.secret_key = 'development key'
@app.route('/')
def index():
flask.g.foo = 23

6
tests/test_testing.py

@ -107,8 +107,6 @@ def test_blueprint_with_subdomain(app, client):
def test_redirect_keep_session(app, client, app_ctx):
app.secret_key = 'testing'
@app.route('/', methods=['GET', 'POST'])
def index():
if flask.request.method == 'POST':
@ -139,8 +137,6 @@ def test_redirect_keep_session(app, client, app_ctx):
def test_session_transactions(app, client):
app.secret_key = 'testing'
@app.route('/')
def index():
return text_type(flask.session['foo'])
@ -169,8 +165,6 @@ def test_session_transactions_no_null_sessions():
def test_session_transactions_keep_context(app, client, req_ctx):
app.secret_key = 'testing'
rv = client.get('/')
req = flask.request._get_current_object()
assert req is not None

2
tox.ini

@ -66,5 +66,5 @@ commands =
skip_install = true
deps = detox
commands =
detox -e py{36,35,34,33,27,26,py},py{36,27,py}-simplejson,py{36,33,27,26,py}-devel,py{36,33,27,26,py}-lowest,docs-html
detox -e py{36,35,34,33,27,26,py},py{36,27,py}-simplejson,py{36,33,27,26,py}-devel,py{36,33,27,26,py}-lowest
tox -e coverage-report

Loading…
Cancel
Save