diff --git a/flask/wrappers.py b/flask/wrappers.py index fcf732e3..bd6aa0f3 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -25,15 +25,88 @@ def _get_data(req, cache): return req.data -def _is_mimetype_json(mimetype): - if mimetype == 'application/json': - return True - if mimetype.startswith('application/') and mimetype.endswith('+json'): - return True - return False +class JSONMixin(object): + """Mixin for both request and response classes to provide JSON parsing + capabilities. + .. versionadded:: 0.12 + """ + + @property + def is_json(self): + """Indicates if this request/response is JSON or not. By default it + is considered to include JSON data if the mimetype is + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + if mt == 'application/json': + return True + if mt.startswith('application/') and mt.endswith('+json'): + return True + return False + + @property + def json(self): + """If the mimetype is :mimetype:`application/json` this will contain the + parsed JSON data. Otherwise this will be ``None``. + + The :meth:`get_json` method should be used instead. + """ + from warnings import warn + warn(DeprecationWarning('json is deprecated. ' + 'Use get_json() instead.'), stacklevel=2) + return self.get_json() + + def get_json(self, force=False, silent=False, cache=True): + """Parses the incoming JSON request data and returns it. By default + this function will return ``None`` if the mimetype is not + :mimetype:`application/json` but this can be overridden by the + ``force`` parameter. If parsing fails the + :meth:`on_json_loading_failed` method on the request object will be + invoked. + + :param force: if set to ``True`` the mimetype is ignored. + :param silent: if set to ``True`` this method will fail silently + and return ``None``. + :param cache: if set to ``True`` the parsed JSON data is remembered + on the request. + """ + rv = getattr(self, '_cached_json', _missing) + if rv is not _missing: + return rv + + if not (force or self.is_json): + return None + + # We accept a request charset against the specification as certain + # clients have been using this in the past. For responses, we assume + # that if the response charset was set explicitly then the data had + # been encoded correctly as well. + charset = self.mimetype_params.get('charset') + try: + data = _get_data(self, cache) + if charset is not None: + rv = json.loads(data, encoding=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 :meth:`get_json` when an error occurred. The + default implementation just raises a :class:`BadRequest` exception. + """ + raise BadRequest() -class Request(RequestBase): + +class Request(RequestBase, JSONMixin): """The request object used by default in Flask. Remembers the matched endpoint and view arguments. @@ -103,69 +176,6 @@ class Request(RequestBase): if self.url_rule and '.' in self.url_rule.endpoint: return self.url_rule.endpoint.rsplit('.', 1)[0] - @property - def json(self): - """If the mimetype is :mimetype:`application/json` this will contain the - parsed JSON data. Otherwise this will be ``None``. - - The :meth:`get_json` method should be used instead. - """ - from warnings import warn - warn(DeprecationWarning('json is deprecated. ' - 'Use get_json() instead.'), stacklevel=2) - return self.get_json() - - @property - def is_json(self): - """Indicates if this request is JSON or not. By default a request - is considered to include JSON data if the mimetype is - :mimetype:`application/json` or :mimetype:`application/*+json`. - - .. versionadded:: 0.11 - """ - return _is_mimetype_json(self.mimetype) - - def get_json(self, force=False, silent=False, cache=True): - """Parses the incoming JSON request data and returns it. By default - this function will return ``None`` if the mimetype is not - :mimetype:`application/json` but this can be overridden by the - ``force`` parameter. If parsing fails the - :meth:`on_json_loading_failed` method on the request object will be - invoked. - - :param force: if set to ``True`` the mimetype is ignored. - :param silent: if set to ``True`` this method will fail silently - and return ``None``. - :param cache: if set to ``True`` the parsed JSON data is remembered - on the request. - """ - rv = getattr(self, '_cached_json', _missing) - if rv is not _missing: - return rv - - if not (force or self.is_json): - 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 :meth:`get_json` when an error occurred. The @@ -195,7 +205,7 @@ class Request(RequestBase): attach_enctype_error_multidict(self) -class Response(ResponseBase): +class Response(ResponseBase, JSONMixin): """The response object that is used by default in Flask. Works like the response object from Werkzeug but is set to have an HTML mimetype by default. Quite often you don't have to create this object yourself because @@ -205,69 +215,3 @@ class Response(ResponseBase): set :attr:`~flask.Flask.response_class` to your subclass. """ default_mimetype = 'text/html' - - @property - def json(self): - """If the mimetype is :mimetype:`application/json` this will contain the - parsed JSON data. Otherwise this will be ``None``. - - The :meth:`get_json` method should be used instead. - - .. versionadded:: 1.0 - """ - from warnings import warn - warn(DeprecationWarning('json is deprecated. ' - 'Use get_json() instead.'), stacklevel=2) - return self.get_json() - - @property - def is_json(self): - """Indicates if this response is JSON or not. By default a response - is considered to include JSON data if the mimetype is - :mimetype:`application/json` or :mimetype:`application/*+json`. - - .. versionadded:: 1.0 - """ - return _is_mimetype_json(self.mimetype) - - 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 :mimetype:`application/json` - but this can be overridden by the `force` parameter. - - :param force: if set to ``True`` the mimetype is ignored. - :param silent: if set to ``True`` this method will fail silently - and return ``None``. - :param cache: if set to ``True`` the parsed JSON data is remembered - on the request. - - .. versionadded:: 1.0 - """ - rv = getattr(self, '_cached_json', _missing) - if rv is not _missing: - return rv - - if not (force or self.is_json): - 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