forked from orbit-oss/flask
Merge branch 'master' into json-object-hook
This commit is contained in:
commit
ea2e9609bc
42 changed files with 1100 additions and 663 deletions
129
flask/app.py
129
flask/app.py
|
|
@ -10,6 +10,7 @@
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from datetime import timedelta
|
||||
from functools import update_wrapper
|
||||
from itertools import chain
|
||||
|
|
@ -17,7 +18,8 @@ from threading import Lock
|
|||
|
||||
from werkzeug.datastructures import ImmutableDict, Headers
|
||||
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||
InternalServerError, MethodNotAllowed, default_exceptions
|
||||
InternalServerError, MethodNotAllowed, default_exceptions, \
|
||||
BadRequestKeyError
|
||||
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||
|
||||
from . import cli, json
|
||||
|
|
@ -307,7 +309,7 @@ class Flask(_PackageBoundObject):
|
|||
'LOGGER_NAME': None,
|
||||
'LOGGER_HANDLER_POLICY': 'always',
|
||||
'SERVER_NAME': None,
|
||||
'APPLICATION_ROOT': None,
|
||||
'APPLICATION_ROOT': '/',
|
||||
'SESSION_COOKIE_NAME': 'session',
|
||||
'SESSION_COOKIE_DOMAIN': None,
|
||||
'SESSION_COOKIE_PATH': None,
|
||||
|
|
@ -316,7 +318,7 @@ class Flask(_PackageBoundObject):
|
|||
'SESSION_REFRESH_EACH_REQUEST': True,
|
||||
'MAX_CONTENT_LENGTH': None,
|
||||
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
|
||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||
'TRAP_BAD_REQUEST_ERRORS': None,
|
||||
'TRAP_HTTP_EXCEPTIONS': False,
|
||||
'EXPLAIN_TEMPLATE_LOADING': False,
|
||||
'PREFERRED_URL_SCHEME': 'http',
|
||||
|
|
@ -925,8 +927,17 @@ class Flask(_PackageBoundObject):
|
|||
: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):
|
||||
|
|
@ -934,19 +945,37 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
|
|
@ -1137,7 +1166,9 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
@setupmethod
|
||||
def errorhandler(self, code_or_exception):
|
||||
"""A decorator that is used to register a function given an
|
||||
"""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)
|
||||
|
|
@ -1150,21 +1181,6 @@ class Flask(_PackageBoundObject):
|
|||
def special_exception_handler(error):
|
||||
return 'Database connection failed', 500
|
||||
|
||||
You can also register a function as error handler without using
|
||||
the :meth:`errorhandler` decorator. The following example is
|
||||
equivalent to the one above::
|
||||
|
||||
def page_not_found(error):
|
||||
return 'This page does not exist', 404
|
||||
app.error_handler_spec[None][404] = page_not_found
|
||||
|
||||
Setting error handlers via assignments to :attr:`error_handler_spec`
|
||||
however is discouraged as it requires fiddling with nested dictionaries
|
||||
and the special case for arbitrary exception types.
|
||||
|
||||
The first ``None`` refers to the active blueprint. If the error
|
||||
handler should be application wide ``None`` shall be used.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
Use :meth:`register_error_handler` instead of modifying
|
||||
:attr:`error_handler_spec` directly, for application wide error
|
||||
|
|
@ -1183,6 +1199,7 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
|
|
@ -1201,11 +1218,18 @@ class Flask(_PackageBoundObject):
|
|||
"""
|
||||
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
||||
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))
|
||||
'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)
|
||||
)
|
||||
|
||||
exc_class, code = self._get_exc_class_and_code(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
|
||||
|
|
@ -1310,7 +1334,7 @@ class Flask(_PackageBoundObject):
|
|||
@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.
|
||||
|
||||
|
|
@ -1438,12 +1462,12 @@ class Flask(_PackageBoundObject):
|
|||
"""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.
|
||||
"""
|
||||
|
|
@ -1515,12 +1539,20 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
if self.config['TRAP_BAD_REQUEST_ERRORS']:
|
||||
|
||||
trap_bad_request = self.config['TRAP_BAD_REQUEST_ERRORS']
|
||||
|
||||
# if unset, trap based on debug mode
|
||||
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):
|
||||
|
|
@ -1531,16 +1563,30 @@ class Flask(_PackageBoundObject):
|
|||
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
|
||||
|
||||
# ensure not to trash sys.exc_info() at that point in case someone
|
||||
# wants the traceback preserved in handle_http_exception. Of course
|
||||
# we cannot prevent users from trashing it themselves in a custom
|
||||
# trap_http_exception method so that's their fault then.
|
||||
|
||||
# MultiDict passes the key to the exception, but that's ignored
|
||||
# when generating the response message. Set an informative
|
||||
# description for key errors in debug mode or when trapping errors.
|
||||
if (
|
||||
(self.debug or self.config['TRAP_BAD_REQUEST_ERRORS'])
|
||||
and isinstance(e, BadRequestKeyError)
|
||||
# only set it if it's still the default description
|
||||
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)
|
||||
|
||||
|
|
@ -1732,10 +1778,10 @@ class Flask(_PackageBoundObject):
|
|||
``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
|
||||
|
|
@ -1744,13 +1790,13 @@ class Flask(_PackageBoundObject):
|
|||
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.
|
||||
|
|
@ -1845,7 +1891,7 @@ class Flask(_PackageBoundObject):
|
|||
if self.config['SERVER_NAME'] is not None:
|
||||
return self.url_map.bind(
|
||||
self.config['SERVER_NAME'],
|
||||
script_name=self.config['APPLICATION_ROOT'] or '/',
|
||||
script_name=self.config['APPLICATION_ROOT'],
|
||||
url_scheme=self.config['PREFERRED_URL_SCHEME'])
|
||||
|
||||
def inject_url_defaults(self, endpoint, values):
|
||||
|
|
@ -1887,7 +1933,7 @@ class Flask(_PackageBoundObject):
|
|||
: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.
|
||||
|
|
@ -1932,7 +1978,7 @@ class Flask(_PackageBoundObject):
|
|||
for handler in funcs:
|
||||
response = handler(response)
|
||||
if not self.session_interface.is_null_session(ctx.session):
|
||||
self.save_session(ctx.session, response)
|
||||
self.session_interface.save_session(self, ctx.session, response)
|
||||
return response
|
||||
|
||||
def do_teardown_request(self, exc=_sentinel):
|
||||
|
|
@ -2017,10 +2063,19 @@ class Flask(_PackageBoundObject):
|
|||
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).
|
||||
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:
|
||||
|
|
|
|||
93
flask/cli.py
93
flask/cli.py
|
|
@ -9,7 +9,10 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from functools import update_wrapper
|
||||
|
|
@ -55,20 +58,20 @@ def find_best_app(script_info, module):
|
|||
' one.'.format(module=module.__name__)
|
||||
)
|
||||
|
||||
# Search for app factory callables.
|
||||
# Search for app factory functions.
|
||||
for attr_name in ('create_app', 'make_app'):
|
||||
app_factory = getattr(module, attr_name, None)
|
||||
|
||||
if callable(app_factory):
|
||||
if inspect.isfunction(app_factory):
|
||||
try:
|
||||
app = call_factory(app_factory, script_info)
|
||||
if isinstance(app, Flask):
|
||||
return app
|
||||
except TypeError:
|
||||
raise NoAppException(
|
||||
'Auto-detected "{callable}()" in module "{module}", but '
|
||||
'Auto-detected "{function}()" in module "{module}", but '
|
||||
'could not call it without specifying arguments.'.format(
|
||||
callable=attr_name, module=module.__name__
|
||||
function=attr_name, module=module.__name__
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -79,18 +82,69 @@ def find_best_app(script_info, module):
|
|||
)
|
||||
|
||||
|
||||
def call_factory(func, script_info):
|
||||
"""Checks if the given app factory function has an argument named
|
||||
``script_info`` or just a single argument and calls the function passing
|
||||
``script_info`` if so. Otherwise, calls the function without any arguments
|
||||
and returns the result.
|
||||
def call_factory(app_factory, script_info, arguments=()):
|
||||
"""Takes an app factory, a ``script_info` object and optionally a tuple
|
||||
of arguments. Checks for the existence of a script_info argument and calls
|
||||
the app_factory depending on that and the arguments provided.
|
||||
"""
|
||||
arguments = getargspec(func).args
|
||||
if 'script_info' in arguments:
|
||||
return func(script_info=script_info)
|
||||
elif len(arguments) == 1:
|
||||
return func(script_info)
|
||||
return func()
|
||||
args_spec = getargspec(app_factory)
|
||||
arg_names = args_spec.args
|
||||
arg_defaults = args_spec.defaults
|
||||
|
||||
if 'script_info' in arg_names:
|
||||
return app_factory(*arguments, script_info=script_info)
|
||||
elif arguments:
|
||||
return app_factory(*arguments)
|
||||
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
||||
return app_factory(script_info)
|
||||
return app_factory()
|
||||
|
||||
|
||||
def find_app_by_string(string, script_info, module):
|
||||
"""Checks if the given string is a variable name or a function. If it is
|
||||
a function, it checks for specified arguments and whether it takes
|
||||
a ``script_info`` argument and calls the function with the appropriate
|
||||
arguments."""
|
||||
from . import Flask
|
||||
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$'
|
||||
match = re.match(function_regex, string)
|
||||
if match:
|
||||
name, args = match.groups()
|
||||
try:
|
||||
if args is not None:
|
||||
args = args.rstrip(' ,')
|
||||
if args:
|
||||
args = ast.literal_eval(
|
||||
"({args}, )".format(args=args))
|
||||
else:
|
||||
args = ()
|
||||
app_factory = getattr(module, name, None)
|
||||
app = call_factory(app_factory, script_info, args)
|
||||
else:
|
||||
attr = getattr(module, name, None)
|
||||
if inspect.isfunction(attr):
|
||||
app = call_factory(attr, script_info)
|
||||
else:
|
||||
app = attr
|
||||
|
||||
if isinstance(app, Flask):
|
||||
return app
|
||||
else:
|
||||
raise RuntimeError('Failed to find application in module '
|
||||
'"{name}"'.format(name=module))
|
||||
except TypeError as e:
|
||||
new_error = NoAppException(
|
||||
'{e}\nThe app factory "{factory}" in module "{module}" could'
|
||||
' not be called with the specified arguments (and a'
|
||||
' script_info argument automatically added if applicable).'
|
||||
' Did you make sure to use the right number of arguments as'
|
||||
' well as not using keyword arguments or'
|
||||
' non-literals?'.format(e=e, factory=string, module=module))
|
||||
reraise(NoAppException, new_error, sys.exc_info()[2])
|
||||
else:
|
||||
raise NoAppException(
|
||||
'The provided string "{string}" is not a valid variable name'
|
||||
'or function expression.'.format(string=string))
|
||||
|
||||
|
||||
def prepare_exec_for_file(filename):
|
||||
|
|
@ -148,14 +202,9 @@ def locate_app(script_info, app_id):
|
|||
|
||||
mod = sys.modules[module]
|
||||
if app_obj is None:
|
||||
app = find_best_app(script_info, mod)
|
||||
return find_best_app(script_info, mod)
|
||||
else:
|
||||
app = getattr(mod, app_obj, None)
|
||||
if app is None:
|
||||
raise RuntimeError('Failed to find application in module "%s"'
|
||||
% module)
|
||||
|
||||
return app
|
||||
return find_app_by_string(app_obj, script_info, mod)
|
||||
|
||||
|
||||
def find_default_import_path():
|
||||
|
|
|
|||
|
|
@ -329,9 +329,11 @@ class RequestContext(object):
|
|||
# available. This allows a custom open_session method to use the
|
||||
# request context (e.g. code that access database information
|
||||
# stored on `g` instead of the appcontext).
|
||||
self.session = self.app.open_session(self.request)
|
||||
session_interface = self.app.session_interface
|
||||
self.session = session_interface.open_session(self.app, self.request)
|
||||
|
||||
if self.session is None:
|
||||
self.session = self.app.make_null_session()
|
||||
self.session = session_interface.make_null_session(self.app)
|
||||
|
||||
def pop(self, exc=_sentinel):
|
||||
"""Pops the request context and unbinds it by doing that. This will
|
||||
|
|
|
|||
|
|
@ -1004,17 +1004,3 @@ def is_ip(value):
|
|||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def patch_vary_header(response, value):
|
||||
"""Add a value to the ``Vary`` header if it is not already present."""
|
||||
|
||||
header = response.headers.get('Vary', '')
|
||||
headers = [h for h in (h.strip() for h in header.split(',')) if h]
|
||||
lower_value = value.lower()
|
||||
|
||||
if not any(h.lower() == lower_value for h in headers):
|
||||
headers.append(value)
|
||||
|
||||
updated_header = ', '.join(headers)
|
||||
response.headers['Vary'] = updated_header
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from itsdangerous import BadSignature, URLSafeTimedSerializer
|
|||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.http import http_date, parse_date
|
||||
|
||||
from flask.helpers import patch_vary_header
|
||||
from . import Markup, json
|
||||
from ._compat import iteritems, text_type
|
||||
from .helpers import is_ip, total_seconds
|
||||
|
|
@ -347,8 +346,8 @@ class SessionInterface(object):
|
|||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||
uses ``/`` if it's ``None``.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_PATH'] or \
|
||||
app.config['APPLICATION_ROOT'] or '/'
|
||||
return app.config['SESSION_COOKIE_PATH'] \
|
||||
or app.config['APPLICATION_ROOT']
|
||||
|
||||
def get_cookie_httponly(self, app):
|
||||
"""Returns True if the session cookie should be httponly. This
|
||||
|
|
@ -466,7 +465,7 @@ class SecureCookieSessionInterface(SessionInterface):
|
|||
|
||||
# Add a "Vary: Cookie" header if the session was accessed at all.
|
||||
if session.accessed:
|
||||
patch_vary_header(response, 'Cookie')
|
||||
response.vary.add('Cookie')
|
||||
|
||||
if not self.should_set_cookie(app, session):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -21,19 +21,37 @@ except ImportError:
|
|||
from urlparse import urlsplit as url_parse
|
||||
|
||||
|
||||
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
|
||||
def make_test_environ_builder(
|
||||
app, path='/', base_url=None, subdomain=None, url_scheme=None,
|
||||
*args, **kwargs
|
||||
):
|
||||
"""Creates a new test builder with some application defaults thrown in."""
|
||||
http_host = app.config.get('SERVER_NAME')
|
||||
app_root = app.config.get('APPLICATION_ROOT')
|
||||
|
||||
assert (
|
||||
not (base_url or subdomain or url_scheme)
|
||||
or (base_url is not None) != bool(subdomain or url_scheme)
|
||||
), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
|
||||
|
||||
if base_url is None:
|
||||
http_host = app.config.get('SERVER_NAME') or 'localhost'
|
||||
app_root = app.config['APPLICATION_ROOT']
|
||||
|
||||
if subdomain:
|
||||
http_host = '{0}.{1}'.format(subdomain, http_host)
|
||||
|
||||
if url_scheme is None:
|
||||
url_scheme = app.config['PREFERRED_URL_SCHEME']
|
||||
|
||||
url = url_parse(path)
|
||||
base_url = 'http://%s/' % (url.netloc or http_host or 'localhost')
|
||||
if app_root:
|
||||
base_url += app_root.lstrip('/')
|
||||
if url.netloc:
|
||||
path = url.path
|
||||
if url.query:
|
||||
path += '?' + url.query
|
||||
base_url = '{0}://{1}/{2}'.format(
|
||||
url_scheme, url.netloc or http_host, app_root.lstrip('/')
|
||||
)
|
||||
path = url.path
|
||||
|
||||
if url.query:
|
||||
sep = b'?' if isinstance(url.query, bytes) else '?'
|
||||
path += sep + url.query
|
||||
|
||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -87,7 +105,8 @@ class FlaskClient(Client):
|
|||
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||
outer_reqctx = _request_ctx_stack.top
|
||||
with app.test_request_context(*args, **kwargs) as c:
|
||||
sess = app.open_session(c.request)
|
||||
session_interface = app.session_interface
|
||||
sess = session_interface.open_session(app, c.request)
|
||||
if sess is None:
|
||||
raise RuntimeError('Session backend did not open a session. '
|
||||
'Check the configuration')
|
||||
|
|
@ -106,8 +125,8 @@ class FlaskClient(Client):
|
|||
_request_ctx_stack.pop()
|
||||
|
||||
resp = app.response_class()
|
||||
if not app.session_interface.is_null_session(sess):
|
||||
app.save_session(sess, resp)
|
||||
if not session_interface.is_null_session(sess):
|
||||
session_interface.save_session(app, sess, resp)
|
||||
headers = resp.get_wsgi_headers(c.request.environ)
|
||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue