diff --git a/CHANGES b/CHANGES index 16f00a2e..33b34d1c 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,8 @@ Release date to be decided. - Added `appcontext_pushed` and `appcontext_popped` signals. - The builtin run method now takes the ``SERVER_NAME`` into account when picking the default port to run on. +- Added `flask.request.get_json()` as a replacement for the old + `flask.request.json` property. Version 0.9 ----------- diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 3736e356..66173061 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -33,7 +33,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return flask.jsonify(foo=text_type(flask.request.json)) + return flask.jsonify(foo=text_type(flask.request.get_json())) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) @@ -43,7 +43,7 @@ class JSONTestCase(FlaskTestCase): app.testing = True @app.route('/') def index(): - return flask.request.json + return flask.request.get_json() c = app.test_client() resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), @@ -82,7 +82,8 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) def add(): - return text_type(flask.request.json['a'] + flask.request.json['b']) + json = flask.request.get_json() + return text_type(json['a'] + json['b']) c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') @@ -127,7 +128,7 @@ class JSONTestCase(FlaskTestCase): app.json_decoder = MyDecoder @app.route('/', methods=['POST']) def index(): - return flask.json.dumps(flask.request.json['x']) + return flask.json.dumps(flask.request.get_json()['x']) c = app.test_client() rv = c.post('/', data=flask.json.dumps({ 'x': {'_foo': 42} diff --git a/flask/wrappers.py b/flask/wrappers.py index d70e7022..1a17824a 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -10,7 +10,6 @@ """ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase -from werkzeug.utils import cached_property from werkzeug.exceptions import BadRequest from .debughelpers import attach_enctype_error_multidict @@ -18,6 +17,16 @@ from . import json from .globals import _request_ctx_stack +_missing = object() + + +def _get_data(req, cache): + getter = getattr(req, 'get_data', None) + if getter is not None: + return getter(cache=cache) + return req.data + + class Request(RequestBase): """The request object used by default in Flask. Remembers the matched endpoint and view arguments. @@ -88,24 +97,60 @@ class Request(RequestBase): if self.url_rule and '.' in self.url_rule.endpoint: return self.url_rule.endpoint.rsplit('.', 1)[0] - @cached_property + @property def json(self): """If the mimetype is `application/json` this will contain the parsed JSON data. Otherwise this will be `None`. + + The :meth:`get_json` method should be used instead. + """ + # XXX: deprecate property + return self.get_json() + + def get_json(self, force=False, silent=False, cache=True): + """Parses the incoming JSON request data and returns it. If + parsing fails the :meth:`on_json_loading_failed` method on the + request object will be invoked. By default this function will + only load the json data if the mimetype is ``application/json`` + but this can be overriden by the `force` parameter. + + :param force: if set to `True` the mimetype is ignored. + :param silent: if set to `False` this method will fail silently + and return `False`. + :param cache: if set to `True` the parsed JSON data is remembered + on the request. """ - if self.mimetype == 'application/json': - request_charset = self.mimetype_params.get('charset') - try: - if request_charset is not None: - return json.loads(self.data, encoding=request_charset) - return json.loads(self.data) - except ValueError as e: - return self.on_json_loading_failed(e) + rv = getattr(self, '_cached_json', _missing) + if rv is not _missing: + return rv + + if self.mimetype != 'application/json' and not force: + return None + + # We accept a request charset against the specification as + # certain clients have been using this in the past. This + # fits our general approach of being nice in what we accept + # and strict in what we send out. + request_charset = self.mimetype_params.get('charset') + try: + data = _get_data(self, cache) + if request_charset is not None: + rv = json.loads(data, encoding=request_charset) + else: + rv = json.loads(data) + except ValueError as e: + if silent: + rv = None + else: + rv = self.on_json_loading_failed(e) + if cache: + self._cached_json = rv + return rv def on_json_loading_failed(self, e): """Called if decoding of the JSON data failed. The return value of - this method is used by :attr:`json` when an error occurred. The default - implementation just raises a :class:`BadRequest` exception. + this method is used by :meth:`get_json` when an error occurred. The + default implementation just raises a :class:`BadRequest` exception. .. versionchanged:: 0.10 Removed buggy previous behavior of generating a random JSON