Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #220: Adds a default JSONEncoder implementation to Flask.json_encoder_class #471

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a7d3059
Fix #220: adds a default JSONEncoder implementation to Flask.json_enc…
jfinkels Apr 1, 2012
95e0895
Merge branch 'master' into fix-issue-220
jfinkels Apr 9, 2012
cf38eec
Merge branch 'master' into fix-issue-220
jfinkels Apr 24, 2012
918e0aa
Merge branch 'master' into fix-issue-220
jfinkels Apr 26, 2012
f5b45b6
Fixes issue #495.
jfinkels Apr 26, 2012
cb6326f
Merge branch 'fix-issue-495' into fix-issue-220
jfinkels Apr 26, 2012
a637d57
Noted change to jsonify function in CHANGES.
jfinkels Apr 26, 2012
3b78ad6
Merge branch 'fix-issue-495' into fix-issue-220
jfinkels Apr 26, 2012
3703156
Merge branch 'master' into fix-issue-220
jfinkels May 2, 2012
08f3af6
Merge branch 'fix-issue-220' of github.com:jfinkels/flask into fix-is…
jfinkels May 2, 2012
1e2aae2
Reverted changes made by too-eager merge with pull request #502.
jfinkels May 2, 2012
1492c48
Merge branch 'master' into fix-issue-220
jfinkels May 8, 2012
48c0d4a
Blueprint example app
dmishe Mar 1, 2012
59269e6
Blueprint example tests
dmishe Mar 1, 2012
6ffacb7
Removed unneeded print statements form mongokit pattern doc
dmishe Apr 8, 2012
070e04c
Update flask/app.py
ekoka Apr 22, 2012
11e94fa
Update flask/testsuite/basic.py
ekoka Apr 24, 2012
8940df2
Improved interface for the URL build error handler
mitsuhiko May 8, 2012
e9ee9b4
Added required_methods
mitsuhiko May 8, 2012
c552360
Added after_this_request decorator.
mitsuhiko May 8, 2012
97c66e8
Changed docstring according to docs.
alekzvik May 14, 2012
9a79a80
Fix failing test: "AssertionError: 'application/javascript' != 'appli…
msabramo May 28, 2012
16188ea
Add .travis.yml
msabramo May 28, 2012
3fc700d
Emended extensiondev.rst.
May 31, 2012
6e8e493
Added link to extensions in "Hook. Extend." section.
brousch Jun 12, 2012
11e249e
I think it should check that cache_timeout is not None to allow for a…
mapio Jun 13, 2012
8646625
Update master
yaph Jun 16, 2012
5b2faf3
Don't build websites with travis
mitsuhiko Jun 17, 2012
6fe0fd6
Removed padded JSON (JSONP) again.
mitsuhiko Jun 17, 2012
5420957
Added #522 in modified version
mitsuhiko Jun 17, 2012
fa616a3
Fix documention for `after_this_request`
mattupstate Jun 18, 2012
18134e7
Fixes #519 by adding return statement
bev-a-tron Jun 25, 2012
5568bb6
Merge branch 'master' into fix-issue-220
jfinkels Jun 25, 2012
989a7a7
Added documentation explaining that the JSON encoder used in the json…
jfinkels Jun 25, 2012
ee67d2e
Merge branch 'fix-issue-220' of github.com:jfinkels/flask into fix-is…
jfinkels Jun 25, 2012
c25ba9a
Merge branch 'master' of git://github.com/mitsuhiko/flask
jfinkels Jun 25, 2012
59d4c02
Merge branch 'master' into fix-issue-220
jfinkels Jun 25, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -8,6 +8,8 @@ Version 0.9

Relase date to be decided, codename to be chosen.

- Added :attr:`flask.Flask.json_encoder_class` so users can customize JSON
encoding during execution of the :func:`flask.jsonify` function.
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
response by default.
- The :func:`flask.url_for` function now can generate anchors to the
Expand Down
5 changes: 5 additions & 0 deletions docs/api.rst
Expand Up @@ -312,6 +312,9 @@ Message Flashing
Returning JSON
--------------

To customize JSON encoding during execution of the :func:`~flask.jsonify`
function, see :attr:`Flask.json_encoder_class`.

.. autofunction:: jsonify

.. data:: json
Expand Down Expand Up @@ -343,6 +346,8 @@ Returning JSON

Note that the ``|tojson`` filter escapes forward slashes properly.

.. autoclass:: JSONEncoder

Template Rendering
------------------

Expand Down
2 changes: 1 addition & 1 deletion flask/__init__.py
Expand Up @@ -22,7 +22,7 @@
from .config import Config
from .helpers import url_for, jsonify, json_available, flash, \
send_file, send_from_directory, get_flashed_messages, \
get_template_attribute, make_response, safe_join
get_template_attribute, make_response, safe_join, JSONEncoder
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
from .ctx import has_request_context, has_app_context, \
Expand Down
34 changes: 33 additions & 1 deletion flask/app.py
Expand Up @@ -25,7 +25,7 @@

