forked from orbit-oss/flask
Merge pull request #3206 from pallets/test-client-json
don't push app context for test client json
This commit is contained in:
commit
3dc84ec460
4 changed files with 95 additions and 39 deletions
|
|
@ -23,10 +23,13 @@ Unreleased
|
|||
handle ``RoutingExcpetion``, which is used internally during
|
||||
routing. This fixes the unexpected behavior that had been introduced
|
||||
in 1.0. (`#2986`_)
|
||||
- Passing the ``json`` argument to ``app.test_client`` does not
|
||||
push/pop an extra app context. (`#2900`_)
|
||||
|
||||
.. _#2766: https://github.com/pallets/flask/issues/2766
|
||||
.. _#2765: https://github.com/pallets/flask/pull/2765
|
||||
.. _#2825: https://github.com/pallets/flask/pull/2825
|
||||
.. _#2900: https://github.com/pallets/flask/issues/2900
|
||||
.. _#2933: https://github.com/pallets/flask/issues/2933
|
||||
.. _#2986: https://github.com/pallets/flask/pull/2986
|
||||
|
||||
|
|
|
|||
|
|
@ -89,33 +89,37 @@ class JSONDecoder(_json.JSONDecoder):
|
|||
"""
|
||||
|
||||
|
||||
def _dump_arg_defaults(kwargs):
|
||||
def _dump_arg_defaults(kwargs, app=None):
|
||||
"""Inject default arguments for dump functions."""
|
||||
if current_app:
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
if app is None:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
bp = app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_encoder if bp and bp.json_encoder
|
||||
else current_app.json_encoder
|
||||
'cls', bp.json_encoder if bp and bp.json_encoder else app.json_encoder
|
||||
)
|
||||
|
||||
if not current_app.config['JSON_AS_ASCII']:
|
||||
if not app.config['JSON_AS_ASCII']:
|
||||
kwargs.setdefault('ensure_ascii', False)
|
||||
|
||||
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
||||
kwargs.setdefault('sort_keys', app.config['JSON_SORT_KEYS'])
|
||||
else:
|
||||
kwargs.setdefault('sort_keys', True)
|
||||
kwargs.setdefault('cls', JSONEncoder)
|
||||
|
||||
|
||||
def _load_arg_defaults(kwargs):
|
||||
def _load_arg_defaults(kwargs, app=None):
|
||||
"""Inject default arguments for load functions."""
|
||||
if current_app:
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
if app is None:
|
||||
app = current_app
|
||||
|
||||
if app:
|
||||
bp = app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_decoder if bp and bp.json_decoder
|
||||
else current_app.json_decoder
|
||||
else app.json_decoder
|
||||
)
|
||||
else:
|
||||
kwargs.setdefault('cls', JSONDecoder)
|
||||
|
|
@ -164,17 +168,28 @@ def detect_encoding(data):
|
|||
return 'utf-8'
|
||||
|
||||
|
||||
def dumps(obj, **kwargs):
|
||||
"""Serialize ``obj`` to a JSON formatted ``str`` by using the application's
|
||||
configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an
|
||||
application on the stack.
|
||||
def dumps(obj, app=None, **kwargs):
|
||||
"""Serialize ``obj`` to a JSON-formatted string. If there is an
|
||||
app context pushed, use the current app's configured encoder
|
||||
(:attr:`~flask.Flask.json_encoder`), or fall back to the default
|
||||
:class:`JSONEncoder`.
|
||||
|
||||
This function can return ``unicode`` strings or ascii-only bytestrings by
|
||||
default which coerce into unicode strings automatically. That behavior by
|
||||
default is controlled by the ``JSON_AS_ASCII`` configuration variable
|
||||
and can be overridden by the simplejson ``ensure_ascii`` parameter.
|
||||
Takes the same arguments as the built-in :func:`json.dumps`, and
|
||||
does some extra configuration based on the application. If the
|
||||
simplejson package is installed, it is preferred.
|
||||
|
||||
:param obj: Object to serialize to JSON.
|
||||
:param app: App instance to use to configure the JSON encoder.
|
||||
Uses ``current_app`` if not given, and falls back to the default
|
||||
encoder when not in an app context.
|
||||
:param kwargs: Extra arguments passed to :func:`json.dumps`.
|
||||
|
||||
.. versionchanged:: 1.0.3
|
||||
|
||||
``app`` can be passed directly, rather than requiring an app
|
||||
context for configuration.
|
||||
"""
|
||||
_dump_arg_defaults(kwargs)
|
||||
_dump_arg_defaults(kwargs, app=app)
|
||||
encoding = kwargs.pop('encoding', None)
|
||||
rv = _json.dumps(obj, **kwargs)
|
||||
if encoding is not None and isinstance(rv, text_type):
|
||||
|
|
@ -182,21 +197,37 @@ def dumps(obj, **kwargs):
|
|||
return rv
|
||||
|
||||
|
||||
def dump(obj, fp, **kwargs):
|
||||
def dump(obj, fp, app=None, **kwargs):
|
||||
"""Like :func:`dumps` but writes into a file object."""
|
||||
_dump_arg_defaults(kwargs)
|
||||
_dump_arg_defaults(kwargs, app=app)
|
||||
encoding = kwargs.pop('encoding', None)
|
||||
if encoding is not None:
|
||||
fp = _wrap_writer_for_text(fp, encoding)
|
||||
_json.dump(obj, fp, **kwargs)
|
||||
|
||||
|
||||
def loads(s, **kwargs):
|
||||
"""Unserialize a JSON object from a string ``s`` by using the application's
|
||||
configured decoder (:attr:`~flask.Flask.json_decoder`) if there is an
|
||||
application on the stack.
|
||||
def loads(s, app=None, **kwargs):
|
||||
"""Deserialize an object from a JSON-formatted string ``s``. If
|
||||
there is an app context pushed, use the current app's configured
|
||||
decoder (:attr:`~flask.Flask.json_decoder`), or fall back to the
|
||||
default :class:`JSONDecoder`.
|
||||
|
||||
Takes the same arguments as the built-in :func:`json.loads`, and
|
||||
does some extra configuration based on the application. If the
|
||||
simplejson package is installed, it is preferred.
|
||||
|
||||
:param s: JSON string to deserialize.
|
||||
:param app: App instance to use to configure the JSON decoder.
|
||||
Uses ``current_app`` if not given, and falls back to the default
|
||||
encoder when not in an app context.
|
||||
:param kwargs: Extra arguments passed to :func:`json.dumps`.
|
||||
|
||||
.. versionchanged:: 1.0.3
|
||||
|
||||
``app`` can be passed directly, rather than requiring an app
|
||||
context for configuration.
|
||||
"""
|
||||
_load_arg_defaults(kwargs)
|
||||
_load_arg_defaults(kwargs, app=app)
|
||||
if isinstance(s, bytes):
|
||||
encoding = kwargs.pop('encoding', None)
|
||||
if encoding is None:
|
||||
|
|
@ -205,10 +236,9 @@ def loads(s, **kwargs):
|
|||
return _json.loads(s, **kwargs)
|
||||
|
||||
|
||||
def load(fp, **kwargs):
|
||||
"""Like :func:`loads` but reads from a file object.
|
||||
"""
|
||||
_load_arg_defaults(kwargs)
|
||||
def load(fp, app=None, **kwargs):
|
||||
"""Like :func:`loads` but reads from a file object."""
|
||||
_load_arg_defaults(kwargs, app=app)
|
||||
if not PY2:
|
||||
fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8')
|
||||
return _json.load(fp, **kwargs)
|
||||
|
|
|
|||
|
|
@ -73,14 +73,10 @@ def make_test_environ_builder(
|
|||
sep = b'?' if isinstance(url.query, bytes) else '?'
|
||||
path += sep + url.query
|
||||
|
||||
# TODO use EnvironBuilder.json_dumps once we require Werkzeug 0.15
|
||||
if 'json' in kwargs:
|
||||
assert 'data' not in kwargs, (
|
||||
"Client cannot provide both 'json' and 'data'."
|
||||
)
|
||||
|
||||
# push a context so flask.json can use app's json attributes
|
||||
with app.app_context():
|
||||
kwargs['data'] = json_dumps(kwargs.pop('json'))
|
||||
assert 'data' not in kwargs, "Client cannot provide both 'json' and 'data'."
|
||||
kwargs['data'] = json_dumps(kwargs.pop('json'), app=app)
|
||||
|
||||
if 'content_type' not in kwargs:
|
||||
kwargs['content_type'] = 'application/json'
|
||||
|
|
|
|||
|
|
@ -14,11 +14,17 @@ import pytest
|
|||
import flask
|
||||
import werkzeug
|
||||
|
||||
from flask import appcontext_popped
|
||||
from flask._compat import text_type
|
||||
from flask.cli import ScriptInfo
|
||||
from flask.json import jsonify
|
||||
from flask.testing import make_test_environ_builder, FlaskCliRunner
|
||||
|
||||
try:
|
||||
import blinker
|
||||
except ImportError:
|
||||
blinker = None
|
||||
|
||||
|
||||
def test_environ_defaults_from_config(app, client):
|
||||
app.config['SERVER_NAME'] = 'example.com:1234'
|
||||
|
|
@ -306,6 +312,27 @@ def test_json_request_and_response(app, client):
|
|||
assert rv.get_json() == json_data
|
||||
|
||||
|
||||
@pytest.mark.skipif(blinker is None, reason="blinker is not installed")
|
||||
def test_client_json_no_app_context(app, client):
|
||||
@app.route("/hello", methods=["POST"])
|
||||
def hello():
|
||||
return "Hello, {}!".format(flask.request.json["name"])
|
||||
|
||||
class Namespace(object):
|
||||
count = 0
|
||||
|
||||
def add(self, app):
|
||||
self.count += 1
|
||||
|
||||
ns = Namespace()
|
||||
|
||||
with appcontext_popped.connected_to(ns.add, app):
|
||||
rv = client.post("/hello", json={"name": "Flask"})
|
||||
|
||||
assert rv.get_data(as_text=True) == "Hello, Flask!"
|
||||
assert ns.count == 1
|
||||
|
||||
|
||||
def test_subdomain():
|
||||
app = flask.Flask(__name__, subdomain_matching=True)
|
||||
app.config['SERVER_NAME'] = 'example.com'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue