Merge branch '1.0.x'

This commit is contained in:
David Lord 2019-05-17 11:13:08 -07:00
commit 05a4e15ee4
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
7 changed files with 105 additions and 49 deletions

View File

@ -30,7 +30,7 @@ Unreleased
Version 1.0.3 Version 1.0.3
------------- -------------
Unreleased Released 2019-05-17
- :func:`send_file` encodes filenames as ASCII instead of Latin-1 - :func:`send_file` encodes filenames as ASCII instead of Latin-1
(ISO-8859-1). This fixes compatibility with Gunicorn, which is (ISO-8859-1). This fixes compatibility with Gunicorn, which is
@ -46,10 +46,13 @@ Unreleased
handle ``RoutingException``, which is used internally during handle ``RoutingException``, which is used internally during
routing. This fixes the unexpected behavior that had been introduced routing. This fixes the unexpected behavior that had been introduced
in 1.0. (`#2986`_) 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 .. _#2766: https://github.com/pallets/flask/issues/2766
.. _#2765: https://github.com/pallets/flask/pull/2765 .. _#2765: https://github.com/pallets/flask/pull/2765
.. _#2825: https://github.com/pallets/flask/pull/2825 .. _#2825: https://github.com/pallets/flask/pull/2825
.. _#2900: https://github.com/pallets/flask/issues/2900
.. _#2933: https://github.com/pallets/flask/issues/2933 .. _#2933: https://github.com/pallets/flask/issues/2933
.. _#2986: https://github.com/pallets/flask/pull/2986 .. _#2986: https://github.com/pallets/flask/pull/2986
@ -57,7 +60,7 @@ Unreleased
Version 1.0.2 Version 1.0.2
------------- -------------
Released on May 2nd 2018 Released 2018-05-02
- Fix more backwards compatibility issues with merging slashes between - Fix more backwards compatibility issues with merging slashes between
a blueprint prefix and route. (`#2748`_) a blueprint prefix and route. (`#2748`_)
@ -71,7 +74,7 @@ Released on May 2nd 2018
Version 1.0.1 Version 1.0.1
------------- -------------
Released on April 29th 2018 Released 2018-04-29
- Fix registering partials (with no ``__name__``) as view functions. - Fix registering partials (with no ``__name__``) as view functions.
(`#2730`_) (`#2730`_)
@ -97,7 +100,7 @@ Released on April 29th 2018
Version 1.0 Version 1.0
----------- -----------
Released on April 26th 2018 Released 2018-04-26
- **Python 2.6 and 3.3 are no longer supported.** (`pallets/meta#24`_) - **Python 2.6 and 3.3 are no longer supported.** (`pallets/meta#24`_)
- Bump minimum dependency versions to the latest stable versions: - Bump minimum dependency versions to the latest stable versions:

View File

@ -357,14 +357,15 @@ This however does not make it possible to also modify the session or to
access the session before a request was fired. Starting with Flask 0.8 we access the session before a request was fired. Starting with Flask 0.8 we
provide a so called “session transaction” which simulates the appropriate provide a so called “session transaction” which simulates the appropriate
calls to open a session in the context of the test client and to modify calls to open a session in the context of the test client and to modify
it. At the end of the transaction the session is stored. This works it. At the end of the transaction the session is stored and ready to be
independently of the session backend used:: used by the test client. This works independently of the session backend used::
with app.test_client() as c: with app.test_client() as c:
with c.session_transaction() as sess: with c.session_transaction() as sess:
sess['a_key'] = 'a value' sess['a_key'] = 'a value'
# once this is reached the session was stored # once this is reached the session was stored and ready to be used by the client
c.get(...)
Note that in this case you have to use the ``sess`` object instead of the Note that in this case you have to use the ``sess`` object instead of the
:data:`flask.session` proxy. The object however itself will provide the :data:`flask.session` proxy. The object however itself will provide the

View File

@ -10,7 +10,6 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from functools import update_wrapper from functools import update_wrapper
from werkzeug.urls import url_join
from .helpers import _PackageBoundObject, _endpoint_from_view_func from .helpers import _PackageBoundObject, _endpoint_from_view_func

View File

@ -650,11 +650,9 @@ def show_server_banner(env, debug, app_import_path, eager_loading):
if env == "production": if env == "production":
click.secho( click.secho(
" WARNING: Do not use the development server in a production" ' WARNING: This is a development server. '
" environment.", 'Do not use it in a production deployment.', fg='red')
fg="red", click.secho(' Use a production WSGI server instead.', dim=True)
)
click.secho(" Use a production WSGI server instead.", dim=True)
if debug is not None: if debug is not None:
click.echo(" * Debug mode: {0}".format("on" if debug else "off")) click.echo(" * Debug mode: {0}".format("on" if debug else "off"))

View File

@ -97,31 +97,35 @@ class JSONDecoder(_json.JSONDecoder):
""" """
def _dump_arg_defaults(kwargs): def _dump_arg_defaults(kwargs, app=None):
"""Inject default arguments for dump functions.""" """Inject default arguments for dump functions."""
if current_app: if app is None:
bp = current_app.blueprints.get(request.blueprint) if request else None app = current_app
if app:
bp = app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault( kwargs.setdefault(
"cls", "cls", bp.json_encoder if bp and bp.json_encoder else app.json_encoder
bp.json_encoder if bp and bp.json_encoder else current_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("ensure_ascii", False)
kwargs.setdefault("sort_keys", current_app.config["JSON_SORT_KEYS"]) kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
else: else:
kwargs.setdefault("sort_keys", True) kwargs.setdefault("sort_keys", True)
kwargs.setdefault("cls", JSONEncoder) kwargs.setdefault("cls", JSONEncoder)
def _load_arg_defaults(kwargs): def _load_arg_defaults(kwargs, app=None):
"""Inject default arguments for load functions.""" """Inject default arguments for load functions."""
if current_app: if app is None:
bp = current_app.blueprints.get(request.blueprint) if request else None app = current_app
if app:
bp = app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault( kwargs.setdefault(
"cls", "cls", bp.json_decoder if bp and bp.json_decoder else app.json_decoder
bp.json_decoder if bp and bp.json_decoder else current_app.json_decoder,
) )
else: else:
kwargs.setdefault("cls", JSONDecoder) kwargs.setdefault("cls", JSONDecoder)
@ -170,17 +174,28 @@ def detect_encoding(data):
return "utf-8" return "utf-8"
def dumps(obj, **kwargs): def dumps(obj, app=None, **kwargs):
"""Serialize ``obj`` to a JSON formatted ``str`` by using the application's """Serialize ``obj`` to a JSON-formatted string. If there is an
configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an app context pushed, use the current app's configured encoder
application on the stack. (:attr:`~flask.Flask.json_encoder`), or fall back to the default
:class:`JSONEncoder`.
This function can return ``unicode`` strings or ascii-only bytestrings by Takes the same arguments as the built-in :func:`json.dumps`, and
default which coerce into unicode strings automatically. That behavior by does some extra configuration based on the application. If the
default is controlled by the ``JSON_AS_ASCII`` configuration variable simplejson package is installed, it is preferred.
and can be overridden by the simplejson ``ensure_ascii`` parameter.
: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) encoding = kwargs.pop("encoding", None)
rv = _json.dumps(obj, **kwargs) rv = _json.dumps(obj, **kwargs)
if encoding is not None and isinstance(rv, text_type): if encoding is not None and isinstance(rv, text_type):
@ -188,21 +203,37 @@ def dumps(obj, **kwargs):
return rv return rv
def dump(obj, fp, **kwargs): def dump(obj, fp, app=None, **kwargs):
"""Like :func:`dumps` but writes into a file object.""" """Like :func:`dumps` but writes into a file object."""
_dump_arg_defaults(kwargs) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
if encoding is not None: if encoding is not None:
fp = _wrap_writer_for_text(fp, encoding) fp = _wrap_writer_for_text(fp, encoding)
_json.dump(obj, fp, **kwargs) _json.dump(obj, fp, **kwargs)
def loads(s, **kwargs): def loads(s, app=None, **kwargs):
"""Unserialize a JSON object from a string ``s`` by using the application's """Deserialize an object from a JSON-formatted string ``s``. If
configured decoder (:attr:`~flask.Flask.json_decoder`) if there is an there is an app context pushed, use the current app's configured
application on the stack. 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): if isinstance(s, bytes):
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
if encoding is None: if encoding is None:
@ -211,10 +242,9 @@ def loads(s, **kwargs):
return _json.loads(s, **kwargs) return _json.loads(s, **kwargs)
def load(fp, **kwargs): def load(fp, app=None, **kwargs):
"""Like :func:`loads` but reads from a file object. """Like :func:`loads` but reads from a file object."""
""" _load_arg_defaults(kwargs, app=app)
_load_arg_defaults(kwargs)
if not PY2: if not PY2:
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8") fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
return _json.load(fp, **kwargs) return _json.load(fp, **kwargs)

View File

@ -71,12 +71,10 @@ def make_test_environ_builder(
sep = b"?" if isinstance(url.query, bytes) else "?" sep = b"?" if isinstance(url.query, bytes) else "?"
path += sep + url.query path += sep + url.query
# TODO use EnvironBuilder.json_dumps once we require Werkzeug 0.15
if "json" in kwargs: if "json" in kwargs:
assert "data" not in kwargs, "Client cannot provide both 'json' and 'data'." assert "data" not in kwargs, "Client cannot provide both 'json' and 'data'."
kwargs["data"] = json_dumps(kwargs.pop("json"), app=app)
# push a context so flask.json can use app's json attributes
with app.app_context():
kwargs["data"] = json_dumps(kwargs.pop("json"))
if "content_type" not in kwargs: if "content_type" not in kwargs:
kwargs["content_type"] = "application/json" kwargs["content_type"] = "application/json"

View File

@ -14,11 +14,17 @@ import pytest
import flask import flask
import werkzeug import werkzeug
from flask import appcontext_popped
from flask._compat import text_type from flask._compat import text_type
from flask.cli import ScriptInfo from flask.cli import ScriptInfo
from flask.json import jsonify from flask.json import jsonify
from flask.testing import make_test_environ_builder, FlaskCliRunner from flask.testing import make_test_environ_builder, FlaskCliRunner
try:
import blinker
except ImportError:
blinker = None
def test_environ_defaults_from_config(app, client): def test_environ_defaults_from_config(app, client):
app.config["SERVER_NAME"] = "example.com:1234" 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 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(): def test_subdomain():
app = flask.Flask(__name__, subdomain_matching=True) app = flask.Flask(__name__, subdomain_matching=True)
app.config["SERVER_NAME"] = "example.com" app.config["SERVER_NAME"] = "example.com"