from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _tojson_filter, _endpoint_from_view_func, \
find_package
find_package, JSONEncoder
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import RequestContext, AppContext, _RequestGlobals
Expand Down Expand Up @@ -284,6 +284,38 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.8
session_interface = SecureCookieSessionInterface()

#: The JSONEncoder to use when the :func:`~flask.jsonify` function is
#: called.
#:
#: Flask provides a default implementation which provides serialization for
#: :class:`datetime.datetime` objects to strings in ISO 8601 format.
#:
#: To customize encoding of Python objects to JSON strings, create a
#: subclass of :class:`flask.JSONEncoder` which implements the
#: :meth:`json.JSONEncoder.default` method, then set the
#: :attr:`json_encoder_class` attribute on your Flask application object.
#: For example, if you want a different string encoding of
#: :class:`~datetime.datetime` objects::
#:
#: import datetime
#: import flask
#:
#: class MyEncoder(flask.JSONEncoder):
#: def default(self, obj):
#: if isinstance(obj, datetime.datetime):
#: return obj.strftime('%A, %B %d, %H:%M')
#: return super(MyEncoder, self).default(obj)
#:
#: app = flask.Flask(__name__)
#: app.json_encoder_class = MyEncoder
#:
#: @app.route('/currenttime')
#: def current_time():
#: return flask.jsonify(dict(time=datetime.datetime.now()))
#:
#: .. versionadded:: 0.9
json_encoder_class = JSONEncoder

def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False):
Expand Down
38 changes: 36 additions & 2 deletions flask/helpers.py
Expand Up @@ -11,6 +11,7 @@

from __future__ import with_statement

import datetime
import os
import sys
import pkgutil
Expand Down Expand Up @@ -113,16 +114,27 @@ def get_current_user():
"id": 42
}

This function will use the JSON encoder specified by the
:attr:`Flask.json_encoder_class` attribute (which is :class:`JSONEncoder`
by default).

This requires Python 2.6 or an installed version of simplejson. For
security reasons only objects are supported toplevel. For more
information about this, have a look at :ref:`json-security`.

.. versionadded:: 0.2

.. versionchanged:: 0.9
Now uses the JSON encoder specified by :attr:`Flask.json_encoder_class`
on :data:`flask.current_app`.

"""
if __debug__:
_assert_have_json()
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
content = json.dumps(dict(*args, **kwargs),
cls=current_app.json_encoder_class,
indent=None if request.is_xhr else 2)
return current_app.response_class(content, mimetype='application/json')


def make_response(*args):
Expand Down Expand Up @@ -660,6 +672,28 @@ def find_package(import_name):
return None, package_path


class JSONEncoder(json.JSONEncoder):
"""Default implementation of :class:`json.JSONEncoder` which provides
serialization for :class:`datetime.datetime` objects (to ISO 8601 format).

.. versionadded:: 0.9

"""

def default(self, obj):
"""Provides serialization for :class:`datetime.datetime` objects (in
addition to the serialization provided by the default
:class:`json.JSONEncoder` implementation).

If `obj` is a :class:`datetime.datetime` object, this converts it into
the corresponding ISO 8601 string representation.

"""
if isinstance(obj, datetime.datetime):
return obj.isoformat()
return super(JSONEncoder, self).default(obj)


class locked_cached_property(object):
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
Expand Down
33 changes: 33 additions & 0 deletions flask/testsuite/helpers.py
Expand Up @@ -11,6 +11,7 @@

from __future__ import with_statement

import datetime
import os
import flask
import unittest
Expand Down Expand Up @@ -64,6 +65,38 @@ def index():
content_type='application/json; charset=iso-8859-15')
self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8'))

def test_jsonify_datetime(self):
app = flask.Flask(__name__)
app.testing = True
now = datetime.datetime.now()
d = dict(a=123, b=now)
@app.route('/json')
def json():
return flask.jsonify(d)
c = app.test_client()
resp = c.get('/json')
responsedict = flask.json.loads(resp.data)
self.assert_equal(responsedict, dict(a=123, b=now.isoformat()))

def test_json_encoder(self):
class MyEncoder(flask.json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return 'foo'
return super(MyEncoder, self).default(obj)
app = flask.Flask(__name__)
app.testing = True
app.json_encoder_class = MyEncoder
now = datetime.datetime.now()
d = dict(a=123, b=now)
@app.route('/json')
def json():
return flask.jsonify(d)
c = app.test_client()
resp = c.get('/json')
responsedict = flask.json.loads(resp.data)
self.assert_equal(responsedict, dict(a=123, b='foo'))

def test_jsonify(self):
d = dict(a=23, b=42, c=[1, 2, 3])
app = flask.Flask(__name__)
Expand Down