From 86e48d3dcb957a5bf536ea43c3de9c77c693c50d Mon Sep 17 00:00:00 2001 From: Mayank_S05 Date: Sun, 19 Nov 2017 22:40:27 +0530 Subject: [PATCH 1/3] Add files via upload --- code/python2.txt | 9564 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 9564 insertions(+) create mode 100644 code/python2.txt diff --git a/code/python2.txt b/code/python2.txt new file mode 100644 index 0000000..3901a68 --- /dev/null +++ b/code/python2.txt @@ -0,0 +1,9564 @@ +""" + flask + ~~~~~ + + A microframework based on Werkzeug. It's extensively documented + and follows best practice patterns. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +__version__ = '0.13-dev' + +from werkzeug.exceptions import abort +from werkzeug.utils import redirect +from jinja2 import Markup, escape + +from .app import Flask, Request, Response +from .config import Config +from .helpers import url_for, flash, send_file, send_from_directory, \ + get_flashed_messages, get_template_attribute, make_response, safe_join, \ + stream_with_context +from .globals import current_app, g, request, session, _request_ctx_stack, \ + _app_ctx_stack +from .ctx import has_request_context, has_app_context, \ + after_this_request, copy_current_request_context +from .blueprints import Blueprint +from .templating import render_template, render_template_string + +from .signals import signals_available, template_rendered, request_started, \ + request_finished, got_request_exception, request_tearing_down, \ + appcontext_tearing_down, appcontext_pushed, \ + appcontext_popped, message_flashed, before_render_template + +from . import json + +jsonify = json.jsonify + +from .sessions import SecureCookieSession as Session +json_available = True + +""" + flask._compat + ~~~~~~~~~~~~~ + + Some py2/py3 compatibility support based on a stripped down + version of six so we don't have to depend on a specific version + of it. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys + +PY2 = sys.version_info[0] == 2 +_identity = lambda x: x + + +if not PY2: + text_type = str + string_types = (str,) + integer_types = (int,) + + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + from inspect import getfullargspec as getargspec + from io import StringIO + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + implements_to_string = _identity + +else: + text_type = unicode + string_types = (str, unicode) + integer_types = (int, long) + + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + from inspect import getargspec + from cStringIO import StringIO + + exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + return cls + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + class metaclass(type): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +BROKEN_PYPY_CTXMGR_EXIT = False +if hasattr(sys, 'pypy_version_info'): + class _Mgr(object): + def __enter__(self): + return self + def __exit__(self, *args): + if hasattr(sys, 'exc_clear'): + sys.exc_clear() + try: + try: + with _Mgr(): + raise AssertionError() + except: + raise + except TypeError: + BROKEN_PYPY_CTXMGR_EXIT = True + except AssertionError: + pass + +""" + flask._compat + ~~~~~~~~~~~~~ + + Some py2/py3 compatibility support based on a stripped down + version of six so we don't have to depend on a specific version + of it. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys + +PY2 = sys.version_info[0] == 2 +_identity = lambda x: x + + +if not PY2: + text_type = str + string_types = (str,) + integer_types = (int,) + + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + from inspect import getfullargspec as getargspec + from io import StringIO + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + implements_to_string = _identity + +else: + text_type = unicode + string_types = (str, unicode) + integer_types = (int, long) + + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + from inspect import getargspec + from cStringIO import StringIO + + exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + return cls + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + class metaclass(type): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +BROKEN_PYPY_CTXMGR_EXIT = False +if hasattr(sys, 'pypy_version_info'): + class _Mgr(object): + def __enter__(self): + return self + def __exit__(self, *args): + if hasattr(sys, 'exc_clear'): + sys.exc_clear() + try: + try: + with _Mgr(): + raise AssertionError() + except: + raise + except TypeError: + BROKEN_PYPY_CTXMGR_EXIT = True + except AssertionError: + pass + +""" + flask.app + ~~~~~~~~~ + + This module implements the central WSGI application object. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import warnings +from datetime import timedelta +from functools import update_wrapper +from itertools import chain +from threading import Lock + +from werkzeug.datastructures import Headers, ImmutableDict +from werkzeug.exceptions import BadRequest, BadRequestKeyError, HTTPException, \ + InternalServerError, MethodNotAllowed, default_exceptions +from werkzeug.routing import BuildError, Map, RequestRedirect, Rule + +from . import cli, json +from ._compat import integer_types, reraise, string_types, text_type +from .config import Config, ConfigAttribute +from .ctx import AppContext, RequestContext, _AppCtxGlobals +from .globals import _request_ctx_stack, g, request, session +from .helpers import _PackageBoundObject, \ + _endpoint_from_view_func, find_package, get_debug_flag, \ + get_flashed_messages, locked_cached_property, url_for +from .logging import create_logger +from .sessions import SecureCookieSessionInterface +from .signals import appcontext_tearing_down, got_request_exception, \ + request_finished, request_started, request_tearing_down +from .templating import DispatchingJinjaLoader, Environment, \ + _default_template_ctx_processor +from .wrappers import Request, Response + +_sentinel = object() + + +def _make_timedelta(value): + if not isinstance(value, timedelta): + return timedelta(seconds=value) + return value + + +def setupmethod(f): + """Wraps a method so that it performs a check in debug mode if the + first request was already handled. + """ + def wrapper_func(self, *args, **kwargs): + if self.debug and self._got_first_request: + raise AssertionError('A setup function was called after the ' + 'first request was handled. This usually indicates a bug ' + 'in the application where a module was not imported ' + 'and decorators or other functionality was called too late.\n' + 'To fix this make sure to import all your view modules, ' + 'database models and everything related at a central place ' + 'before the application starts serving requests.') + return f(self, *args, **kwargs) + return update_wrapper(wrapper_func, f) + + +class Flask(_PackageBoundObject): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 0.13 + The `host_matching` and `static_host` parameters were added. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: the folder with static files that should be served + at `static_url_path`. Defaults to the ``'static'`` + folder in the root path of the application. + :param host_matching: sets the app's ``url_map.host_matching`` to the given + value. Defaults to False. + :param static_host: the host to use when adding the static route. Defaults + to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: Flask by default will automatically calculate the path + to the root of the application. In certain situations + this cannot be achieved (for instance if the package + is a Python 3 namespace package) and needs to be + manually defined. + """ + + request_class = Request + + response_class = Response + + jinja_environment = Environment + + app_ctx_globals_class = _AppCtxGlobals + + config_class = Config + + debug = ConfigAttribute('DEBUG') + + testing = ConfigAttribute('TESTING') + + secret_key = ConfigAttribute('SECRET_KEY') + + session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME') + + permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME', + get_converter=_make_timedelta) + + send_file_max_age_default = ConfigAttribute('SEND_FILE_MAX_AGE_DEFAULT', + get_converter=_make_timedelta) + + use_x_sendfile = ConfigAttribute('USE_X_SENDFILE') + + json_encoder = json.JSONEncoder + + json_decoder = json.JSONDecoder + + jinja_options = ImmutableDict( + extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] + ) + + default_config = ImmutableDict({ + 'DEBUG': get_debug_flag(default=False), + 'TESTING': False, + 'PROPAGATE_EXCEPTIONS': None, + 'PRESERVE_CONTEXT_ON_EXCEPTION': None, + 'SECRET_KEY': None, + 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), + 'USE_X_SENDFILE': False, + 'SERVER_NAME': None, + 'APPLICATION_ROOT': '/', + 'SESSION_COOKIE_NAME': 'session', + 'SESSION_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_PATH': None, + 'SESSION_COOKIE_HTTPONLY': True, + 'SESSION_COOKIE_SECURE': False, + 'SESSION_REFRESH_EACH_REQUEST': True, + 'MAX_CONTENT_LENGTH': None, + 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), + 'TRAP_BAD_REQUEST_ERRORS': None, + 'TRAP_HTTP_EXCEPTIONS': False, + 'EXPLAIN_TEMPLATE_LOADING': False, + 'PREFERRED_URL_SCHEME': 'http', + 'JSON_AS_ASCII': True, + 'JSON_SORT_KEYS': True, + 'JSONIFY_PRETTYPRINT_REGULAR': False, + 'JSONIFY_MIMETYPE': 'application/json', + 'TEMPLATES_AUTO_RELOAD': None, + }) + + url_rule_class = Rule + + test_client_class = None + + session_interface = SecureCookieSessionInterface() + + + import_name = None + + template_folder = None + + root_path = None + + 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.' + ) + + self.instance_path = instance_path + + self.config = self.make_config(instance_relative_config) + + self.view_functions = {} + + self.error_handler_spec = {} + + self.url_build_error_handlers = [] + + self.before_request_funcs = {} + + self.before_first_request_funcs = [] + + self.after_request_funcs = {} + + self.teardown_request_funcs = {} + + self.teardown_appcontext_funcs = [] + + self.url_value_preprocessors = {} + + self.url_default_functions = {} + + self.template_context_processors = { + None: [_default_template_ctx_processor] + } + + self.shell_context_processors = [] + + self.blueprints = {} + self._blueprint_order = [] + + self.extensions = {} + + self.url_map = Map() + + self.url_map.host_matching = host_matching + + self._got_first_request = False + self._before_request_lock = Lock() + + 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 + '/', + endpoint='static', + host=static_host, + view_func=self.send_static_file + ) + + self.cli = cli.AppGroup(self.name) + + @locked_cached_property + def name(self): + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == '__main__': + fn = getattr(sys.modules['__main__'], '__file__', None) + if fn is None: + return '__main__' + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + + @property + def propagate_exceptions(self): + """Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration + value in case it's set, otherwise a sensible default is returned. + + .. versionadded:: 0.7 + """ + rv = self.config['PROPAGATE_EXCEPTIONS'] + if rv is not None: + return rv + return self.testing or self.debug + + @property + def preserve_context_on_exception(self): + """Returns the value of the ``PRESERVE_CONTEXT_ON_EXCEPTION`` + configuration value in case it's set, otherwise a sensible default + is returned. + + .. versionadded:: 0.7 + """ + rv = self.config['PRESERVE_CONTEXT_ON_EXCEPTION'] + if rv is not None: + return rv + return self.debug + + @locked_cached_property + def logger(self): + """The ``'flask.app'`` logger, a standard Python + :class:`~logging.Logger`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will be set + to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be added. + See :ref:`logging` for more information. + + .. versionchanged:: 1.0 + Behavior was simplified. The logger is always named + ``flask.app``. The level is only set during configuration, it + doesn't check ``app.debug`` each time. Only one format is used, + not different ones depending on ``app.debug``. No handlers are + removed, and a handler is only added if no handlers are already + configured. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @locked_cached_property + def jinja_env(self): + """The Jinja2 environment used to load templates.""" + return self.create_jinja_environment() + + @property + def got_first_request(self): + """This attribute is set to ``True`` if the application started + handling the first request. + + .. versionadded:: 0.8 + """ + return self._got_first_request + + def make_config(self, instance_relative=False): + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + return self.config_class(root_path, self.default_config) + + def auto_find_instance_path(self): + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + prefix, package_path = find_package(self.import_name) + if prefix is None: + return os.path.join(package_path, 'instance') + return os.path.join(prefix, 'var', self.name + '-instance') + + def open_instance_resource(self, resource, mode='rb'): + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. + """ + return open(os.path.join(self.instance_path, resource), mode) + + def _get_templates_auto_reload(self): + """Reload templates when they are changed. Used by + :meth:`create_jinja_environment`. + + This attribute can be configured with :data:`TEMPLATES_AUTO_RELOAD`. If + not set, it will be enabled in debug mode. + + .. versionadded:: 1.0 + This property was added but the underlying config and behavior + already existed. + """ + rv = self.config['TEMPLATES_AUTO_RELOAD'] + return rv if rv is not None else self.debug + + def _set_templates_auto_reload(self, value): + self.config['TEMPLATES_AUTO_RELOAD'] = value + + templates_auto_reload = property( + _get_templates_auto_reload, _set_templates_auto_reload + ) + del _get_templates_auto_reload, _set_templates_auto_reload + + def create_jinja_environment(self): + """Creates the Jinja2 environment based on :attr:`jinja_options` + and :meth:`select_jinja_autoescape`. Since 0.7 this also adds + the Jinja2 globals and filters after initialization. Override + this function to customize the behavior. + + .. versionadded:: 0.5 + .. versionchanged:: 0.11 + ``Environment.auto_reload`` set in accordance with + ``TEMPLATES_AUTO_RELOAD`` configuration option. + """ + options = dict(self.jinja_options) + + if 'autoescape' not in options: + options['autoescape'] = self.select_jinja_autoescape + + if 'auto_reload' not in options: + options['auto_reload'] = self.templates_auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=url_for, + get_flashed_messages=get_flashed_messages, + config=self.config, + request=request, + session=session, + g=g + ) + rv.filters['tojson'] = json.tojson_filter + return rv + + def create_global_jinja_loader(self): + """Creates the loader for the Jinja2 environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + 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`. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + + def update_template_context(self, context): + """Update the template context with some commonly used variables. + This injects request, session, config and g into the template + context as well as everything template context processors want + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overridden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + funcs = self.template_context_processors[None] + reqctx = _request_ctx_stack.top + if reqctx is not None: + bp = reqctx.request.blueprint + if bp is not None and bp in self.template_context_processors: + funcs = chain(funcs, self.template_context_processors[bp]) + orig_ctx = context.copy() + for func in funcs: + context.update(func()) + context.update(orig_ctx) + + def make_shell_context(self): + """Returns the shell context for an interactive shell for this + application. This runs all the registered shell context + processors. + + .. versionadded:: 0.11 + """ + rv = {'app': self, 'g': g} + for processor in self.shell_context_processors: + rv.update(processor()) + return rv + + def _reconfigure_for_run_debug(self, debug): + """The ``run`` commands will set the application's debug flag. Some + application configuration may already be calculated based on the + previous debug value. This method will recalculate affected values. + + Called by the :func:`flask.cli.run` command or :meth:`Flask.run` + method if the debug flag is set explicitly in the call. + + :param debug: the new value of the debug flag + + .. versionadded:: 1.0 + Reconfigures ``app.jinja_env.auto_reload``. + """ + self.debug = debug + self.jinja_env.auto_reload = self.templates_auto_reload + + def run( + self, host=None, port=None, debug=None, load_dotenv=True, **options + ): + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :ref:`deployment` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + It is not recommended to use this function for development with + automatic reloading as this is badly supported. Instead you should + be using the :command:`flask` command line script's ``run`` support. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to ``True`` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable + if present. + :param port: the port of the webserver. Defaults to ``5000`` or the + port defined in the ``SERVER_NAME`` config variable if present. + :param debug: if given, enable or disable debug mode. See + :attr:`debug`. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param options: the options to be forwarded to the underlying Werkzeug + server. See :func:`werkzeug.serving.run_simple` for more + information. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment + variables from :file:`.env` and :file:`.flaskenv` files. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` variable. + + """ + if os.environ.get('FLASK_RUN_FROM_CLI') == 'true': + from .debughelpers import explain_ignored_app_run + explain_ignored_app_run() + return + + if load_dotenv: + from flask.cli import load_dotenv + load_dotenv() + + if debug is not None: + self._reconfigure_for_run_debug(bool(debug)) + + _host = '127.0.0.1' + _port = 5000 + server_name = self.config.get("SERVER_NAME") + sn_host, sn_port = None, None + + if server_name: + sn_host, _, sn_port = server_name.partition(':') + + host = host or sn_host or _host + port = int(port or sn_port or _port) + options.setdefault('use_reloader', self.debug) + options.setdefault('use_debugger', self.debug) + + from werkzeug.serving import run_simple + + try: + run_simple(host, port, self, **options) + finally: + self._got_first_request = False + + def test_client(self, use_cookies=True, **kwargs): + """Creates a test client for this application. For information + about unit testing head over to :ref:`testing`. + + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + + The test client can be used in a ``with`` block to defer the closing down + of the context until the end of the ``with`` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + Additionally, you may pass optional keyword arguments that will then + be passed to the application's :attr:`test_client_class` constructor. + For example:: + + from flask.testing import FlaskClient + + class CustomClient(FlaskClient): + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) + + app.test_client_class = CustomClient + client = app.test_client(authentication='Basic ....') + + See :class:`~flask.testing.FlaskClient` for more information. + + .. versionchanged:: 0.4 + added support for ``with`` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + + .. versionchanged:: 0.11 + Added `**kwargs` to support passing additional keyword arguments to + the constructor of :attr:`test_client_class`. + """ + cls = self.test_client_class + if cls is None: + from flask.testing import FlaskClient as cls + return cls(self, self.response_class, use_cookies=use_cookies, **kwargs) + + def open_session(self, request): + """Creates or opens a new session. Default implementation stores all + session data in a signed cookie. This requires that the + :attr:`secret_key` is set. Instead of overriding this method + we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 1.1. Use ``session_interface.open_session`` + instead. + + :param request: an instance of :attr:`request_class`. + """ + + warnings.warn(DeprecationWarning( + '"open_session" is deprecated and will be removed in 1.1. Use' + ' "session_interface.open_session" instead.' + )) + return self.session_interface.open_session(self, request) + + def save_session(self, session, response): + """Saves the session if it needs updates. For the default + implementation, check :meth:`open_session`. Instead of overriding this + method we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 1.1. Use ``session_interface.save_session`` + instead. + + :param session: the session to be saved (a + :class:`~werkzeug.contrib.securecookie.SecureCookie` + object) + :param response: an instance of :attr:`response_class` + """ + + warnings.warn(DeprecationWarning( + '"save_session" is deprecated and will be removed in 1.1. Use' + ' "session_interface.save_session" instead.' + )) + return self.session_interface.save_session(self, session, response) + + def make_null_session(self): + """Creates a new instance of a missing session. Instead of overriding + this method we recommend replacing the :class:`session_interface`. + + .. deprecated: 1.0 + Will be removed in 1.1. Use ``session_interface.make_null_session`` + instead. + + .. versionadded:: 0.7 + """ + + warnings.warn(DeprecationWarning( + '"make_null_session" is deprecated and will be removed in 1.1. Use' + ' "session_interface.make_null_session" instead.' + )) + return self.session_interface.make_null_session(self) + + @setupmethod + def register_blueprint(self, blueprint, **options): + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionadded:: 0.7 + """ + first_registration = False + + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, ( + 'A name collision occurred between blueprints %r and %r. Both' + ' share the same name "%s". Blueprints that are created on the' + ' fly need unique names.' % ( + blueprint, self.blueprints[blueprint.name], blueprint.name + ) + ) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + first_registration = True + + blueprint.register(self, options, first_registration) + + def iter_blueprints(self): + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return iter(self._blueprint_order) + + @setupmethod + def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): + """Connects a URL rule. Works exactly like the :meth:`route` + decorator. If a view_func is provided it will be registered with the + endpoint. + + Basically this example:: + + @app.route('/') + def index(): + pass + + Is equivalent to the following:: + + def index(): + pass + app.add_url_rule('/', 'index', index) + + If the view_func is not provided you will need to connect the endpoint + to a view function like so:: + + app.view_functions['index'] = index + + Internally :meth:`route` invokes :meth:`add_url_rule` so if you want + to customize the behavior via subclassing you only need to change + this method. + + For more information refer to :ref:`url-route-registrations`. + + .. versionchanged:: 0.2 + `view_func` parameter added. + + .. versionchanged:: 0.6 + ``OPTIONS`` is added automatically as method. + + :param rule: the URL rule as string + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param view_func: the function to call when serving a request to the + provided endpoint + :param provide_automatic_options: controls whether the ``OPTIONS`` + method should be added automatically. This can also be controlled + by setting the ``view_func.provide_automatic_options = False`` + before adding the rule. + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited + to (``GET``, ``POST`` etc.). By default a rule + just listens for ``GET`` (and implicitly ``HEAD``). + Starting with Flask 0.6, ``OPTIONS`` is implicitly + added and handled by the standard request handling. + """ + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) + options['endpoint'] = endpoint + methods = options.pop('methods', None) + + if methods is None: + methods = getattr(view_func, 'methods', None) or ('GET',) + if isinstance(methods, string_types): + raise TypeError('Allowed methods have to be iterables of strings, ' + 'for example: @app.route(..., methods=["POST"])') + methods = set(item.upper() for item in methods) + + required_methods = set(getattr(view_func, 'required_methods', ())) + + if provide_automatic_options is None: + provide_automatic_options = getattr(view_func, + 'provide_automatic_options', None) + + if provide_automatic_options is None: + if 'OPTIONS' not in methods: + provide_automatic_options = True + required_methods.add('OPTIONS') + else: + provide_automatic_options = False + + methods |= required_methods + + rule = self.url_rule_class(rule, methods=methods, **options) + rule.provide_automatic_options = provide_automatic_options + + self.url_map.add(rule) + if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func != view_func: + raise AssertionError('View function mapping is overwriting an ' + 'existing endpoint function: %s' % endpoint) + self.view_functions[endpoint] = view_func + + def route(self, rule, **options): + """A decorator that is used to register a view function for a + given URL rule. This does the same thing as :meth:`add_url_rule` + but is intended for decorator usage:: + + @app.route('/') + def index(): + return 'Hello World' + + For more information refer to :ref:`url-route-registrations`. + + :param rule: the URL rule as string + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited + to (``GET``, ``POST`` etc.). By default a rule + just listens for ``GET`` (and implicitly ``HEAD``). + Starting with Flask 0.6, ``OPTIONS`` is implicitly + added and handled by the standard request handling. + """ + def decorator(f): + endpoint = options.pop('endpoint', None) + self.add_url_rule(rule, endpoint, f, **options) + return f + return decorator + + @setupmethod + def endpoint(self, endpoint): + """A decorator to register a function as an endpoint. + Example:: + + @app.endpoint('example.endpoint') + def example(): + return "example" + + :param endpoint: the name of the endpoint + """ + def decorator(f): + self.view_functions[endpoint] = f + return f + return decorator + + @staticmethod + def _get_exc_class_and_code(exc_class_or_code): + """Ensure that we register only exceptions as handler keys""" + if isinstance(exc_class_or_code, integer_types): + exc_class = default_exceptions[exc_class_or_code] + else: + exc_class = exc_class_or_code + + assert issubclass(exc_class, Exception) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + @setupmethod + def errorhandler(self, code_or_exception): + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + def decorator(f): + self._register_error_handler(None, code_or_exception, f) + return f + return decorator + + @setupmethod + def register_error_handler(self, code_or_exception, f): + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + self._register_error_handler(None, code_or_exception, f) + + @setupmethod + def _register_error_handler(self, key, code_or_exception, f): + """ + :type key: None|str + :type code_or_exception: int|T<=Exception + :type f: callable + """ + raise ValueError( + 'Tried to register a handler for an exception instance {0!r}.' + ' Handlers can only be registered for exception classes or' + ' HTTP error codes.'.format(code_or_exception) + ) + + try: + exc_class, code = self._get_exc_class_and_code(code_or_exception) + except KeyError: + raise KeyError( + "'{0}' is not a recognized HTTP error code. Use a subclass of" + " HTTPException with that code instead.".format(code_or_exception) + ) + + handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) + handlers[exc_class] = f + + @setupmethod + def template_filter(self, name=None): + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + def decorator(f): + self.add_template_filter(f, name=name) + return f + return decorator + + @setupmethod + def add_template_filter(self, f, name=None): + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + + @setupmethod + def template_test(self, name=None): + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + def decorator(f): + self.add_template_test(f, name=name) + return f + return decorator + + @setupmethod + def add_template_test(self, f, name=None): + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod + def template_global(self, name=None): + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + def decorator(f): + self.add_template_global(f, name=name) + return f + return decorator + + @setupmethod + def add_template_global(self, f, name=None): + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + + @setupmethod + def before_request(self, f): + """Registers a function to run before each request. + + For example, this can be used to open a database connection, or to load + the logged in user from the session. + + The function will be called without any arguments. If it returns a + non-None value, the value is handled as if it was the return value from + the view, and further request handling is stopped. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def before_first_request(self, f): + """Registers a function to be run before the first request to this + instance of the application. + + The function will be called without any arguments and its return + value is ignored. + + .. versionadded:: 0.8 + """ + self.before_first_request_funcs.append(f) + return f + + @setupmethod + def after_request(self, f): + """Register a function to be run after each request. + + Your function must take one parameter, an instance of + :attr:`response_class` and return a new response object or the + same (see :meth:`process_response`). + + As of Flask 0.7 this function might not be executed at the end of the + request in case an unhandled exception occurred. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f): + """Register a function to be run at the end of each request, + regardless of whether there was an exception or not. These functions + are executed when the request context is popped, even if not an + actual request was performed. + + Example:: + + ctx = app.test_request_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the request context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + Generally teardown functions must take every necessary step to avoid + that they will fail. If they do execute code that might fail they + will have to surround the execution of these code by try/except + statements and log occurring errors. + + When a teardown function was called because of an exception it will + be passed an error object. + + The return values of teardown functions are ignored. + + .. admonition:: Debug Note + + In debug mode Flask will not tear down a request on an exception + immediately. Instead it will keep it alive so that the interactive + debugger can still access it. This behavior can be controlled + by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_appcontext(self, f): + """Registers a function to be called when the application context + ends. These functions are typically also called when the request + context is popped. + + Example:: + + ctx = app.app_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the app context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + Since a request context typically also manages an application + context it would also be called when you pop a request context. + + When a teardown function was called because of an unhandled exception + it will be passed an error object. If an :meth:`errorhandler` is + registered, it will handle the exception and the teardown will not + receive it. + + The return values of teardown functions are ignored. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + + @setupmethod + def context_processor(self, f): + """Registers a template context processor function.""" + self.template_context_processors[None].append(f) + return f + + @setupmethod + def shell_context_processor(self, f): + """Registers a shell context processor function. + + .. versionadded:: 0.11 + """ + self.shell_context_processors.append(f) + return f + + @setupmethod + def url_value_preprocessor(self, f): + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + """ + self.url_value_preprocessors.setdefault(None, []).append(f) + return f + + @setupmethod + def url_defaults(self, f): + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + """ + self.url_default_functions.setdefault(None, []).append(f) + return f + + def _find_error_handler(self, e): + """Return a registered error handler for an exception in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. + """ + exc_class, code = self._get_exc_class_and_code(type(e)) + + for name, c in ( + (request.blueprint, code), (None, code), + (request.blueprint, None), (None, None) + ): + handler_map = self.error_handler_spec.setdefault(name, {}).get(c) + + if not handler_map: + continue + + for cls in exc_class.__mro__: + handler = handler_map.get(cls) + + if handler is not None: + return handler + + def handle_http_exception(self, e): + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionadded:: 0.3 + """ + if e.code is None: + return e + + handler = self._find_error_handler(e) + if handler is None: + return e + return handler(e) + + def trap_http_exception(self, e): + """Checks if an HTTP exception should be trapped or not. By default + this will return ``False`` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It + also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``. + + This is called for all HTTP exceptions raised by a view function. + If it returns ``True`` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + + .. versionchanged:: 1.0 + Bad request errors are not trapped by default in debug mode. + + .. versionadded:: 0.8 + """ + if self.config['TRAP_HTTP_EXCEPTIONS']: + return True + + trap_bad_request = self.config['TRAP_BAD_REQUEST_ERRORS'] + + if (trap_bad_request is None and self.debug) or trap_bad_request: + return isinstance(e, BadRequest) + + return False + + def handle_user_exception(self, e): + """This method is called whenever an exception occurs that should be + handled. A special case are + :class:`~werkzeug.exception.HTTPException`\s which are forwarded by + this function to the :meth:`handle_http_exception` method. This + function will either return a response value or reraise the + exception with the same traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the the bad + key in debug mode rather than a generic bad request message. + + .. versionadded:: 0.7 + """ + exc_type, exc_value, tb = sys.exc_info() + assert exc_value is e + + if ( + (self.debug or self.config['TRAP_BAD_REQUEST_ERRORS']) + and isinstance(e, BadRequestKeyError) + and e.description is BadRequestKeyError.description + ): + e.description = "KeyError: '{0}'".format(*e.args) + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e) + + if handler is None: + reraise(exc_type, exc_value, tb) + return handler(e) + + def handle_exception(self, e): + """Default exception handling that kicks in when an exception + occurs that is not caught. In debug mode the exception will + be re-raised immediately, otherwise it is logged and the handler + for a 500 internal server error is used. If no such handler + exists, a default 500 internal server error message is displayed. + + .. versionadded:: 0.3 + """ + exc_type, exc_value, tb = sys.exc_info() + + got_request_exception.send(self, exception=e) + handler = self._find_error_handler(InternalServerError()) + + if self.propagate_exceptions: + if exc_value is e: + reraise(exc_type, exc_value, tb) + else: + raise e + + self.log_exception((exc_type, exc_value, tb)) + if handler is None: + return InternalServerError() + return self.finalize_request(handler(e), from_error_handler=True) + + def log_exception(self, exc_info): + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error('Exception on %s [%s]' % ( + request.path, + request.method + ), exc_info=exc_info) + + def raise_routing_exception(self, request): + """Exceptions that are recording during routing are reraised with + this method. During debug we are not reraising redirect requests + for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising + a different error instead to help debug situations. + + :internal: + """ + if not self.debug \ + or not isinstance(request.routing_exception, RequestRedirect) \ + or request.method in ('GET', 'HEAD', 'OPTIONS'): + raise request.routing_exception + + from .debughelpers import FormDataRoutingRedirect + raise FormDataRoutingRedirect(request) + + def dispatch_request(self): + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = _request_ctx_stack.top.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule = req.url_rule + if getattr(rule, 'provide_automatic_options', False) \ + and req.method == 'OPTIONS': + return self.make_default_options_response() + return self.view_functions[rule.endpoint](**req.view_args) + + def full_dispatch_request(self): + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + self.try_trigger_before_first_request_functions() + try: + request_started.send(self) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request(self, rv, from_error_handler=False): + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send(self, response=response) + except Exception: + if not from_error_handler: + raise + self.logger.exception('Request finalizing failed with an ' + 'error while handling an error') + return response + + def try_trigger_before_first_request_functions(self): + """Called before each request and will ensure that it triggers + the :attr:`before_first_request_funcs` and only exactly once per + application instance (which means process usually). + + :internal: + """ + if self._got_first_request: + return + with self._before_request_lock: + if self._got_first_request: + return + for func in self.before_first_request_funcs: + func() + self._got_first_request = True + + def make_default_options_response(self): + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = _request_ctx_stack.top.url_adapter + if hasattr(adapter, 'allowed_methods'): + methods = adapter.allowed_methods() + else: + methods = [] + try: + adapter.match(method='--') + except MethodNotAllowed as e: + methods = e.valid_methods + except HTTPException as e: + pass + rv = self.response_class() + rv.allow.update(methods) + return rv + + def should_ignore_error(self, error): + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns ``True`` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + + def make_response(self, rv): + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` (``unicode`` in Python 2) + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` (``str`` in Python 2) + A response object is created with the bytes as the body. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status = headers = None + + if isinstance(rv, (tuple, list)): + len_rv = len(rv) + + if len_rv == 3: + rv, status, headers = rv + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv + else: + raise TypeError( + 'The view function did not return a valid response tuple.' + ' The tuple must have the form (body, status, headers),' + ' (body, status), or (body, headers).' + ) + + if rv is None: + raise TypeError( + 'The view function did not return a valid response. The' + ' function either returned None or ended without a return' + ' statement.' + ) + + if not isinstance(rv, self.response_class): + if isinstance(rv, (text_type, bytes, bytearray)): + rv = self.response_class(rv, status=status, headers=headers) + status = headers = None + else: + try: + rv = self.response_class.force_type(rv, request.environ) + except TypeError as e: + new_error = TypeError( + '{e}\nThe view function did not return a valid' + ' response. The return type must be a string, tuple,' + ' Response instance, or WSGI callable, but it was a' + ' {rv.__class__.__name__}.'.format(e=e, rv=rv) + ) + reraise(TypeError, new_error, sys.exc_info()[2]) + + if status is not None: + if isinstance(status, (text_type, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + if headers: + rv.headers.extend(headers) + + return rv + + def create_url_adapter(self, request): + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set up + so the request is passed explicitly. + + .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + URL adapter is created for the application context. + """ + if request is not None: + return self.url_map.bind_to_environ(request.environ, + server_name=self.config['SERVER_NAME']) + if self.config['SERVER_NAME'] is not None: + return self.url_map.bind( + self.config['SERVER_NAME'], + script_name=self.config['APPLICATION_ROOT'], + url_scheme=self.config['PREFERRED_URL_SCHEME']) + + def inject_url_defaults(self, endpoint, values): + """Injects the URL defaults for the given endpoint directly into + the values dictionary passed. This is used internally and + automatically called on URL building. + + .. versionadded:: 0.7 + """ + funcs = self.url_default_functions.get(None, ()) + if '.' in endpoint: + bp = endpoint.rsplit('.', 1)[0] + funcs = chain(funcs, self.url_default_functions.get(bp, ())) + for func in funcs: + func(endpoint, values) + + def handle_url_build_error(self, error, endpoint, values): + """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. + """ + exc_type, exc_value, tb = sys.exc_info() + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + if rv is not None: + return rv + except BuildError as e: + error = e + + if error is exc_value: + reraise(exc_type, exc_value, tb) + raise error + + def preprocess_request(self): + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. + """ + + bp = _request_ctx_stack.top.request.blueprint + + funcs = self.url_value_preprocessors.get(None, ()) + if bp is not None and bp in self.url_value_preprocessors: + funcs = chain(funcs, self.url_value_preprocessors[bp]) + for func in funcs: + func(request.endpoint, request.view_args) + + funcs = self.before_request_funcs.get(None, ()) + if bp is not None and bp in self.before_request_funcs: + funcs = chain(funcs, self.before_request_funcs[bp]) + for func in funcs: + rv = func() + if rv is not None: + return rv + + def process_response(self, response): + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = _request_ctx_stack.top + bp = ctx.request.blueprint + funcs = ctx._after_request_functions + if bp is not None and bp in self.after_request_funcs: + funcs = chain(funcs, reversed(self.after_request_funcs[bp])) + if None in self.after_request_funcs: + funcs = chain(funcs, reversed(self.after_request_funcs[None])) + for handler in funcs: + response = handler(response) + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + return response + + def do_teardown_request(self, exc=_sentinel): + """Called after the actual request dispatching and will + call every as :meth:`teardown_request` decorated function. This is + not actually called by the :class:`Flask` object itself but is always + triggered when the request context is popped. That way we have a + tighter control over certain resources under testing environments. + + .. versionchanged:: 0.9 + Added the `exc` argument. Previously this was always using the + current exception information. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + funcs = reversed(self.teardown_request_funcs.get(None, ())) + bp = _request_ctx_stack.top.request.blueprint + if bp is not None and bp in self.teardown_request_funcs: + funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) + for func in funcs: + func(exc) + request_tearing_down.send(self, exc=exc) + + def do_teardown_appcontext(self, exc=_sentinel): + """Called when an application context is popped. This works pretty + much the same as :meth:`do_teardown_request` but for the application + context. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + for func in reversed(self.teardown_appcontext_funcs): + func(exc) + appcontext_tearing_down.send(self, exc=exc) + + def app_context(self): + """Binds the application only. For as long as the application is bound + to the current context the :data:`flask.current_app` points to that + application. An application context is automatically created when a + request context is pushed if necessary. + + Example usage:: + + with app.app_context(): + ... + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ): + """Creates a :class:`~flask.ctx.RequestContext` from the given + environment and binds it to the current context. This must be used in + combination with the ``with`` statement because the request is only bound + to the current context for the duration of the ``with`` block. + + Example usage:: + + with app.request_context(environ): + do_something_with(request) + + The object returned can also be used without the ``with`` statement + which is useful for working in the shell. The example above is + doing exactly the same as this code:: + + ctx = app.request_context(environ) + ctx.push() + try: + do_something_with(request) + finally: + ctx.pop() + + .. versionchanged:: 0.3 + Added support for non-with statement usage and ``with`` statement + is now passed the ctx object. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args, **kwargs): + """Creates a WSGI environment from the given values (see + :class:`werkzeug.test.EnvironBuilder` for more information, this + function accepts the same arguments plus two additional). + + Additional arguments (only if ``base_url`` is not specified): + + :param subdomain: subdomain to use for route matching + :param url_scheme: scheme for the request, default + ``PREFERRED_URL_SCHEME`` or ``http``. + """ + + from flask.testing import make_test_environ_builder + + builder = make_test_environ_builder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + def wsgi_app(self, environ, start_response): + """The actual WSGI application. This is not implemented in + `__call__` so that middlewares can be applied without losing a + reference to the class. So instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + The behavior of the before and after request callbacks was changed + under error conditions and a new callback was added that will + always execute at the end of the request, independent on if an + error occurred or not. See :ref:`callbacks-and-errors`. + + :param environ: a WSGI environment + :param start_response: a callable accepting a status code, + a list of headers and an optional + exception context to start the response + """ + ctx = self.request_context(environ) + error = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if self.should_ignore_error(error): + error = None + ctx.auto_pop(error) + + def __call__(self, environ, start_response): + """Shortcut for :attr:`wsgi_app`.""" + return self.wsgi_app(environ, start_response) + + def __repr__(self): + return '<%s %r>' % ( + self.__class__.__name__, + self.name, + ) + +""" + Flask Extension Tests + ~~~~~~~~~~~~~~~~~~~~~ + + Tests the Flask extensions. + + :copyright: (c) 2015 by Ali Afshar. + :license: BSD, see LICENSE for more details. +""" + +import os +import sys +import shutil +import urllib2 +import tempfile +import subprocess +import argparse + +from flask import json + +from setuptools.package_index import PackageIndex +from setuptools.archive_util import unpack_archive + +flask_svc_url = 'http://flask.pocoo.org/extensions/' + + +if sys.platform == 'darwin': + _tempdir = '/private/tmp' +else: + _tempdir = tempfile.gettempdir() +tdir = _tempdir + '/flaskext-test' +flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + + +os.environ['PYTHONDONTWRITEBYTECODE'] = '' + + +RESULT_TEMPATE = u'''\ + +Flask-Extension Test Results + +

