Skip to content

Commit

Permalink
let the user choose a custom json serializer through the RESTPLUS_JSO…
Browse files Browse the repository at this point in the history
…N_SERIALIZER config option
  • Loading branch information
ziirish committed May 13, 2019
1 parent bee950a commit 813ac5d
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Changelog
Current
-------

- **Behavior**:
* ``ujson`` is not the default json serializer anymore. A new configuration option is available instead: ``RESTPLUS_JSON_SERIALIZER`` (:issue:`507`, :issue:`587`, :issue:`589`, :pr:`637`)
- Add new `Wildcard` fields (:pr:`255`)
- Fix ABC deprecation warnings (:pr:`580`)
- Fix `@api.expect(..., validate=False)` decorators for an :class:`Api` where `validate=True` is set on the constructor (:issue:`609`, :pr:`610`)
Expand Down
69 changes: 69 additions & 0 deletions doc/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,75 @@ parameter to some classes or function to force order preservation:
- globally on :class:`Namespace`: ``ns = Namespace(ordered=True)``
- locally on :func:`marshal`: ``return marshal(data, fields, ordered=True)``

Configuration
-------------

The following configuration options exist for Flask-RESTPlus:

============================ =============== ==================================
OPTION DEFAULT VALUE DESCRIPTION
============================ =============== ==================================
``BUNDLE_ERRORS`` ``False`` Bundle all the validation errors
instead of returning only the
first one encountered.
See the `Error Handling
<parsing.html#error-handling>`__
section of the documentation for
details.
``RESTPLUS_VALIDATE`` ``False`` Whether to enforce payload
validation by default when using
the ``@api.expect()`` decorator.
See the `@api.expect()
<swagger.html#the-api-expect-decorator>`__
documentation for details.
``RESTPLUS_MASK_HEADER`` ``X-Fields`` Choose the name of the *Header*
that will contain the masks to
apply to your answer.
See the `Fields masks <mask.html>`__
documentation for details.
``RESTPLUS_MASK_SWAGGER`` ``True`` Whether to enable the mask
documentation in your swagger or
not.
See the `mask usage
<mask.html#usage>`__ documentation
for details.
``RESTPLUS_JSON`` ``{}`` Dictionary of options to pass to
the json *serializer* (by default
``json.dumps``).
``RESTPLUS_JSON_SERIALIZER`` ``None`` Here you can choose your
own/preferred json *serializer*.
You can either specify the name
of the module (example: ``ujson``)
or you can give the full name of
your *serializer* (example:
``ujson.dumps``).

.. note::
If you only specify the module
name the default Flask-RESTPlus
behavior is to import its
``dumps`` method.


.. note::
Flask-RESTPlus will always
silently fallback to the
default ``json.dumps``
*serializer* if it cannot
manage to import the one
you configured.


.. warning::
We only officially support
python's builtin
``json.dumps``.
Please keep in mind some
serializers may behave
differently depending on the
input types (floats, dates,
etc.).
============================ =============== ==================================

Full example
------------
Expand Down
31 changes: 30 additions & 1 deletion flask_restplus/representations.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import

import importlib

from json import dumps

from flask import make_response, current_app

DEFAULT_SERIALIZER = 'dumps'
serializer = None


def _importer(mod_name, func_name=DEFAULT_SERIALIZER, default=dumps):
imported = importlib.import_module(mod_name)
return getattr(imported, func_name, default)


def output_json(data, code, headers=None):
'''Makes a Flask response with a JSON encoded body'''

global serializer

settings = current_app.config.get('RESTPLUS_JSON', {})
custom_serializer = current_app.config.get('RESTPLUS_JSON_SERIALIZER', None)

# If the user wants to use a custom serializer, let it be
if serializer is None and custom_serializer:
try:
serializer = _importer(custom_serializer)
except ImportError:
if '.' in custom_serializer:
mod, func = custom_serializer.rsplit('.', 1)
try:
serializer = _importer(mod, func)
except ImportError:
pass

# fallback, no serializer found so far, use the default one
if serializer is None:
serializer = dumps

# If we're in debug mode, and the indent is not set, we set it to a
# reasonable value here. Note that this won't override any existing value
Expand All @@ -19,7 +48,7 @@ def output_json(data, code, headers=None):

# always end the json dumps with a new line
# see https://github.com/mitsuhiko/flask/pull/1262
dumped = dumps(data, **settings) + "\n"
dumped = serializer(data, **settings) + "\n"

resp = make_response(dumped, code)
resp.headers.extend(headers or {})
Expand Down
1 change: 1 addition & 0 deletions requirements/test.pip
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pytest-mock==1.6.3
pytest-profiling==1.2.11
pytest-sugar==0.9.0
tzlocal
ujson
41 changes: 41 additions & 0 deletions tests/test_representations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import flask_restplus.representations as rep

from json import dumps, loads
from ujson import dumps as udumps, loads as uloads

payload = {
'id': 1,
'name': 'toto',
'address': 'test',
}


def test_representations_serialization_output_correct(app):
r = rep.output_json(payload, 200)
assert loads(r.get_data(True)) == loads(dumps(payload))


def test_config_custom_serializer_is_module(app):
# now reset serializer
rep.serializer = None
# then enforce a custom serializer
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson'
r2 = rep.output_json(payload, 200)
assert uloads(r2.get_data(True)) == uloads(udumps(payload))
assert rep.serializer == udumps


def test_config_custom_serializer_is_function(app):
# test other config syntax
rep.serializer = None
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson.dumps'
rep.output_json(payload, 200)
assert rep.serializer == udumps


def test_config_custom_serializer_fallback(app):
# test fallback
rep.serializer = None
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson.lol.dumps'
rep.output_json(payload, 200)
assert rep.serializer == dumps

0 comments on commit 813ac5d

Please sign in to comment.