Skip to content

Commit bb1006c

Browse files
committed
notify the user in case we couldn't load the custom serializer through a warnings.warn(UserWarning)
1 parent 813ac5d commit bb1006c

File tree

5 files changed

+77
-47
lines changed

5 files changed

+77
-47
lines changed

doc/quickstart.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,13 @@ The following configuration options exist for Flask-RESTPlus:
360360

361361
.. note::
362362
Flask-RESTPlus will always
363-
silently fallback to the
364-
default ``json.dumps``
365-
*serializer* if it cannot
366-
manage to import the one
367-
you configured.
363+
fallback to the default
364+
``json.dumps`` *serializer*
365+
if it cannot manage to import
366+
the one you configured.
367+
In such case, a
368+
``UserWarning`` will be
369+
raised.
368370

369371

370372
.. warning::

flask_restplus/api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from .postman import PostmanCollectionV1
3333
from .resource import Resource
3434
from .swagger import Swagger
35-
from .utils import default_id, camel_to_dash, unpack
35+
from .utils import default_id, camel_to_dash, preload_serializer, unpack
3636
from .representations import output_json
3737
from ._http import HTTPStatus
3838

@@ -209,6 +209,7 @@ def _init_app(self, app):
209209
self._validate = self._validate if self._validate is not None else app.config.get('RESTPLUS_VALIDATE', False)
210210
app.config.setdefault('RESTPLUS_MASK_HEADER', 'X-Fields')
211211
app.config.setdefault('RESTPLUS_MASK_SWAGGER', True)
212+
preload_serializer(app)
212213

213214
def __getattr__(self, name):
214215
try:

flask_restplus/representations.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals, absolute_import
33

4-
import importlib
5-
6-
from json import dumps
7-
84
from flask import make_response, current_app
95

10-
DEFAULT_SERIALIZER = 'dumps'
11-
serializer = None
12-
6+
from json import dumps
137

14-
def _importer(mod_name, func_name=DEFAULT_SERIALIZER, default=dumps):
15-
imported = importlib.import_module(mod_name)
16-
return getattr(imported, func_name, default)
8+
from .utils import preload_serializer
179

1810

1911
def output_json(data, code, headers=None):
2012
'''Makes a Flask response with a JSON encoded body'''
21-
22-
global serializer
23-
2413
settings = current_app.config.get('RESTPLUS_JSON', {})
25-
custom_serializer = current_app.config.get('RESTPLUS_JSON_SERIALIZER', None)
26-
27-
# If the user wants to use a custom serializer, let it be
28-
if serializer is None and custom_serializer:
29-
try:
30-
serializer = _importer(custom_serializer)
31-
except ImportError:
32-
if '.' in custom_serializer:
33-
mod, func = custom_serializer.rsplit('.', 1)
34-
try:
35-
serializer = _importer(mod, func)
36-
except ImportError:
37-
pass
38-
39-
# fallback, no serializer found so far, use the default one
14+
serializer = current_app.config.get('RESTPLUS_CACHED_SERIALIZER')
4015
if serializer is None:
41-
serializer = dumps
16+
preload_serializer(current_app)
17+
serializer = current_app.config.get('RESTPLUS_CACHED_SERIALIZER')
4218

4319
# If we're in debug mode, and the indent is not set, we set it to a
4420
# reasonable value here. Note that this won't override any existing value

flask_restplus/utils.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
from __future__ import unicode_literals
33

44
import re
5+
import importlib
6+
import warnings
57

68
from collections import OrderedDict
79
from copy import deepcopy
10+
from json import dumps
811
from six import iteritems
912

1013
from ._http import HTTPStatus
@@ -14,7 +17,50 @@
1417
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')
1518

1619

17-
__all__ = ('merge', 'camel_to_dash', 'default_id', 'not_none', 'not_none_sorted', 'unpack')
20+
__all__ = ('preload_serializer', 'importer', 'merge', 'camel_to_dash', 'default_id', 'not_none', 'not_none_sorted', 'unpack')
21+
22+
23+
def preload_serializer(app):
24+
'''
25+
Preload the json serializer for the given ``app``.
26+
27+
:param flask.Flask app: The flask application object
28+
'''
29+
custom_serializer = app.config.get('RESTPLUS_JSON_SERIALIZER', None)
30+
serializer = None
31+
32+
# If the user wants to use a custom serializer, let it be
33+
if custom_serializer:
34+
try:
35+
serializer = importer(custom_serializer, 'dumps')
36+
except ImportError:
37+
if '.' in custom_serializer:
38+
mod, func = custom_serializer.rsplit('.', 1)
39+
try:
40+
serializer = importer(mod, func)
41+
except ImportError:
42+
warnings.warn("Unable to load custom serializer '{}', falling back to "
43+
"'json.dumps'".format(custom_serializer),
44+
UserWarning)
45+
46+
# fallback, no serializer found so far, use the default one
47+
if serializer is None:
48+
serializer = dumps
49+
app.config['RESTPLUS_CACHED_SERIALIZER'] = serializer
50+
51+
52+
def importer(mod_name, obj_name, default=None):
53+
'''
54+
Import the given ``obj_name`` from the given ``mod_name``.
55+
56+
:param str mod_name: Module from which to import the ``obj_name``
57+
:param str obj_name: Object to import from ``mod_name``
58+
:param object default: Default object to return
59+
60+
:return: Imported object
61+
'''
62+
imported = importlib.import_module(mod_name)
63+
return getattr(imported, obj_name, default)
1864

1965

2066
def merge(first, second):

tests/test_representations.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import pytest
2+
13
import flask_restplus.representations as rep
4+
from flask_restplus.utils import preload_serializer
25

36
from json import dumps, loads
47
from ujson import dumps as udumps, loads as uloads
@@ -11,31 +14,33 @@
1114

1215

1316
def test_representations_serialization_output_correct(app):
17+
print(app.config)
1418
r = rep.output_json(payload, 200)
1519
assert loads(r.get_data(True)) == loads(dumps(payload))
1620

1721

18-
def test_config_custom_serializer_is_module(app):
19-
# now reset serializer
20-
rep.serializer = None
21-
# then enforce a custom serializer
22+
def test_config_custom_serializer_is_module(app, api):
23+
# enforce a custom serializer
2224
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson'
25+
# now reset serializer
26+
preload_serializer(app)
2327
r2 = rep.output_json(payload, 200)
2428
assert uloads(r2.get_data(True)) == uloads(udumps(payload))
25-
assert rep.serializer == udumps
29+
assert app.config.get('RESTPLUS_CACHED_SERIALIZER') == udumps
2630

2731

28-
def test_config_custom_serializer_is_function(app):
32+
def test_config_custom_serializer_is_function(app, api):
2933
# test other config syntax
30-
rep.serializer = None
3134
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson.dumps'
35+
preload_serializer(app)
3236
rep.output_json(payload, 200)
33-
assert rep.serializer == udumps
37+
assert app.config.get('RESTPLUS_CACHED_SERIALIZER') == udumps
3438

3539

36-
def test_config_custom_serializer_fallback(app):
40+
def test_config_custom_serializer_fallback(app, api):
3741
# test fallback
38-
rep.serializer = None
3942
app.config['RESTPLUS_JSON_SERIALIZER'] = 'ujson.lol.dumps'
43+
with pytest.warns(UserWarning):
44+
preload_serializer(app)
4045
rep.output_json(payload, 200)
41-
assert rep.serializer == dumps
46+
assert app.config.get('RESTPLUS_CACHED_SERIALIZER') == dumps

0 commit comments

Comments
 (0)