Flask-Extension Test Results

+

+ This page contains the detailed test results for the test run of + all {{ 'approved' if approved }} Flask extensions. +

Summary

+ + + + + + + {%- for result in results %} + {% set outcome = 'success' if result.success else 'failed' %} + + + {%- endfor %} + +
Extension + Version + Author + License + Outcome + {%- for iptr, _ in results[0].logs|dictsort %} + {{ iptr }} + {%- endfor %} +
{{ result.name }} + {{ result.version }} + {{ result.author }} + {{ result.license }} + {{ outcome }} + {%- for iptr, _ in result.logs|dictsort %} + {%- endfor %} +
+

Test Logs

+

Detailed test logs for all tests on all platforms: +{%- for result in results %} + {%- for iptr, log in result.logs|dictsort %} +

+ {{ result.name }} - {{ result.version }} [{{ iptr }}]

+
{{ log }}
+ {%- endfor %} +{%- endfor %} +''' + + +def log(msg, *args): + print('[EXTTEST] ' + (msg % args)) + + +class TestResult(object): + + def __init__(self, name, folder, statuscode, interpreters): + intrptr = os.path.join(folder, '.tox/%s/bin/python' + % interpreters[0]) + self.statuscode = statuscode + self.folder = folder + self.success = statuscode == 0 + + def fetch(field): + try: + c = subprocess.Popen([intrptr, 'setup.py', + '--' + field], cwd=folder, + stdout=subprocess.PIPE) + return c.communicate()[0].strip() + except OSError: + return '?' + self.name = name + self.license = fetch('license') + self.author = fetch('author') + self.version = fetch('version') + + self.logs = {} + for interpreter in interpreters: + logfile = os.path.join(folder, '.tox/%s/log/test.log' + % interpreter) + if os.path.isfile(logfile): + self.logs[interpreter] = open(logfile).read() + else: + self.logs[interpreter] = '' + + +def create_tdir(): + try: + shutil.rmtree(tdir) + except Exception: + pass + os.mkdir(tdir) + + +def package_flask(): + distfolder = tdir + '/.flask-dist' + c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar', + '--dist', distfolder], cwd=flaskdir) + c.wait() + return os.path.join(distfolder, os.listdir(distfolder)[0]) + + +def get_test_command(checkout_dir): + if os.path.isfile(checkout_dir + '/Makefile'): + return 'make test' + return 'python setup.py test' + + +def fetch_extensions_list(): + req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'}) + d = urllib2.urlopen(req).read() + data = json.loads(d) + for ext in data['extensions']: + yield ext + + +def checkout_extension(name): + log('Downloading extension %s to temporary folder', name) + root = os.path.join(tdir, name) + os.mkdir(root) + checkout_path = PackageIndex().download(name, root) + + unpack_archive(checkout_path, root) + path = None + for fn in os.listdir(root): + path = os.path.join(root, fn) + if os.path.isdir(path): + break + log('Downloaded to %s', path) + return path + + +tox_template = """[tox] +envlist=%(env)s + +[testenv] +deps= + %(deps)s + distribute + py +commands=bash flaskext-runtest.sh {envlogdir}/test.log +downloadcache=%(cache)s +""" + + +def create_tox_ini(checkout_path, interpreters, flask_dep): + tox_path = os.path.join(checkout_path, 'tox-flask-test.ini') + if not os.path.exists(tox_path): + with open(tox_path, 'w') as f: + f.write(tox_template % { + 'env': ','.join(interpreters), + 'cache': tdir, + 'deps': flask_dep + }) + return tox_path + + +def iter_extensions(only_approved=True): + for ext in fetch_extensions_list(): + if ext['approved'] or not only_approved: + yield ext['name'] + + +def test_extension(name, interpreters, flask_dep): + checkout_path = checkout_extension(name) + log('Running tests with tox in %s', checkout_path) + + test_command = get_test_command(checkout_path) + log('Test command: %s', test_command) + f = open(checkout_path + '/flaskext-runtest.sh', 'w') + f.write(test_command + ' &> "$1" < /dev/null\n') + f.close() + + + create_tox_ini(checkout_path, interpreters, flask_dep) + rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path) + return TestResult(name, checkout_path, rv, interpreters) + + +def run_tests(extensions, interpreters): + results = {} + create_tdir() + log('Packaging Flask') + flask_dep = package_flask() + log('Running extension tests') + log('Temporary Environment: %s', tdir) + for name in extensions: + log('Testing %s', name) + result = test_extension(name, interpreters, flask_dep) + if result.success: + log('Extension test succeeded') + else: + log('Extension test failed') + results[name] = result + return results + + +def render_results(results, approved): + from jinja2 import Template + items = results.values() + items.sort(key=lambda x: x.name.lower()) + rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items, + approved=approved) + fd, filename = tempfile.mkstemp(suffix='.html') + os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n') + return filename + + +def main(): + parser = argparse.ArgumentParser(description='Runs Flask extension tests') + parser.add_argument('--all', dest='all', action='store_true', + help='run against all extensions, not just approved') + parser.add_argument('--browse', dest='browse', action='store_true', + help='show browser with the result summary') + parser.add_argument('--env', dest='env', default='py25,py26,py27', + help='the tox environments to run against') + parser.add_argument('--extension=', dest='extension', default=None, + help='tests a single extension') + args = parser.parse_args() + + if args.extension is not None: + only_approved = False + extensions = [args.extension] + else: + only_approved = not args.all + extensions = iter_extensions(only_approved) + + results = run_tests(extensions, [x.strip() for x in args.env.split(',')]) + filename = render_results(results, only_approved) + if args.browse: + import webbrowser + webbrowser.open('file:///' + filename.lstrip('/')) + print('Results written to {}'.format(filename)) + + +if __name__ == '__main__': + main() +""" + make-release + ~~~~~~~~~~~~ + + Helper script that performs a release. Does pretty much everything + automatically for us. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from __future__ import print_function +import sys +import os +import re +from datetime import datetime, date +from subprocess import Popen, PIPE + +_date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)') + + +def parse_changelog(): + with open('CHANGES') as f: + lineiter = iter(f) + for line in lineiter: + match = re.search('^Version\s+(.*)', line.strip()) + if match is None: + continue + version = match.group(1).strip() + if lineiter.next().count('-') != len(match.group(0)): + continue + while 1: + change_info = lineiter.next().strip() + if change_info: + break + + match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)' + r'(?:, codename (.*))?(?i)', change_info) + if match is None: + continue + + datestr, codename = match.groups() + return version, parse_date(datestr), codename + + +def bump_version(version): + try: + parts = map(int, version.split('.')) + except ValueError: + fail('Current version is not numeric') + parts[-1] += 1 + return '.'.join(map(str, parts)) + + +def parse_date(string): + string = _date_clean_re.sub(r'\1', string) + return datetime.strptime(string, '%B %d %Y') + + +def set_filename_version(filename, version_number, pattern): + changed = [] + + def inject_version(match): + before, old, after = match.groups() + changed.append(True) + return before + version_number + after + with open(filename) as f: + contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, + inject_version, f.read()) + + if not changed: + fail('Could not find %s in %s', pattern, filename) + + with open(filename, 'w') as f: + f.write(contents) + + +def set_init_version(version): + info('Setting __init__.py version to %s', version) + set_filename_version('flask/__init__.py', version, '__version__') + + +def build_and_upload(): + Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait() + + +def fail(message, *args): + print('Error:', message % args, file=sys.stderr) + sys.exit(1) + + +def info(message, *args): + print(message % args, file=sys.stderr) + + +def get_git_tags(): + return set(Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines()) + + +def git_is_clean(): + return Popen(['git', 'diff', '--quiet']).wait() == 0 + + +def make_git_commit(message, *args): + message = message % args + Popen(['git', 'commit', '-am', message]).wait() + + +def make_git_tag(tag): + info('Tagging "%s"', tag) + Popen(['git', 'tag', tag]).wait() + + +def main(): + os.chdir(os.path.join(os.path.dirname(__file__), '..')) + + rv = parse_changelog() + if rv is None: + fail('Could not parse changelog') + + version, release_date, codename = rv + dev_version = bump_version(version) + '-dev' + + info('Releasing %s (codename %s, release date %s)', + version, codename, release_date.strftime('%d/%m/%Y')) + tags = get_git_tags() + + if version in tags: + fail('Version "%s" is already tagged', version) + if release_date.date() != date.today(): + fail('Release date is not today (%s != %s)', + release_date.date(), date.today()) + + if not git_is_clean(): + fail('You have uncommitted changes in git') + + set_init_version(version) + make_git_commit('Bump version number to %s', version) + make_git_tag(version) + build_and_upload() + set_init_version(dev_version) + + +if __name__ == '__main__': + main() +""" + tests.conftest + ~~~~~~~~~~~~~~ + + :copyright: (c) 2015 by the Flask Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" +import flask +import gc +import os +import sys +import pkgutil +import pytest +import textwrap +from flask import Flask as _Flask + + +class Flask(_Flask): + testing = True + secret_key = 'test key' + + +@pytest.fixture +def app(): + app = Flask(__name__) + return app + + +@pytest.fixture +def app_ctx(app): + with app.app_context() as ctx: + yield ctx + + +@pytest.fixture +def req_ctx(app): + with app.test_request_context() as ctx: + yield ctx + + +@pytest.fixture +def client(app): + return app.test_client() + + +@pytest.fixture +def test_apps(monkeypatch): + monkeypatch.syspath_prepend( + os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps')) + ) + + +@pytest.fixture(autouse=True) +def leak_detector(): + yield + + leaks = [] + while flask._request_ctx_stack.top is not None: + leaks.append(flask._request_ctx_stack.pop()) + assert leaks == [] + + +@pytest.fixture(params=(True, False)) +def limit_loader(request, monkeypatch): + """Patch pkgutil.get_loader to give loader without get_filename or archive. + + This provides for tests where a system has custom loaders, e.g. Google App + Engine's HardenedModulesHook, which have neither the `get_filename` method + nor the `archive` attribute. + + This fixture will run the testcase twice, once with and once without the + limitation/mock. + """ + if not request.param: + return + + class LimitedLoader(object): + def __init__(self, loader): + self.loader = loader + + def __getattr__(self, name): + if name in ('archive', 'get_filename'): + msg = 'Mocking a loader which does not have `%s.`' % name + raise AttributeError(msg) + return getattr(self.loader, name) + + old_get_loader = pkgutil.get_loader + + def get_loader(*args, **kwargs): + return LimitedLoader(old_get_loader(*args, **kwargs)) + + monkeypatch.setattr(pkgutil, 'get_loader', get_loader) + + +@pytest.fixture +def modules_tmpdir(tmpdir, monkeypatch): + """A tmpdir added to sys.path.""" + rv = tmpdir.mkdir('modules_tmpdir') + monkeypatch.syspath_prepend(str(rv)) + return rv + + +@pytest.fixture +def modules_tmpdir_prefix(modules_tmpdir, monkeypatch): + monkeypatch.setattr(sys, 'prefix', str(modules_tmpdir)) + return modules_tmpdir + + +@pytest.fixture +def site_packages(modules_tmpdir, monkeypatch): + """Create a fake site-packages.""" + rv = modules_tmpdir \ + .mkdir('lib') \ + .mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info)) \ + .mkdir('site-packages') + monkeypatch.syspath_prepend(str(rv)) + return rv + + +@pytest.fixture +def install_egg(modules_tmpdir, monkeypatch): + """Generate egg from package name inside base and put the egg into + sys.path.""" + + def inner(name, base=modules_tmpdir): + if not isinstance(name, str): + raise ValueError(name) + base.join(name).ensure_dir() + base.join(name).join('__init__.py').ensure() + + egg_setup = base.join('setup.py') + egg_setup.write(textwrap.dedent(""" + from setuptools import setup + setup(name='{0}', + version='1.0', + packages=['site_egg'], + zip_safe=True) + """.format(name))) + + import subprocess + subprocess.check_call( + [sys.executable, 'setup.py', 'bdist_egg'], + cwd=str(modules_tmpdir) + ) + egg_path, = modules_tmpdir.join('dist/').listdir() + monkeypatch.syspath_prepend(str(egg_path)) + return egg_path + + return inner + + +@pytest.fixture +def purge_module(request): + def inner(name): + request.addfinalizer(lambda: sys.modules.pop(name, None)) + + return inner + + +@pytest.fixture(autouse=True) +def catch_deprecation_warnings(recwarn): + yield + gc.collect() + assert not recwarn.list, '\n'.join(str(w.message) for w in recwarn.list) +""" + tests.appctx + ~~~~~~~~~~~~ + + Tests the application context. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import pytest + +import flask + + +def test_basic_url_generation(app): + app.config['SERVER_NAME'] = 'localhost' + app.config['PREFERRED_URL_SCHEME'] = 'https' + + @app.route('/') + def index(): + pass + + with app.app_context(): + rv = flask.url_for('index') + assert rv == 'https://localhost/' + + +def test_url_generation_requires_server_name(app): + with app.app_context(): + with pytest.raises(RuntimeError): + flask.url_for('index') + + +def test_url_generation_without_context_fails(): + with pytest.raises(RuntimeError): + flask.url_for('index') + + +def test_request_context_means_app_context(app): + with app.test_request_context(): + assert flask.current_app._get_current_object() == app + assert flask._app_ctx_stack.top is None + + +def test_app_context_provides_current_app(app): + with app.app_context(): + assert flask.current_app._get_current_object() == app + assert flask._app_ctx_stack.top is None + + +def test_app_tearing_down(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + with app.app_context(): + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_previous_exception(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + try: + raise Exception('dummy') + except Exception: + pass + + with app.app_context(): + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_handled_exception_by_except_block(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + with app.app_context(): + try: + raise Exception('dummy') + except Exception: + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_handled_exception_by_app_handler(app, client): + app.config['PROPAGATE_EXCEPTIONS'] = True + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + @app.route('/') + def index(): + raise Exception('dummy') + + @app.errorhandler(Exception) + def handler(f): + return flask.jsonify(str(f)) + + with app.app_context(): + client.get('/') + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_unhandled_exception(app, client): + app.config['PROPAGATE_EXCEPTIONS'] = True + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + @app.route('/') + def index(): + raise Exception('dummy') + + with pytest.raises(Exception): + with app.app_context(): + client.get('/') + + assert len(cleanup_stuff) == 1 + assert isinstance(cleanup_stuff[0], Exception) + assert str(cleanup_stuff[0]) == 'dummy' + + +def test_app_ctx_globals_methods(app, app_ctx): + assert flask.g.get('foo') is None + assert flask.g.get('foo', 'bar') == 'bar' + assert 'foo' not in flask.g + flask.g.foo = 'bar' + assert 'foo' in flask.g + flask.g.setdefault('bar', 'the cake is a lie') + flask.g.setdefault('bar', 'hello world') + assert flask.g.bar == 'the cake is a lie' + assert flask.g.pop('bar') == 'the cake is a lie' + with pytest.raises(KeyError): + flask.g.pop('bar') + assert flask.g.pop('bar', 'more cake') == 'more cake' + assert list(flask.g) == ['foo'] + + +def test_custom_app_ctx_globals_class(app): + class CustomRequestGlobals(object): + def __init__(self): + self.spam = 'eggs' + + app.app_ctx_globals_class = CustomRequestGlobals + with app.app_context(): + assert flask.render_template_string('{{ g.spam }}') == 'eggs' + + +def test_context_refcounts(app, client): + called = [] + + @app.teardown_request + def teardown_req(error=None): + called.append('request') + + @app.teardown_appcontext + def teardown_app(error=None): + called.append('app') + + @app.route('/') + def index(): + with flask._app_ctx_stack.top: + with flask._request_ctx_stack.top: + pass + env = flask._request_ctx_stack.top.request.environ + assert env['werkzeug.request'] is not None + return u'' + + res = client.get('/') + assert res.status_code == 200 + assert res.data == b'' + assert called == ['request', 'app'] + + +def test_clean_pop(app): + app.testing = False + called = [] + + @app.teardown_request + def teardown_req(error=None): + 1 / 0 + + @app.teardown_appcontext + def teardown_app(error=None): + called.append('TEARDOWN') + + try: + with app.test_request_context(): + called.append(flask.current_app.name) + except ZeroDivisionError: + pass + + assert called == ['conftest', 'TEARDOWN'] + assert not flask.current_app + +""" + tests.basic + ~~~~~~~~~~~~~~~~~~~~~ + + The basic functionality. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import re +import time +import uuid +from datetime import datetime +from threading import Thread + +import pytest +import werkzeug.serving +from werkzeug.exceptions import BadRequest, Forbidden, NotFound +from werkzeug.http import parse_date +from werkzeug.routing import BuildError + +import flask +from flask._compat import text_type + + +def test_options_work(app, client): + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + + rv = client.open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + assert rv.data == b'' + + +def test_options_on_multiple_rules(app, client): + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + + @app.route('/', methods=['PUT']) + def index_put(): + return 'Aha!' + + rv = client.open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + +def test_provide_automatic_options_attr(): + app = flask.Flask(__name__) + + def index(): + return 'Hello World!' + + index.provide_automatic_options = False + app.route('/')(index) + rv = app.test_client().open('/', method='OPTIONS') + assert rv.status_code == 405 + + app = flask.Flask(__name__) + + def index2(): + return 'Hello World!' + + index2.provide_automatic_options = True + app.route('/', methods=['OPTIONS'])(index2) + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['OPTIONS'] + + +def test_provide_automatic_options_kwarg(app, client): + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', view_func=index, provide_automatic_options=False) + app.add_url_rule( + '/more', view_func=more, methods=['GET', 'POST'], + provide_automatic_options=False + ) + assert client.get('/').data == b'GET' + + rv = client.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + + if hasattr(client, 'options'): + rv = client.options('/') + else: + rv = client.open('/', method='OPTIONS') + + assert rv.status_code == 405 + + rv = client.head('/') + assert rv.status_code == 200 + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' + + rv = client.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + + if hasattr(client, 'options'): + rv = client.options('/more') + else: + rv = client.open('/more', method='OPTIONS') + + assert rv.status_code == 405 + + +def test_request_dispatching(app, client): + @app.route('/') + def index(): + return flask.request.method + + @app.route('/more', methods=['GET', 'POST']) + def more(): + return flask.request.method + + assert client.get('/').data == b'GET' + rv = client.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = client.head('/') + assert rv.status_code == 200 + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' + rv = client.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + +def test_disallow_string_for_allowed_methods(app): + with pytest.raises(TypeError): + @app.route('/', methods='GET POST') + def index(): + return "Hey" + + +def test_url_mapping(app, client): + random_uuid4 = "7eb41166-9ebf-4d26-b771-ea3f54f8b383" + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + def options(): + return random_uuid4 + + app.add_url_rule('/', 'index', index) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) + + app.add_url_rule('/options', 'options', options, methods=['options']) + + assert client.get('/').data == b'GET' + rv = client.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = client.head('/') + assert rv.status_code == 200 + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' + rv = client.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + rv = client.open('/options', method='OPTIONS') + assert rv.status_code == 200 + assert random_uuid4 in rv.data.decode("utf-8") + + +def test_werkzeug_routing(app, client): + from werkzeug.routing import Submount, Rule + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + + def bar(): + return 'bar' + + def index(): + return 'index' + + app.view_functions['bar'] = bar + app.view_functions['index'] = index + + assert client.get('/foo/').data == b'index' + assert client.get('/foo/bar').data == b'bar' + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Submount, Rule + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + + @app.endpoint('bar') + def bar(): + return 'bar' + + @app.endpoint('index') + def index(): + return 'index' + + assert client.get('/foo/').data == b'index' + assert client.get('/foo/bar').data == b'bar' + + +def test_session(app, client): + @app.route('/set', methods=['POST']) + def set(): + flask.session['value'] = flask.request.form['value'] + return 'value set' + + @app.route('/get') + def get(): + return flask.session['value'] + + assert client.post('/set', data={'value': '42'}).data == b'value set' + assert client.get('/get').data == b'42' + + +def test_session_using_server_name(app, client): + app.config.update( + SERVER_NAME='example.com' + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + + rv = client.get('/', 'http://example.com/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + +def test_session_using_server_name_and_port(app, client): + app.config.update( + SERVER_NAME='example.com:8080' + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + + rv = client.get('/', 'http://example.com:8080/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + +def test_session_using_server_name_port_and_path(app, client): + app.config.update( + SERVER_NAME='example.com:8080', + APPLICATION_ROOT='/foo' + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + + rv = client.get('/', 'http://example.com:8080/foo') + assert 'domain=example.com' in rv.headers['set-cookie'].lower() + assert 'path=/foo' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + +def test_session_using_application_root(app, client): + class PrefixPathMiddleware(object): + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') + app.config.update( + APPLICATION_ROOT='/bar' + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + + rv = client.get('/', 'http://example.com:8080/') + assert 'path=/bar' in rv.headers['set-cookie'].lower() + + +def test_session_using_session_settings(app, client): + app.config.update( + SERVER_NAME='www.example.com:8080', + APPLICATION_ROOT='/test', + SESSION_COOKIE_DOMAIN='.example.com', + SESSION_COOKIE_HTTPONLY=False, + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_PATH='/' + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + + rv = client.get('/', 'http://www.example.com:8080/test/') + cookie = rv.headers['set-cookie'].lower() + assert 'domain=.example.com' in cookie + assert 'path=/' in cookie + assert 'secure' in cookie + assert 'httponly' not in cookie + + +def test_session_localhost_warning(recwarn, app, client): + app.config.update( + SERVER_NAME='localhost:5000', + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'testing' + + rv = client.get('/', 'http://localhost:5000/') + assert 'domain' not in rv.headers['set-cookie'].lower() + w = recwarn.pop(UserWarning) + assert '"localhost" is not a valid cookie domain' in str(w.message) + + +def test_session_ip_warning(recwarn, app, client): + app.config.update( + SERVER_NAME='127.0.0.1:5000', + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'testing' + + rv = client.get('/', 'http://127.0.0.1:5000/') + assert 'domain=127.0.0.1' in rv.headers['set-cookie'].lower() + w = recwarn.pop(UserWarning) + assert 'cookie domain is an IP' in str(w.message) + + +def test_missing_session(app): + app.secret_key = None + + def expect_exception(f, *args, **kwargs): + e = pytest.raises(RuntimeError, f, *args, **kwargs) + assert e.value.args and 'session is unavailable' in e.value.args[0] + + with app.test_request_context(): + assert flask.session.get('missing_key') is None + expect_exception(flask.session.__setitem__, 'foo', 42) + expect_exception(flask.session.pop, 'foo') + + +def test_session_expiration(app, client): + permanent = True + + @app.route('/') + def index(): + flask.session['test'] = 42 + flask.session.permanent = permanent + return '' + + @app.route('/test') + def test(): + return text_type(flask.session.permanent) + + rv = client.get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie']) + expires = parse_date(match.group()) + expected = datetime.utcnow() + app.permanent_session_lifetime + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get('/test') + assert rv.data == b'True' + + permanent = False + rv = client.get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + assert match is None + + +def test_session_stored_last(app, client): + @app.after_request + def modify_session(response): + flask.session['foo'] = 42 + return response + + @app.route('/') + def dump_session_contents(): + return repr(flask.session.get('foo')) + + assert client.get('/').data == b'None' + assert client.get('/').data == b'42' + + +def test_session_special_types(app, client): + now = datetime.utcnow().replace(microsecond=0) + the_uuid = uuid.uuid4() + + @app.route('/') + def dump_session_contents(): + flask.session['t'] = (1, 2, 3) + flask.session['b'] = b'\xff' + flask.session['m'] = flask.Markup('') + flask.session['u'] = the_uuid + flask.session['d'] = now + flask.session['t_tag'] = {' t': 'not-a-tuple'} + flask.session['di_t_tag'] = {' t__': 'not-a-tuple'} + flask.session['di_tag'] = {' di': 'not-a-dict'} + return '', 204 + + with client: + client.get('/') + s = flask.session + assert s['t'] == (1, 2, 3) + assert type(s['b']) == bytes + assert s['b'] == b'\xff' + assert type(s['m']) == flask.Markup + assert s['m'] == flask.Markup('') + assert s['u'] == the_uuid + assert s['d'] == now + assert s['t_tag'] == {' t': 'not-a-tuple'} + assert s['di_t_tag'] == {' t__': 'not-a-tuple'} + assert s['di_tag'] == {' di': 'not-a-dict'} + + +def test_session_cookie_setting(app): + is_permanent = True + + @app.route('/bump') + def bump(): + rv = flask.session['foo'] = flask.session.get('foo', 0) + 1 + flask.session.permanent = is_permanent + return str(rv) + + @app.route('/read') + def read(): + return str(flask.session.get('foo', 0)) + + def run_test(expect_header): + with app.test_client() as c: + assert c.get('/bump').data == b'1' + assert c.get('/bump').data == b'2' + assert c.get('/bump').data == b'3' + + rv = c.get('/read') + set_cookie = rv.headers.get('set-cookie') + assert (set_cookie is not None) == expect_header + assert rv.data == b'3' + + is_permanent = True + app.config['SESSION_REFRESH_EACH_REQUEST'] = True + run_test(expect_header=True) + + is_permanent = True + app.config['SESSION_REFRESH_EACH_REQUEST'] = False + run_test(expect_header=False) + + is_permanent = False + app.config['SESSION_REFRESH_EACH_REQUEST'] = True + run_test(expect_header=False) + + is_permanent = False + app.config['SESSION_REFRESH_EACH_REQUEST'] = False + run_test(expect_header=False) + + +def test_session_vary_cookie(app, client): + @app.route('/set') + def set_session(): + flask.session['test'] = 'test' + return '' + + @app.route('/get') + def get(): + return flask.session.get('test') + + @app.route('/getitem') + def getitem(): + return flask.session['test'] + + @app.route('/setdefault') + def setdefault(): + return flask.session.setdefault('test', 'default') + + @app.route('/vary-cookie-header-set') + def vary_cookie_header_set(): + response = flask.Response() + response.vary.add('Cookie') + flask.session['test'] = 'test' + return response + + @app.route('/vary-header-set') + def vary_header_set(): + response = flask.Response() + response.vary.update(('Accept-Encoding', 'Accept-Language')) + flask.session['test'] = 'test' + return response + + @app.route('/no-vary-header') + def no_vary_header(): + return '' + + def expect(path, header_value='Cookie'): + rv = client.get(path) + + if header_value: + assert len(rv.headers.get_all('Vary')) == 1 + assert rv.headers['Vary'] == header_value + else: + assert 'Vary' not in rv.headers + + expect('/set') + expect('/get') + expect('/getitem') + expect('/setdefault') + expect('/vary-cookie-header-set') + expect('/vary-header-set', 'Accept-Encoding, Accept-Language, Cookie') + expect('/no-vary-header', None) + + +def test_flashes(app, req_ctx): + assert not flask.session.modified + flask.flash('Zap') + flask.session.modified = False + flask.flash('Zip') + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + + +def test_extended_flashing(app): + + @app.route('/') + def index(): + flask.flash(u'Hello World') + flask.flash(u'Hello World', 'error') + flask.flash(flask.Markup(u'Testing'), 'warning') + return '' + + @app.route('/test/') + def test(): + messages = flask.get_flashed_messages() + assert list(messages) == [ + u'Hello World', + u'Hello World', + flask.Markup(u'Testing') + ] + return '' + + @app.route('/test_with_categories/') + def test_with_categories(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert list(messages) == [ + ('message', u'Hello World'), + ('error', u'Hello World'), + ('warning', flask.Markup(u'Testing')) + ] + return '' + + @app.route('/test_filter/') + def test_filter(): + messages = flask.get_flashed_messages( + category_filter=['message'], with_categories=True) + assert list(messages) == [('message', u'Hello World')] + return '' + + @app.route('/test_filters/') + def test_filters(): + messages = flask.get_flashed_messages( + category_filter=['message', 'warning'], with_categories=True) + assert list(messages) == [ + ('message', u'Hello World'), + ('warning', flask.Markup(u'Testing')) + ] + return '' + + @app.route('/test_filters_without_returning_categories/') + def test_filters2(): + messages = flask.get_flashed_messages( + category_filter=['message', 'warning']) + assert len(messages) == 2 + assert messages[0] == u'Hello World' + assert messages[1] == flask.Markup(u'Testing') + return '' + + + client = app.test_client() + client.get('/') + client.get('/test_with_categories/') + + client = app.test_client() + client.get('/') + client.get('/test_filter/') + + client = app.test_client() + client.get('/') + client.get('/test_filters/') + + client = app.test_client() + client.get('/') + client.get('/test_filters_without_returning_categories/') + + +def test_request_processing(app, client): + evts = [] + + @app.before_request + def before_request(): + evts.append('before') + + @app.after_request + def after_request(response): + response.data += b'|after' + evts.append('after') + return response + + @app.route('/') + def index(): + assert 'before' in evts + assert 'after' not in evts + return 'request' + + assert 'after' not in evts + rv = client.get('/').data + assert 'after' in evts + assert rv == b'request|after' + + +def test_request_preprocessing_early_return(app, client): + evts = [] + + @app.before_request + def before_request1(): + evts.append(1) + + @app.before_request + def before_request2(): + evts.append(2) + return "hello" + + @app.before_request + def before_request3(): + evts.append(3) + return "bye" + + @app.route('/') + def index(): + evts.append('index') + return "damnit" + + rv = client.get('/').data.strip() + assert rv == b'hello' + assert evts == [1, 2] + + +def test_after_request_processing(app, client): + @app.route('/') + def index(): + @flask.after_this_request + def foo(response): + response.headers['X-Foo'] = 'a header' + return response + + return 'Test' + + resp = client.get('/') + assert resp.status_code == 200 + assert resp.headers['X-Foo'] == 'a header' + + +def test_teardown_request_handler(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route('/') + def root(): + return "Response" + + rv = client.get('/') + assert rv.status_code == 200 + assert b'Response' in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_debug_mode(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route('/') + def root(): + return "Response" + + rv = client.get('/') + assert rv.status_code == 200 + assert b'Response' in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_error(app, client): + called = [] + app.testing = False + + @app.teardown_request + def teardown_request1(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + try: + raise TypeError() + except: + pass + + @app.teardown_request + def teardown_request2(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + try: + raise TypeError() + except: + pass + + @app.route('/') + def fails(): + 1 // 0 + + rv = client.get('/') + assert rv.status_code == 500 + assert b'Internal Server Error' in rv.data + assert len(called) == 2 + + +def test_before_after_request_order(app, client): + called = [] + + @app.before_request + def before1(): + called.append(1) + + @app.before_request + def before2(): + called.append(2) + + @app.after_request + def after1(response): + called.append(4) + return response + + @app.after_request + def after2(response): + called.append(3) + return response + + @app.teardown_request + def finish1(exc): + called.append(6) + + @app.teardown_request + def finish2(exc): + called.append(5) + + @app.route('/') + def index(): + return '42' + + rv = client.get('/') + assert rv.data == b'42' + assert called == [1, 2, 3, 4, 5, 6] + + +def test_error_handling(app, client): + app.testing = False + + @app.errorhandler(404) + def not_found(e): + return 'not found', 404 + + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + + @app.errorhandler(Forbidden) + def forbidden(e): + return 'forbidden', 403 + + @app.route('/') + def index(): + flask.abort(404) + + @app.route('/error') + def error(): + 1 // 0 + + @app.route('/forbidden') + def error2(): + flask.abort(403) + + rv = client.get('/') + assert rv.status_code == 404 + assert rv.data == b'not found' + rv = client.get('/error') + assert rv.status_code == 500 + assert b'internal server error' == rv.data + rv = client.get('/forbidden') + assert rv.status_code == 403 + assert b'forbidden' == rv.data + + +def test_error_handler_unknown_code(app): + with pytest.raises(KeyError) as exc_info: + app.register_error_handler(999, lambda e: ('999', 999)) + + assert 'Use a subclass' in exc_info.value.args[0] + + +def test_error_handling_processing(app, client): + app.testing = False + + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + + @app.route('/') + def broken_func(): + 1 // 0 + + @app.after_request + def after_request(resp): + resp.mimetype = 'text/x-special' + return resp + + resp = client.get('/') + assert resp.mimetype == 'text/x-special' + assert resp.data == b'internal server error' + + +def test_baseexception_error_handling(app, client): + app.testing = False + + @app.route('/') + def broken_func(): + raise KeyboardInterrupt() + + with pytest.raises(KeyboardInterrupt): + client.get('/') + + ctx = flask._request_ctx_stack.top + assert ctx.preserved + assert type(ctx._preserved_exc) is KeyboardInterrupt + + +def test_before_request_and_routing_errors(app, client): + @app.before_request + def attach_something(): + flask.g.something = 'value' + + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + + rv = client.get('/') + assert rv.status_code == 404 + assert rv.data == b'value' + + +def test_user_error_handling(app, client): + class MyException(Exception): + pass + + @app.errorhandler(MyException) + def handle_my_exception(e): + assert isinstance(e, MyException) + return '42' + + @app.route('/') + def index(): + raise MyException() + + assert client.get('/').data == b'42' + + +def test_http_error_subclass_handling(app, client): + class ForbiddenSubclass(Forbidden): + pass + + @app.errorhandler(ForbiddenSubclass) + def handle_forbidden_subclass(e): + assert isinstance(e, ForbiddenSubclass) + return 'banana' + + @app.errorhandler(403) + def handle_forbidden_subclass(e): + assert not isinstance(e, ForbiddenSubclass) + assert isinstance(e, Forbidden) + return 'apple' + + @app.route('/1') + def index1(): + raise ForbiddenSubclass() + + @app.route('/2') + def index2(): + flask.abort(403) + + @app.route('/3') + def index3(): + raise Forbidden() + + assert client.get('/1').data == b'banana' + assert client.get('/2').data == b'apple' + assert client.get('/3').data == b'apple' + + +def test_errorhandler_precedence(app, client): + class E1(Exception): + pass + + class E2(Exception): + pass + + class E3(E1, E2): + pass + + @app.errorhandler(E2) + def handle_e2(e): + return 'E2' + + @app.errorhandler(Exception) + def handle_exception(e): + return 'Exception' + + @app.route('/E1') + def raise_e1(): + raise E1 + + @app.route('/E3') + def raise_e3(): + raise E3 + + rv = client.get('/E1') + assert rv.data == b'Exception' + + rv = client.get('/E3') + assert rv.data == b'E2' + + +def test_trapping_of_bad_request_key_errors(app, client): + @app.route('/fail') + def fail(): + flask.request.form['missing_key'] + + rv = client.get('/fail') + assert rv.status_code == 400 + assert b'missing_key' not in rv.data + + app.config['TRAP_BAD_REQUEST_ERRORS'] = True + + with pytest.raises(KeyError) as e: + client.get("/fail") + + assert e.errisinstance(BadRequest) + assert 'missing_key' in e.value.description + + +def test_trapping_of_all_http_exceptions(app, client): + app.config['TRAP_HTTP_EXCEPTIONS'] = True + + @app.route('/fail') + def fail(): + flask.abort(404) + + with pytest.raises(NotFound): + client.get('/fail') + + +def test_error_handler_after_processor_error(app, client): + app.testing = False + + @app.before_request + def before_request(): + if trigger == 'before': + 1 // 0 + + @app.after_request + def after_request(response): + if trigger == 'after': + 1 // 0 + return response + + @app.route('/') + def index(): + return 'Foo' + + @app.errorhandler(500) + def internal_server_error(e): + return 'Hello Server Error', 500 + + for trigger in 'before', 'after': + rv = client.get('/') + assert rv.status_code == 500 + assert rv.data == b'Hello Server Error' + + +def test_enctype_debug_helper(app, client): + from flask.debughelpers import DebugFilesKeyError + app.debug = True + + @app.route('/fail', methods=['POST']) + def index(): + return flask.request.files['foo'].filename + + with client: + with pytest.raises(DebugFilesKeyError) as e: + client.post('/fail', data={'foo': 'index.txt'}) + assert 'no file contents were transmitted' in str(e.value) + assert 'This was submitted: "index.txt"' in str(e.value) + + +def test_response_types(app, client): + @app.route('/text') + def from_text(): + return u'Hällo Wörld' + + @app.route('/bytes') + def from_bytes(): + return u'Hällo Wörld'.encode('utf-8') + + @app.route('/full_tuple') + def from_full_tuple(): + return 'Meh', 400, { + 'X-Foo': 'Testing', + 'Content-Type': 'text/plain; charset=utf-8' + } + + @app.route('/text_headers') + def from_text_headers(): + return 'Hello', { + 'X-Foo': 'Test', + 'Content-Type': 'text/plain; charset=utf-8' + } + + @app.route('/text_status') + def from_text_status(): + return 'Hi, status!', 400 + + @app.route('/response_headers') + def from_response_headers(): + return flask.Response('Hello world', 404, {'X-Foo': 'Baz'}), { + "X-Foo": "Bar", + "X-Bar": "Foo" + } + + @app.route('/response_status') + def from_response_status(): + return app.response_class('Hello world', 400), 500 + + @app.route('/wsgi') + def from_wsgi(): + return NotFound() + + assert client.get('/text').data == u'Hällo Wörld'.encode('utf-8') + assert client.get('/bytes').data == u'Hällo Wörld'.encode('utf-8') + + rv = client.get('/full_tuple') + assert rv.data == b'Meh' + assert rv.headers['X-Foo'] == 'Testing' + assert rv.status_code == 400 + assert rv.mimetype == 'text/plain' + + rv = client.get('/text_headers') + assert rv.data == b'Hello' + assert rv.headers['X-Foo'] == 'Test' + assert rv.status_code == 200 + assert rv.mimetype == 'text/plain' + + rv = client.get('/text_status') + assert rv.data == b'Hi, status!' + assert rv.status_code == 400 + assert rv.mimetype == 'text/html' + + rv = client.get('/response_headers') + assert rv.data == b'Hello world' + assert rv.headers.getlist('X-Foo') == ['Baz', 'Bar'] + assert rv.headers['X-Bar'] == 'Foo' + assert rv.status_code == 404 + + rv = client.get('/response_status') + assert rv.data == b'Hello world' + assert rv.status_code == 500 + + rv = client.get('/wsgi') + assert b'Not Found' in rv.data + assert rv.status_code == 404 + + +def test_response_type_errors(): + app = flask.Flask(__name__) + app.testing = True + + @app.route('/none') + def from_none(): + pass + + @app.route('/small_tuple') + def from_small_tuple(): + return 'Hello', + + @app.route('/large_tuple') + def from_large_tuple(): + return 'Hello', 234, {'X-Foo': 'Bar'}, '???' + + @app.route('/bad_type') + def from_bad_type(): + return True + + @app.route('/bad_wsgi') + def from_bad_wsgi(): + return lambda: None + + c = app.test_client() + + with pytest.raises(TypeError) as e: + c.get('/none') + assert 'returned None' in str(e) + + with pytest.raises(TypeError) as e: + c.get('/small_tuple') + assert 'tuple must have the form' in str(e) + + pytest.raises(TypeError, c.get, '/large_tuple') + + with pytest.raises(TypeError) as e: + c.get('/bad_type') + assert 'it was a bool' in str(e) + + pytest.raises(TypeError, c.get, '/bad_wsgi') + + +def test_make_response(app, req_ctx): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == b'' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('Awesome') + assert rv.status_code == 200 + assert rv.data == b'Awesome' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('W00t', 404) + assert rv.status_code == 404 + assert rv.data == b'W00t' + assert rv.mimetype == 'text/html' + + +def test_make_response_with_response_instance(app, req_ctx): + rv = flask.make_response( + flask.jsonify({'msg': 'W00t'}), 400) + assert rv.status_code == 400 + assert rv.data == b'{"msg":"W00t"}\n' + assert rv.mimetype == 'application/json' + + rv = flask.make_response( + flask.Response(''), 400) + assert rv.status_code == 400 + assert rv.data == b'' + assert rv.mimetype == 'text/html' + + rv = flask.make_response( + flask.Response('', headers={'Content-Type': 'text/html'}), + 400, [('X-Foo', 'bar')]) + assert rv.status_code == 400 + assert rv.headers['Content-Type'] == 'text/html' + assert rv.headers['X-Foo'] == 'bar' + + +def test_jsonify_no_prettyprint(app, req_ctx): + app.config.update({"JSONIFY_PRETTYPRINT_REGULAR": False}) + compressed_msg = b'{"msg":{"submsg":"W00t"},"msg2":"foobar"}\n' + uncompressed_msg = { + "msg": { + "submsg": "W00t" + }, + "msg2": "foobar" + } + + rv = flask.make_response( + flask.jsonify(uncompressed_msg), 200) + assert rv.data == compressed_msg + + +def test_jsonify_prettyprint(app, req_ctx): + app.config.update({"JSONIFY_PRETTYPRINT_REGULAR": True}) + compressed_msg = {"msg": {"submsg": "W00t"}, "msg2": "foobar"} + pretty_response = \ + b'{\n "msg": {\n "submsg": "W00t"\n }, \n "msg2": "foobar"\n}\n' + + rv = flask.make_response( + flask.jsonify(compressed_msg), 200) + assert rv.data == pretty_response + + +def test_jsonify_mimetype(app, req_ctx): + app.config.update({"JSONIFY_MIMETYPE": 'application/vnd.api+json'}) + msg = { + "msg": {"submsg": "W00t"}, + } + rv = flask.make_response( + flask.jsonify(msg), 200) + assert rv.mimetype == 'application/vnd.api+json' + + +def test_jsonify_args_and_kwargs_check(app, req_ctx): + with pytest.raises(TypeError) as e: + flask.jsonify('fake args', kwargs='fake') + assert 'behavior undefined' in str(e.value) + + +def test_url_generation(app, req_ctx): + @app.route('/hello/', methods=['POST']) + def hello(): + pass + + assert flask.url_for('hello', name='test x') == '/hello/test%20x' + assert flask.url_for('hello', name='test x', _external=True) == \ + 'http://localhost/hello/test%20x' + + +def test_build_error_handler(app): + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, 'spam') + + try: + with app.test_request_context(): + flask.url_for('spam') + except BuildError as err: + error = err + try: + raise RuntimeError('Test case where BuildError is not current.') + except RuntimeError: + pytest.raises( + BuildError, app.handle_url_build_error, error, 'spam', {}) + + def handler(error, endpoint, values): + return '/test_handler/' + + app.url_build_error_handlers.append(handler) + with app.test_request_context(): + assert flask.url_for('spam') == '/test_handler/' + + +def test_build_error_handler_reraise(app): + def handler_raises_build_error(error, endpoint, values): + raise error + + app.url_build_error_handlers.append(handler_raises_build_error) + + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, 'not.existing') + + +def test_url_for_passes_special_values_to_build_error_handler(app): + @app.url_build_error_handlers.append + def handler(error, endpoint, values): + assert values == { + '_external': False, + '_anchor': None, + '_method': None, + '_scheme': None, + } + return 'handled' + + with app.test_request_context(): + flask.url_for('/') + + +def test_custom_converters(app, client): + from werkzeug.routing import BaseConverter + + class ListConverter(BaseConverter): + def to_python(self, value): + return value.split(',') + + def to_url(self, value): + base_to_url = super(ListConverter, self).to_url + return ','.join(base_to_url(x) for x in value) + + app.url_map.converters['list'] = ListConverter + + @app.route('/') + def index(args): + return '|'.join(args) + + assert client.get('/1,2,3').data == b'1|2|3' + + +def test_static_files(app, client): + rv = client.get('/static/index.html') + assert rv.status_code == 200 + assert rv.data.strip() == b'

Hello World!

' + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') == \ + '/static/index.html' + rv.close() + + +def test_static_url_path(): + app = flask.Flask(__name__, static_url_path='/foo') + 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_route_with_host_matching(): + app = flask.Flask(__name__, host_matching=True, static_host='example.com') + c = app.test_client() + rv = c.get('http://example.com/static/index.html') + assert rv.status_code == 200 + rv.close() + with app.test_request_context(): + rv = flask.url_for('static', filename='index.html', _external=True) + assert rv == 'http://example.com/static/index.html' + with pytest.raises(Exception): + flask.Flask(__name__, static_host='example.com') + with pytest.raises(Exception): + flask.Flask(__name__, host_matching=True) + flask.Flask(__name__, host_matching=True, static_folder=None) + + +def test_request_locals(): + assert repr(flask.g) == '' + assert not flask.g + + +def test_test_app_proper_environ(app, client): + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + + @app.route('/') + def index(): + return 'Foo' + + @app.route('/', subdomain='foo') + def subdomain(): + return 'Foo SubDomain' + + rv = client.get('/') + assert rv.data == b'Foo' + + rv = client.get('/', 'http://localhost.localdomain:5000') + assert rv.data == b'Foo' + + rv = client.get('/', 'https://localhost.localdomain:5000') + assert rv.data == b'Foo' + + app.config.update(SERVER_NAME='localhost.localdomain') + rv = client.get('/', 'https://localhost.localdomain') + assert rv.data == b'Foo' + + try: + app.config.update(SERVER_NAME='localhost.localdomain:443') + rv = client.get('/', 'https://localhost.localdomain') + assert rv.status_code == 404 + except ValueError as e: + assert str(e) == ( + "the server name provided " + "('localhost.localdomain:443') does not match the " + "server name from the WSGI environment ('localhost.localdomain')" + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + rv = client.get('/', 'http://foo.localhost') + assert rv.status_code == 404 + except ValueError as e: + assert str(e) == ( + "the server name provided " + "('localhost.localdomain') does not match the " + "server name from the WSGI environment ('foo.localhost')" + ) + + rv = client.get('/', 'http://foo.localhost.localdomain') + assert rv.data == b'Foo SubDomain' + + +def test_exception_propagation(app, client): + def apprunner(config_key): + + @app.route('/') + def index(): + 1 // 0 + + if config_key is not None: + app.config[config_key] = True + with pytest.raises(Exception): + client.get('/') + else: + assert client.get('/').status_code == 500 + + for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: + t = Thread(target=apprunner, args=(config_key,)) + t.start() + t.join() + + +@pytest.mark.parametrize('debug', [True, False]) +@pytest.mark.parametrize('use_debugger', [True, False]) +@pytest.mark.parametrize('use_reloader', [True, False]) +@pytest.mark.parametrize('propagate_exceptions', [None, True, False]) +def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, + use_reloader, propagate_exceptions, app): + rv = {} + + def run_simple_mock(*args, **kwargs): + rv['passthrough_errors'] = kwargs.get('passthrough_errors') + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions + app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) + + +def test_max_content_length(app, client): + app.config['MAX_CONTENT_LENGTH'] = 64 + + @app.before_request + def always_first(): + flask.request.form['myfile'] + assert False + + @app.route('/accept', methods=['POST']) + def accept_file(): + flask.request.form['myfile'] + assert False + + @app.errorhandler(413) + def catcher(error): + return '42' + + rv = client.post('/accept', data={'myfile': 'foo' * 100}) + assert rv.data == b'42' + + +def test_url_processors(app, client): + + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and \ + app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values.setdefault('lang_code', flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + return flask.url_for('about') + + @app.route('//about') + def about(): + return flask.url_for('something_else') + + @app.route('/foo') + def something_else(): + return flask.url_for('about', lang_code='en') + + assert client.get('/de/').data == b'/de/about' + assert client.get('/de/about').data == b'/foo' + assert client.get('/foo').data == b'/en/about' + + +def test_inject_blueprint_url_defaults(app): + bp = flask.Blueprint('foo.bar.baz', __name__, + template_folder='template') + + @bp.url_defaults + def bp_defaults(endpoint, values): + values['page'] = 'login' + + @bp.route('/') + def view(page): + pass + + app.register_blueprint(bp) + + values = dict() + app.inject_url_defaults('foo.bar.baz.view', values) + expected = dict(page='login') + assert values == expected + + with app.test_request_context('/somepage'): + url = flask.url_for('foo.bar.baz.view') + expected = '/login' + assert url == expected + + +def test_nonascii_pathinfo(app, client): + @app.route(u'/???????') + def index(): + return 'Hello World!' + + rv = client.get(u'/???????') + assert rv.data == b'Hello World!' + + +def test_debug_mode_complains_after_first_request(app, client): + app.debug = True + + @app.route('/') + def index(): + return 'Awesome' + + assert not app.got_first_request + assert client.get('/').data == b'Awesome' + with pytest.raises(AssertionError) as e: + @app.route('/foo') + def broken(): + return 'Meh' + assert 'A setup function was called' in str(e) + + app.debug = False + + @app.route('/foo') + def working(): + return 'Meh' + + assert client.get('/foo').data == b'Meh' + assert app.got_first_request + + +def test_before_first_request_functions(app, client): + got = [] + + @app.before_first_request + def foo(): + got.append(42) + + client.get('/') + assert got == [42] + client.get('/') + assert got == [42] + assert app.got_first_request + + +def test_before_first_request_functions_concurrent(app, client): + got = [] + + @app.before_first_request + def foo(): + time.sleep(0.2) + got.append(42) + + def get_and_assert(): + client.get("/") + assert got == [42] + + t = Thread(target=get_and_assert) + t.start() + get_and_assert() + t.join() + assert app.got_first_request + + +def test_routing_redirect_debugging(app, client): + app.debug = True + + @app.route('/foo/', methods=['GET', 'POST']) + def foo(): + return 'success' + + with client: + with pytest.raises(AssertionError) as e: + client.post('/foo', data={}) + assert 'http://localhost/foo/' in str(e) + assert ('Make sure to directly send ' + 'your POST-request to this URL') in str(e) + + rv = client.get('/foo', data={}, follow_redirects=True) + assert rv.data == b'success' + + app.debug = False + with client: + rv = client.post('/foo', data={}, follow_redirects=True) + assert rv.data == b'success' + + +def test_route_decorator_custom_endpoint(app, client): + app.debug = True + + @app.route('/foo/') + def foo(): + return flask.request.endpoint + + @app.route('/bar/', endpoint='bar') + def for_bar(): + return flask.request.endpoint + + @app.route('/bar/123', endpoint='123') + def for_bar_foo(): + return flask.request.endpoint + + with app.test_request_context(): + assert flask.url_for('foo') == '/foo/' + assert flask.url_for('bar') == '/bar/' + assert flask.url_for('123') == '/bar/123' + + assert client.get('/foo/').data == b'foo' + assert client.get('/bar/').data == b'bar' + assert client.get('/bar/123').data == b'123' + + +def test_preserve_only_once(app, client): + app.debug = True + + @app.route('/fail') + def fail_func(): + 1 // 0 + + for x in range(3): + with pytest.raises(ZeroDivisionError): + client.get('/fail') + + assert flask._request_ctx_stack.top is not None + assert flask._app_ctx_stack.top is not None + flask._request_ctx_stack.top.pop() + assert flask._request_ctx_stack.top is None + assert flask._app_ctx_stack.top is None + + +def test_preserve_remembers_exception(app, client): + app.debug = True + errors = [] + + @app.route('/fail') + def fail_func(): + 1 // 0 + + @app.route('/success') + def success_func(): + return 'Okay' + + @app.teardown_request + def teardown_handler(exc): + errors.append(exc) + + with pytest.raises(ZeroDivisionError): + client.get('/fail') + assert errors == [] + + client.get('/success') + assert len(errors) == 2 + assert isinstance(errors[0], ZeroDivisionError) + + client.get('/success') + assert len(errors) == 3 + assert errors[1] is None + + +def test_get_method_on_g(app_ctx): + assert flask.g.get('x') is None + assert flask.g.get('x', 11) == 11 + flask.g.x = 42 + assert flask.g.get('x') == 42 + assert flask.g.x == 42 + + +def test_g_iteration_protocol(app_ctx): + flask.g.foo = 23 + flask.g.bar = 42 + assert 'foo' in flask.g + assert 'foos' not in flask.g + assert sorted(flask.g) == ['bar', 'foo'] + + +def test_subdomain_basic_support(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain' + + @app.route('/') + def normal_index(): + return 'normal index' + + @app.route('/', subdomain='test') + def test_index(): + return 'test index' + + rv = client.get('/', 'http://localhost.localdomain/') + assert rv.data == b'normal index' + + rv = client.get('/', 'http://test.localhost.localdomain/') + assert rv.data == b'test index' + + +def test_subdomain_matching(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain' + + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + rv = client.get('/', 'http://mitsuhiko.localhost.localdomain/') + assert rv.data == b'index for mitsuhiko' + + +def test_subdomain_matching_with_ports(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain:3000' + + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + rv = client.get('/', 'http://mitsuhiko.localhost.localdomain:3000/') + assert rv.data == b'index for mitsuhiko' + + +def test_multi_route_rules(app, client): + @app.route('/') + @app.route('//') + def index(test='a'): + return test + + rv = client.open('/') + assert rv.data == b'a' + rv = client.open('/b/') + assert rv.data == b'b' + + +def test_multi_route_class_views(app, client): + class View(object): + def __init__(self, app): + app.add_url_rule('/', 'index', self.index) + app.add_url_rule('//', 'index', self.index) + + def index(self, test='a'): + return test + + _ = View(app) + rv = client.open('/') + assert rv.data == b'a' + rv = client.open('/b/') + assert rv.data == b'b' + + +def test_run_defaults(monkeypatch, app): + rv = {} + + def run_simple_mock(*args, **kwargs): + rv['result'] = 'running...' + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app.run() + assert rv['result'] == 'running...' + + +def test_run_server_port(monkeypatch, app): + rv = {} + + def run_simple_mock(hostname, port, application, *args, **kwargs): + rv['result'] = 'running on %s:%s ...' % (hostname, port) + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + hostname, port = 'localhost', 8000 + app.run(hostname, port, debug=True) + assert rv['result'] == 'running on %s:%s ...' % (hostname, port) + + +@pytest.mark.parametrize('host,port,expect_host,expect_port', ( + (None, None, 'pocoo.org', 8080), + ('localhost', None, 'localhost', 8080), + (None, 80, 'pocoo.org', 80), + ('localhost', 80, 'localhost', 80), +)) +def test_run_from_config(monkeypatch, host, port, expect_host, expect_port, app): + def run_simple_mock(hostname, port, *args, **kwargs): + assert hostname == expect_host + assert port == expect_port + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app.config['SERVER_NAME'] = 'pocoo.org:8080' + app.run(host, port) +""" + tests.blueprints + ~~~~~~~~~~~~~~~~ + + Blueprints (and currently modules) + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import pytest + +import flask + +from flask._compat import text_type +from werkzeug.http import parse_cache_control_header +from jinja2 import TemplateNotFound + + +def test_blueprint_specific_error_handling(app, client): + frontend = flask.Blueprint('frontend', __name__) + backend = flask.Blueprint('backend', __name__) + sideend = flask.Blueprint('sideend', __name__) + + @frontend.errorhandler(403) + def frontend_forbidden(e): + return 'frontend says no', 403 + + @frontend.route('/frontend-no') + def frontend_no(): + flask.abort(403) + + @backend.errorhandler(403) + def backend_forbidden(e): + return 'backend says no', 403 + + @backend.route('/backend-no') + def backend_no(): + flask.abort(403) + + @sideend.route('/what-is-a-sideend') + def sideend_no(): + flask.abort(403) + + app.register_blueprint(frontend) + app.register_blueprint(backend) + app.register_blueprint(sideend) + + @app.errorhandler(403) + def app_forbidden(e): + return 'application itself says no', 403 + + assert client.get('/frontend-no').data == b'frontend says no' + assert client.get('/backend-no').data == b'backend says no' + assert client.get('/what-is-a-sideend').data == b'application itself says no' + + +def test_blueprint_specific_user_error_handling(app, client): + class MyDecoratorException(Exception): + pass + + class MyFunctionException(Exception): + pass + + blue = flask.Blueprint('blue', __name__) + + @blue.errorhandler(MyDecoratorException) + def my_decorator_exception_handler(e): + assert isinstance(e, MyDecoratorException) + return 'boom' + + def my_function_exception_handler(e): + assert isinstance(e, MyFunctionException) + return 'bam' + + blue.register_error_handler(MyFunctionException, my_function_exception_handler) + + @blue.route('/decorator') + def blue_deco_test(): + raise MyDecoratorException() + + @blue.route('/function') + def blue_func_test(): + raise MyFunctionException() + + app.register_blueprint(blue) + + assert client.get('/decorator').data == b'boom' + assert client.get('/function').data == b'bam' + + +def test_blueprint_app_error_handling(app, client): + errors = flask.Blueprint('errors', __name__) + + @errors.app_errorhandler(403) + def forbidden_handler(e): + return 'you shall not pass', 403 + + @app.route('/forbidden') + def app_forbidden(): + flask.abort(403) + + forbidden_bp = flask.Blueprint('forbidden_bp', __name__) + + @forbidden_bp.route('/nope') + def bp_forbidden(): + flask.abort(403) + + app.register_blueprint(errors) + app.register_blueprint(forbidden_bp) + + assert client.get('/forbidden').data == b'you shall not pass' + assert client.get('/nope').data == b'you shall not pass' + + +def test_blueprint_url_definitions(app, client): + bp = flask.Blueprint('test', __name__) + + @bp.route('/foo', defaults={'baz': 42}) + def foo(bar, baz): + return '%s/%d' % (bar, baz) + + @bp.route('/bar') + def bar(bar): + return text_type(bar) + + app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) + app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) + + assert client.get('/1/foo').data == b'23/42' + assert client.get('/2/foo').data == b'19/42' + assert client.get('/1/bar').data == b'23' + assert client.get('/2/bar').data == b'19' + + +def test_blueprint_url_processors(app, client): + bp = flask.Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + return flask.url_for('.about') + + @bp.route('/about') + def about(): + return flask.url_for('.index') + + app.register_blueprint(bp) + + assert client.get('/de/').data == b'/de/about' + assert client.get('/de/about').data == b'/de/' + + +def test_templates_and_static(test_apps): + from blueprintapp import app + client = app.test_client() + + rv = client.get('/') + assert rv.data == b'Hello from the Frontend' + rv = client.get('/admin/') + assert rv.data == b'Hello from the Admin' + rv = client.get('/admin/index2') + assert rv.data == b'Hello from the Admin' + rv = client.get('/admin/static/test.txt') + assert rv.data.strip() == b'Admin File' + rv.close() + rv = client.get('/admin/static/css/test.css') + assert rv.data.strip() == b'/* nested file */' + rv.close() + + max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] + try: + expected_max_age = 3600 + if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age: + expected_max_age = 7200 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age + rv = client.get('/admin/static/css/test.css') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + assert cc.max_age == expected_max_age + rv.close() + finally: + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default + + with app.test_request_context(): + assert flask.url_for('admin.static', filename='test.txt') == '/admin/static/test.txt' + + with app.test_request_context(): + with pytest.raises(TemplateNotFound) as e: + flask.render_template('missing.html') + assert e.value.name == 'missing.html' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + +def test_default_static_cache_timeout(app): + class MyBlueprint(flask.Blueprint): + def get_send_file_max_age(self, filename): + return 100 + + blueprint = MyBlueprint('blueprint', __name__, static_folder='static') + app.register_blueprint(blueprint) + + max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] + try: + with app.test_request_context(): + unexpected_max_age = 3600 + if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == unexpected_max_age: + unexpected_max_age = 7200 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = unexpected_max_age + rv = blueprint.send_static_file('index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + assert cc.max_age == 100 + rv.close() + finally: + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default + + +def test_templates_list(test_apps): + from blueprintapp import app + templates = sorted(app.jinja_env.list_templates()) + assert templates == ['admin/index.html', 'frontend/index.html'] + + +def test_dotted_names(app, client): + frontend = flask.Blueprint('myapp.frontend', __name__) + backend = flask.Blueprint('myapp.backend', __name__) + + @frontend.route('/fe') + def frontend_index(): + return flask.url_for('myapp.backend.backend_index') + + @frontend.route('/fe2') + def frontend_page2(): + return flask.url_for('.frontend_index') + + @backend.route('/be') + def backend_index(): + return flask.url_for('myapp.frontend.frontend_index') + + app.register_blueprint(frontend) + app.register_blueprint(backend) + + assert client.get('/fe').data.strip() == b'/be' + assert client.get('/fe2').data.strip() == b'/fe' + assert client.get('/be').data.strip() == b'/fe' + + +def test_dotted_names_from_app(app, client): + test = flask.Blueprint('test', __name__) + + @app.route('/') + def app_index(): + return flask.url_for('test.index') + + @test.route('/test/') + def index(): + return flask.url_for('app_index') + + app.register_blueprint(test) + + rv = client.get('/') + assert rv.data == b'/test/' + + +def test_empty_url_defaults(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/', defaults={'page': 1}) + @bp.route('/page/') + def something(page): + return str(page) + + app.register_blueprint(bp) + + assert client.get('/').data == b'1' + assert client.get('/page/2').data == b'2' + + +def test_route_decorator_custom_endpoint(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/foo') + def foo(): + return flask.request.endpoint + + @bp.route('/bar', endpoint='bar') + def foo_bar(): + return flask.request.endpoint + + @bp.route('/bar/123', endpoint='123') + def foo_bar_foo(): + return flask.request.endpoint + + @bp.route('/bar/foo') + def bar_foo(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.request.endpoint + + assert client.get('/').data == b'index' + assert client.get('/py/foo').data == b'bp.foo' + assert client.get('/py/bar').data == b'bp.bar' + assert client.get('/py/bar/123').data == b'bp.123' + assert client.get('/py/bar/foo').data == b'bp.bar_foo' + + +def test_route_decorator_custom_endpoint_with_dots(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/foo') + def foo(): + return flask.request.endpoint + + try: + @bp.route('/bar', endpoint='bar.bar') + def foo_bar(): + return flask.request.endpoint + except AssertionError: + pass + else: + raise AssertionError('expected AssertionError not raised') + + try: + @bp.route('/bar/123', endpoint='bar.123') + def foo_bar_foo(): + return flask.request.endpoint + except AssertionError: + pass + else: + raise AssertionError('expected AssertionError not raised') + + def foo_foo_foo(): + pass + + pytest.raises( + AssertionError, + lambda: bp.add_url_rule( + '/bar/123', endpoint='bar.123', view_func=foo_foo_foo + ) + ) + + pytest.raises( + AssertionError, + bp.route('/bar/123', endpoint='bar.123'), + lambda: None + ) + + foo_foo_foo.__name__ = 'bar.123' + + pytest.raises( + AssertionError, + lambda: bp.add_url_rule( + '/bar/123', view_func=foo_foo_foo + ) + ) + + app.register_blueprint(bp, url_prefix='/py') + + assert client.get('/py/foo').data == b'bp.foo' + rv = client.get('/py/bar') + assert rv.status_code == 404 + rv = client.get('/py/bar/123') + assert rv.status_code == 404 + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Rule + app.url_map.add(Rule('/foo', endpoint='bar')) + + bp = flask.Blueprint('bp', __name__) + + @bp.endpoint('bar') + def foobar(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix='/bp_prefix') + + assert client.get('/foo').data == b'bar' + assert client.get('/bp_prefix/bar').status_code == 404 + + +def test_template_filter(app): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_filter() + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix='/py') + assert 'my_reverse' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['my_reverse'] == my_reverse + assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + + +def test_add_template_filter(app): + bp = flask.Blueprint('bp', __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse) + app.register_blueprint(bp, url_prefix='/py') + assert 'my_reverse' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['my_reverse'] == my_reverse + assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + + +def test_template_filter_with_name(app): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_filter('strrev') + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix='/py') + assert 'strrev' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['strrev'] == my_reverse + assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + + +def test_add_template_filter_with_name(app): + bp = flask.Blueprint('bp', __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, 'strrev') + app.register_blueprint(bp, url_prefix='/py') + assert 'strrev' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['strrev'] == my_reverse + assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + + +def test_template_filter_with_template(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + + rv = client.get('/') + assert rv.data == b'dcba' + + +def test_template_filter_after_route_with_template(app, client): + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix='/py') + rv = client.get('/') + assert rv.data == b'dcba' + + +def test_add_template_filter_with_template(app, client): + bp = flask.Blueprint('bp', __name__) + + def super_reverse(s): + return s[::-1] + + bp.add_app_template_filter(super_reverse) + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + + rv = client.get('/') + assert rv.data == b'dcba' + + +def test_template_filter_with_name_and_template(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_filter('super_reverse') + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + + rv = client.get('/') + assert rv.data == b'dcba' + + +def test_add_template_filter_with_name_and_template(app, client): + bp = flask.Blueprint('bp', __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, 'super_reverse') + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + + rv = client.get('/') + assert rv.data == b'dcba' + + +def test_template_test(app): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_test() + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix='/py') + assert 'is_boolean' in app.jinja_env.tests.keys() + assert app.jinja_env.tests['is_boolean'] == is_boolean + assert app.jinja_env.tests['is_boolean'](False) + + +def test_add_template_test(app): + bp = flask.Blueprint('bp', __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean) + app.register_blueprint(bp, url_prefix='/py') + assert 'is_boolean' in app.jinja_env.tests.keys() + assert app.jinja_env.tests['is_boolean'] == is_boolean + assert app.jinja_env.tests['is_boolean'](False) + + +def test_template_test_with_name(app): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix='/py') + assert 'boolean' in app.jinja_env.tests.keys() + assert app.jinja_env.tests['boolean'] == is_boolean + assert app.jinja_env.tests['boolean'](False) + + +def test_add_template_test_with_name(app): + bp = flask.Blueprint('bp', __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, 'boolean') + app.register_blueprint(bp, url_prefix='/py') + assert 'boolean' in app.jinja_env.tests.keys() + assert app.jinja_env.tests['boolean'] == is_boolean + assert app.jinja_env.tests['boolean'](False) + + +def test_template_test_with_template(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + + rv = client.get('/') + assert b'Success!' in rv.data + + +def test_template_test_after_route_with_template(app, client): + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix='/py') + rv = client.get('/') + assert b'Success!' in rv.data + + +def test_add_template_test_with_template(app, client): + bp = flask.Blueprint('bp', __name__) + + def boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(boolean) + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + + rv = client.get('/') + assert b'Success!' in rv.data + + +def test_template_test_with_name_and_template(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + + rv = client.get('/') + assert b'Success!' in rv.data + + +def test_add_template_test_with_name_and_template(app, client): + bp = flask.Blueprint('bp', __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, 'boolean') + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + + rv = client.get('/') + assert b'Success!' in rv.data + + +def test_context_processing(app, client): + answer_bp = flask.Blueprint('answer_bp', __name__) + + template_string = lambda: flask.render_template_string( + '{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}' + '{% if answer %}{{ answer }} is the answer.{% endif %}' + ) + + @answer_bp.app_context_processor + def not_answer_context_processor(): + return {'notanswer': 43} + + @answer_bp.context_processor + def answer_context_processor(): + return {'answer': 42} + + @answer_bp.route('/bp') + def bp_page(): + return template_string() + + @app.route('/') + def app_page(): + return template_string() + + app.register_blueprint(answer_bp) + + app_page_bytes = client.get('/').data + answer_page_bytes = client.get('/bp').data + + assert b'43' in app_page_bytes + assert b'42' not in app_page_bytes + + assert b'42' in answer_page_bytes + assert b'43' in answer_page_bytes + + +def test_template_global(app): + bp = flask.Blueprint('bp', __name__) + + @bp.app_template_global() + def get_answer(): + return 42 + + assert 'get_answer' not in app.jinja_env.globals.keys() + app.register_blueprint(bp) + + assert 'get_answer' in app.jinja_env.globals.keys() + assert app.jinja_env.globals['get_answer'] is get_answer + assert app.jinja_env.globals['get_answer']() == 42 + + with app.app_context(): + rv = flask.render_template_string('{{ get_answer() }}') + assert rv == '42' + + +def test_request_processing(app, client): + bp = flask.Blueprint('bp', __name__) + evts = [] + + @bp.before_request + def before_bp(): + evts.append('before') + + @bp.after_request + def after_bp(response): + response.data += b'|after' + evts.append('after') + return response + + @bp.teardown_request + def teardown_bp(exc): + evts.append('teardown') + + @bp.route('/bp') + def bp_endpoint(): + return 'request' + + app.register_blueprint(bp) + + assert evts == [] + rv = client.get('/bp') + assert rv.data == b'request|after' + assert evts == ['before', 'after', 'teardown'] + + +def test_app_request_processing(app, client): + bp = flask.Blueprint('bp', __name__) + evts = [] + + @bp.before_app_first_request + def before_first_request(): + evts.append('first') + + @bp.before_app_request + def before_app(): + evts.append('before') + + @bp.after_app_request + def after_app(response): + response.data += b'|after' + evts.append('after') + return response + + @bp.teardown_app_request + def teardown_app(exc): + evts.append('teardown') + + app.register_blueprint(bp) + + @app.route('/') + def bp_endpoint(): + return 'request' + + assert evts == [] + + resp = client.get('/').data + assert resp == b'request|after' + assert evts == ['first', 'before', 'after', 'teardown'] + + resp = client.get('/').data + assert resp == b'request|after' + assert evts == ['first'] + ['before', 'after', 'teardown'] * 2 + + +def test_app_url_processors(app, client): + bp = flask.Blueprint('bp', __name__) + + @bp.app_url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', flask.g.lang_code) + + @bp.app_url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code') + + @app.route('//') + def index(): + return flask.url_for('about') + + @app.route('//about') + def about(): + return flask.url_for('index') + + app.register_blueprint(bp) + + assert client.get('/de/').data == b'/de/about' + assert client.get('/de/about').data == b'/de/' +""" + tests.test_cli + ~~~~~~~~~~~~~~ + + :copyright: (c) 2016 by the Flask Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import os +import sys +from functools import partial + +import click +import pytest +from _pytest.monkeypatch import notset +from click.testing import CliRunner + +from flask import Flask, current_app +from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, \ + find_best_app, get_version, load_dotenv, locate_app, prepare_import, \ + with_appcontext + +cwd = os.getcwd() +test_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps' +)) + + +@pytest.fixture(autouse=True) +def manage_os_environ(monkeypatch): + os.environ.pop('FLASK_APP', None) + os.environ.pop('FLASK_DEBUG', None) + monkeypatch._setitem.extend(( + (os.environ, 'FLASK_APP', notset), + (os.environ, 'FLASK_DEBUG', notset), + (os.environ, 'FLASK_RUN_FROM_CLI', notset), + )) + + +@pytest.fixture +def runner(): + return CliRunner() + + +def test_cli_name(test_apps): + """Make sure the CLI object's name is the app's name and not the app itself""" + from cliapp.app import testapp + assert testapp.cli.name == testapp.name + + +def test_find_best_app(test_apps): + """Test if `find_best_app` behaves as expected with different combinations of input.""" + script_info = ScriptInfo() + + class Module: + app = Flask('appname') + + assert find_best_app(script_info, Module) == Module.app + + class Module: + application = Flask('appname') + + assert find_best_app(script_info, Module) == Module.application + + class Module: + myapp = Flask('appname') + + assert find_best_app(script_info, Module) == Module.myapp + + class Module: + @staticmethod + def create_app(): + return Flask('appname') + + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo): + return Flask('appname') + + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo=None, script_info=None): + return Flask('appname') + + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def make_app(): + return Flask('appname') + + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + myapp = Flask('appname1') + + @staticmethod + def create_app(): + return Flask('appname2') + + assert find_best_app(script_info, Module) == Module.myapp + + class Module: + myapp = Flask('appname1') + + @staticmethod + def create_app(): + return Flask('appname2') + + assert find_best_app(script_info, Module) == Module.myapp + + class Module: + pass + + pytest.raises(NoAppException, find_best_app, script_info, Module) + + class Module: + myapp1 = Flask('appname1') + myapp2 = Flask('appname2') + + pytest.raises(NoAppException, find_best_app, script_info, Module) + + class Module: + @staticmethod + def create_app(foo, bar): + return Flask('appname2') + + pytest.raises(NoAppException, find_best_app, script_info, Module) + + +@pytest.mark.parametrize('value,path,result', ( + ('test', cwd, 'test'), + ('test.py', cwd, 'test'), + ('a/test', os.path.join(cwd, 'a'), 'test'), + ('test/__init__.py', cwd, 'test'), + ('test/__init__', cwd, 'test'), + ( + os.path.join(test_path, 'cliapp', 'inner1', '__init__'), + test_path, 'cliapp.inner1' + ), + ( + os.path.join(test_path, 'cliapp', 'inner1', 'inner2'), + test_path, 'cliapp.inner1.inner2' + ), + ('test.a.b', cwd, 'test.a.b'), + (os.path.join(test_path, 'cliapp.app'), test_path, 'cliapp.app'), + ( + os.path.join(test_path, 'cliapp', 'message.txt'), + test_path, 'cliapp.message.txt' + ), +)) +def test_prepare_import(request, value, path, result): + """Expect the correct path to be set and the correct import and app names + to be returned. + + :func:`prepare_exec_for_file` has a side effect where the parent directory + of the given import is added to :data:`sys.path`. This is reset after the + test runs. + """ + original_path = sys.path[:] + + def reset_path(): + sys.path[:] = original_path + + request.addfinalizer(reset_path) + + assert prepare_import(value) == result + assert sys.path[0] == path + + +@pytest.mark.parametrize('iname,aname,result', ( + ('cliapp.app', None, 'testapp'), + ('cliapp.app', 'testapp', 'testapp'), + ('cliapp.factory', None, 'app'), + ('cliapp.factory', 'create_app', 'app'), + ('cliapp.factory', 'create_app()', 'app'), + ('cliapp.factory', 'create_app2("foo", "bar")', 'app2_foo_bar'), + ('cliapp.factory', 'create_app2("foo", "bar", )', 'app2_foo_bar'), + ('cliapp.factory', 'create_app3("foo")', 'app3_foo_spam'), + ('cliapp.factory', ' create_app () ', 'app'), +)) +def test_locate_app(test_apps, iname, aname, result): + info = ScriptInfo() + info.data['test'] = 'spam' + assert locate_app(info, iname, aname).name == result + + +@pytest.mark.parametrize('iname,aname', ( + ('notanapp.py', None), + ('cliapp/app', None), + ('cliapp.app', 'notanapp'), + ('cliapp.factory', 'create_app2("foo")'), + ('cliapp.factory', 'create_app('), + ('cliapp.factory', 'no_app'), + ('cliapp.importerrorapp', None), + ('cliapp.message.txt', None), +)) +def test_locate_app_raises(test_apps, iname, aname): + info = ScriptInfo() + + with pytest.raises(NoAppException): + locate_app(info, iname, aname) + + +def test_locate_app_suppress_raise(): + info = ScriptInfo() + app = locate_app(info, 'notanapp.py', None, raise_if_not_found=False) + assert app is None + + with pytest.raises(NoAppException): + locate_app( + info, 'cliapp.importerrorapp', None, raise_if_not_found=False + ) + + +def test_get_version(test_apps, capsys): + """Test of get_version.""" + from flask import __version__ as flask_ver + from sys import version as py_ver + + class MockCtx(object): + resilient_parsing = False + color = None + + def exit(self): return + + ctx = MockCtx() + get_version(ctx, None, "test") + out, err = capsys.readouterr() + assert flask_ver in out + assert py_ver in out + + +def test_scriptinfo(test_apps, monkeypatch): + """Test of ScriptInfo.""" + obj = ScriptInfo(app_import_path="cliapp.app:testapp") + assert obj.load_app().name == "testapp" + assert obj.load_app().name == "testapp" + + def create_app(info): + return Flask("createapp") + + obj = ScriptInfo(create_app=create_app) + app = obj.load_app() + assert app.name == "createapp" + assert obj.load_app() == app + + obj = ScriptInfo() + pytest.raises(NoAppException, obj.load_app) + + monkeypatch.chdir(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps', 'helloworld' + ))) + obj = ScriptInfo() + app = obj.load_app() + assert app.name == 'hello' + + monkeypatch.chdir(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps', 'cliapp' + ))) + obj = ScriptInfo() + app = obj.load_app() + assert app.name == 'testapp' + + +def test_with_appcontext(runner): + """Test of with_appcontext.""" + + @click.command() + @with_appcontext + def testcmd(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda info: Flask("testapp")) + + result = runner.invoke(testcmd, obj=obj) + assert result.exit_code == 0 + assert result.output == 'testapp\n' + + +def test_appgroup(runner): + """Test of with_appcontext.""" + + @click.group(cls=AppGroup) + def cli(): + pass + + @cli.command(with_appcontext=True) + def test(): + click.echo(current_app.name) + + @cli.group() + def subgroup(): + pass + + @subgroup.command(with_appcontext=True) + def test2(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda info: Flask("testappgroup")) + + result = runner.invoke(cli, ['test'], obj=obj) + assert result.exit_code == 0 + assert result.output == 'testappgroup\n' + + result = runner.invoke(cli, ['subgroup', 'test2'], obj=obj) + assert result.exit_code == 0 + assert result.output == 'testappgroup\n' + + +def test_flaskgroup(runner): + """Test FlaskGroup.""" + + def create_app(info): + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(**params): + pass + + @cli.command() + def test(): + click.echo(current_app.name) + + result = runner.invoke(cli, ['test']) + assert result.exit_code == 0 + assert result.output == 'flaskgroup\n' + + +def test_print_exceptions(runner): + """Print the stacktrace if the CLI.""" + + def create_app(info): + raise Exception("oh no") + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(**params): + pass + + result = runner.invoke(cli, ['--help']) + assert result.exit_code == 0 + assert 'Exception: oh no' in result.output + assert 'Traceback' in result.output + + +class TestRoutes: + @pytest.fixture + def invoke(self, runner): + def create_app(info): + app = Flask(__name__) + app.testing = True + + @app.route('/get_post//', methods=['GET', 'POST']) + def yyy_get_post(x, y): + pass + + @app.route('/zzz_post', methods=['POST']) + def aaa_post(): + pass + + return app + + cli = FlaskGroup(create_app=create_app) + return partial(runner.invoke, cli) + + def expect_order(self, order, output): + for expect, line in zip(order, output.splitlines()[2:]): + assert line[:len(expect)] == expect + + def test_simple(self, invoke): + result = invoke(['routes']) + assert result.exit_code == 0 + self.expect_order( + ['aaa_post', 'static', 'yyy_get_post'], + result.output + ) + + def test_sort(self, invoke): + default_output = invoke(['routes']).output + endpoint_output = invoke(['routes', '-s', 'endpoint']).output + assert default_output == endpoint_output + self.expect_order( + ['static', 'yyy_get_post', 'aaa_post'], + invoke(['routes', '-s', 'methods']).output + ) + self.expect_order( + ['yyy_get_post', 'static', 'aaa_post'], + invoke(['routes', '-s', 'rule']).output + ) + self.expect_order( + ['aaa_post', 'yyy_get_post', 'static'], + invoke(['routes', '-s', 'match']).output + ) + + def test_all_methods(self, invoke): + output = invoke(['routes']).output + assert 'GET, HEAD, OPTIONS, POST' not in output + output = invoke(['routes', '--all-methods']).output + assert 'GET, HEAD, OPTIONS, POST' in output + + +need_dotenv = pytest.mark.skipif( + dotenv is None, reason='dotenv is not installed' +) + + +@need_dotenv +def test_load_dotenv(monkeypatch): + for item in ('FOO', 'BAR', 'SPAM'): + monkeypatch._setitem.append((os.environ, item, notset)) + + monkeypatch.setenv('EGGS', '3') + monkeypatch.chdir(os.path.join(test_path, 'cliapp', 'inner1')) + load_dotenv() + assert os.getcwd() == test_path + assert os.environ['FOO'] == 'env' + assert os.environ['BAR'] == 'bar' + assert os.environ['SPAM'] == '1' + assert os.environ['EGGS'] == '3' + + +@need_dotenv +def test_dotenv_path(monkeypatch): + for item in ('FOO', 'BAR', 'EGGS'): + monkeypatch._setitem.append((os.environ, item, notset)) + + cwd = os.getcwd() + load_dotenv(os.path.join(test_path, '.flaskenv')) + assert os.getcwd() == cwd + assert 'FOO' in os.environ + + +def test_dotenv_optional(monkeypatch): + monkeypatch.setattr('flask.cli.dotenv', None) + monkeypatch.chdir(test_path) + load_dotenv() + assert 'FOO' not in os.environ +""" + tests.test_config + ~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2015 by the Flask Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + + +from datetime import timedelta +import os +import textwrap + +import flask +from flask._compat import PY2 +import pytest + + +TEST_KEY = 'foo' +SECRET_KEY = 'config' + + +def common_object_test(app): + assert app.secret_key == 'config' + assert app.config['TEST_KEY'] == 'foo' + assert 'TestConfig' not in app.config + + +def test_config_from_file(): + app = flask.Flask(__name__) + app.config.from_pyfile(__file__.rsplit('.', 1)[0] + '.py') + common_object_test(app) + + +def test_config_from_object(): + app = flask.Flask(__name__) + app.config.from_object(__name__) + common_object_test(app) + + +def test_config_from_json(): + app = flask.Flask(__name__) + current_dir = os.path.dirname(os.path.abspath(__file__)) + app.config.from_json(os.path.join(current_dir, 'static', 'config.json')) + common_object_test(app) + + +def test_config_from_mapping(): + app = flask.Flask(__name__) + app.config.from_mapping({ + 'SECRET_KEY': 'config', + 'TEST_KEY': 'foo' + }) + common_object_test(app) + + app = flask.Flask(__name__) + app.config.from_mapping([ + ('SECRET_KEY', 'config'), + ('TEST_KEY', 'foo') + ]) + common_object_test(app) + + app = flask.Flask(__name__) + app.config.from_mapping( + SECRET_KEY='config', + TEST_KEY='foo' + ) + common_object_test(app) + + app = flask.Flask(__name__) + with pytest.raises(TypeError): + app.config.from_mapping( + {}, {} + ) + + +def test_config_from_class(): + class Base(object): + TEST_KEY = 'foo' + + class Test(Base): + SECRET_KEY = 'config' + + app = flask.Flask(__name__) + app.config.from_object(Test) + common_object_test(app) + + +def test_config_from_envvar(): + env = os.environ + try: + os.environ = {} + app = flask.Flask(__name__) + with pytest.raises(RuntimeError) as e: + app.config.from_envvar('FOO_SETTINGS') + assert "'FOO_SETTINGS' is not set" in str(e.value) + assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + + os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} + assert app.config.from_envvar('FOO_SETTINGS') + common_object_test(app) + finally: + os.environ = env + + +def test_config_from_envvar_missing(): + env = os.environ + try: + os.environ = {'FOO_SETTINGS': 'missing.cfg'} + with pytest.raises(IOError) as e: + app = flask.Flask(__name__) + app.config.from_envvar('FOO_SETTINGS') + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") + assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + finally: + os.environ = env + + +def test_config_missing(): + app = flask.Flask(__name__) + with pytest.raises(IOError) as e: + app.config.from_pyfile('missing.cfg') + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") + assert not app.config.from_pyfile('missing.cfg', silent=True) + + +def test_config_missing_json(): + app = flask.Flask(__name__) + with pytest.raises(IOError) as e: + app.config.from_json('missing.json') + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.json'") + assert not app.config.from_json('missing.json', silent=True) + + +def test_custom_config_class(): + class Config(flask.Config): + pass + + class Flask(flask.Flask): + config_class = Config + app = Flask(__name__) + assert isinstance(app.config, Config) + app.config.from_object(__name__) + common_object_test(app) + + +def test_session_lifetime(): + app = flask.Flask(__name__) + app.config['PERMANENT_SESSION_LIFETIME'] = 42 + assert app.permanent_session_lifetime.seconds == 42 + + +def test_send_file_max_age(): + app = flask.Flask(__name__) + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600 + assert app.send_file_max_age_default.seconds == 3600 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(hours=2) + assert app.send_file_max_age_default.seconds == 7200 + + +def test_get_namespace(): + app = flask.Flask(__name__) + app.config['FOO_OPTION_1'] = 'foo option 1' + app.config['FOO_OPTION_2'] = 'foo option 2' + app.config['BAR_STUFF_1'] = 'bar stuff 1' + app.config['BAR_STUFF_2'] = 'bar stuff 2' + foo_options = app.config.get_namespace('FOO_') + assert 2 == len(foo_options) + assert 'foo option 1' == foo_options['option_1'] + assert 'foo option 2' == foo_options['option_2'] + bar_options = app.config.get_namespace('BAR_', lowercase=False) + assert 2 == len(bar_options) + assert 'bar stuff 1' == bar_options['STUFF_1'] + assert 'bar stuff 2' == bar_options['STUFF_2'] + foo_options = app.config.get_namespace('FOO_', trim_namespace=False) + assert 2 == len(foo_options) + assert 'foo option 1' == foo_options['foo_option_1'] + assert 'foo option 2' == foo_options['foo_option_2'] + bar_options = app.config.get_namespace('BAR_', lowercase=False, trim_namespace=False) + assert 2 == len(bar_options) + assert 'bar stuff 1' == bar_options['BAR_STUFF_1'] + assert 'bar stuff 2' == bar_options['BAR_STUFF_2'] + + +@pytest.mark.parametrize('encoding', ['utf-8', 'iso-8859-15', 'latin-1']) +def test_from_pyfile_weird_encoding(tmpdir, encoding): + f = tmpdir.join('my_config.py') + f.write_binary(textwrap.dedent(u''' + TEST_VALUE = "föö" + '''.format(encoding)).encode(encoding)) + app = flask.Flask(__name__) + app.config.from_pyfile(str(f)) + value = app.config['TEST_VALUE'] + if PY2: + value = value.decode(encoding) + assert value == u'föö' +""" + tests.helpers + ~~~~~~~~~~~~~~~~~~~~~~~ + + Various helpers. + + :copyright: (c) 2015 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +import datetime +import os +import uuid + +import pytest +from werkzeug.datastructures import Range +from werkzeug.exceptions import BadRequest, NotFound +from werkzeug.http import http_date, parse_cache_control_header, \ + parse_options_header + +import flask +from flask._compat import StringIO, text_type +from flask.helpers import get_debug_flag + + +def has_encoding(name): + try: + import codecs + codecs.lookup(name) + return True + except LookupError: + return False + + +class FixedOffset(datetime.tzinfo): + """Fixed offset in hours east from UTC. + + This is a slight adaptation of the ``FixedOffset`` example found in + https://docs.python.org/2.7/library/datetime.html. + """ + + def __init__(self, hours, name): + self.__offset = datetime.timedelta(hours=hours) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return datetime.timedelta() + + +class TestJSON(object): + def test_ignore_cached_json(self, app): + with app.test_request_context('/', method='POST', data='malformed', + content_type='application/json'): + assert flask.request.get_json(silent=True, cache=True) is None + with pytest.raises(BadRequest): + flask.request.get_json(silent=False, cache=False) + + def test_post_empty_json_adds_exception_to_response_content_in_debug(self, app, client): + app.config['DEBUG'] = True + app.config['TRAP_BAD_REQUEST_ERRORS'] = False + + @app.route('/json', methods=['POST']) + def post_json(): + flask.request.get_json() + return None + + rv = client.post('/json', data=None, content_type='application/json') + assert rv.status_code == 400 + assert b'Failed to decode JSON object' in rv.data + + def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self, app, client): + app.config['DEBUG'] = False + app.config['TRAP_BAD_REQUEST_ERRORS'] = False + + @app.route('/json', methods=['POST']) + def post_json(): + flask.request.get_json() + return None + + rv = client.post('/json', data=None, content_type='application/json') + assert rv.status_code == 400 + assert b'Failed to decode JSON object' not in rv.data + + def test_json_bad_requests(self, app, client): + + @app.route('/json', methods=['POST']) + def return_json(): + return flask.jsonify(foo=text_type(flask.request.get_json())) + + rv = client.post('/json', data='malformed', content_type='application/json') + assert rv.status_code == 400 + + def test_json_custom_mimetypes(self, app, client): + + @app.route('/json', methods=['POST']) + def return_json(): + return flask.request.get_json() + + rv = client.post('/json', data='"foo"', content_type='application/x+json') + assert rv.data == b'foo' + + def test_json_body_encoding(self, app, client): + + @app.route('/') + def index(): + return flask.request.get_json() + + resp = client.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), + content_type='application/json; charset=iso-8859-15') + assert resp.data == u'Hällo Wörld'.encode('utf-8') + + @pytest.mark.parametrize('test_value,expected', [(True, '"\\u2603"'), (False, u'"\u2603"')]) + def test_json_as_unicode(self, test_value, expected, app, app_ctx): + + app.config['JSON_AS_ASCII'] = test_value + rv = flask.json.dumps(u'\N{SNOWMAN}') + assert rv == expected + + def test_json_dump_to_file(self, app, app_ctx): + test_data = {'name': 'Flask'} + out = StringIO() + + flask.json.dump(test_data, out) + out.seek(0) + rv = flask.json.load(out) + assert rv == test_data + + @pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None]) + def test_jsonify_basic_types(self, test_value, app, client): + """Test jsonify with basic types.""" + + url = '/jsonify_basic_types' + app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x)) + rv = client.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == test_value + + def test_jsonify_dicts(self, app, client): + """Test jsonify with dicts and kwargs unpacking.""" + d = {'a': 0, 'b': 23, 'c': 3.14, 'd': 't', + 'e': 'Hi', 'f': True, 'g': False, + 'h': ['test list', 10, False], + 'i': {'test': 'dict'}} + + @app.route('/kw') + def return_kwargs(): + return flask.jsonify(**d) + + @app.route('/dict') + def return_dict(): + return flask.jsonify(d) + + for url in '/kw', '/dict': + rv = client.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == d + + def test_jsonify_arrays(self, app, client): + """Test jsonify of lists and args unpacking.""" + l = [ + 0, 42, 3.14, 't', 'hello', True, False, + ['test list', 2, False], + {'test': 'dict'} + ] + + @app.route('/args_unpack') + def return_args_unpack(): + return flask.jsonify(*l) + + @app.route('/array') + def return_array(): + return flask.jsonify(l) + + for url in '/args_unpack', '/array': + rv = client.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == l + + def test_jsonify_date_types(self, app, client): + """Test jsonify with datetime.date and datetime.datetime types.""" + test_dates = ( + datetime.datetime(1973, 3, 11, 6, 30, 45), + datetime.date(1975, 1, 5) + ) + + for i, d in enumerate(test_dates): + url = '/datetest{0}'.format(i) + app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val)) + rv = client.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) + + @pytest.mark.parametrize('tz', (('UTC', 0), ('PST', -8), ('KST', 9))) + def test_jsonify_aware_datetimes(self, tz): + """Test if aware datetime.datetime objects are converted into GMT.""" + tzinfo = FixedOffset(hours=tz[1], name=tz[0]) + dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo) + gmt = FixedOffset(hours=0, name='GMT') + expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"') + assert flask.json.JSONEncoder().encode(dt) == expected + + def test_jsonify_uuid_types(self, app, client): + """Test jsonify with uuid.UUID types""" + + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4) + url = '/uuid_test' + app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) + + rv = client.get(url) + + rv_x = flask.json.loads(rv.data)['x'] + assert rv_x == str(test_uuid) + rv_uuid = uuid.UUID(rv_x) + assert rv_uuid == test_uuid + + def test_json_attr(self, app, client): + + @app.route('/add', methods=['POST']) + def add(): + json = flask.request.get_json() + return text_type(json['a'] + json['b']) + + rv = client.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), + content_type='application/json') + assert rv.data == b'3' + + def test_template_escaping(self, app, req_ctx): + render = flask.render_template_string + rv = flask.json.htmlsafe_dumps('') + assert rv == u'"\\u003c/script\\u003e"' + assert type(rv) == text_type + rv = render('{{ ""|tojson }}') + assert rv == '"\\u003c/script\\u003e"' + rv = render('{{ "<\0/script>"|tojson }}') + assert rv == '"\\u003c\\u0000/script\\u003e"' + rv = render('{{ "