Skip to content

Commit 912920e

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

File tree

5 files changed

+77
-48
lines changed

5 files changed

+77
-48
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: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,18 @@
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-
13-
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)
6+
from .utils import preload_serializer
177

188

199
def output_json(data, code, headers=None):
2010
'''Makes a Flask response with a JSON encoded body'''
21-
22-
global serializer
23-
2411
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
12+
serializer = current_app.config.get('RESTPLUS_CACHED_SERIALIZER')
4013
if serializer is None:
41-
serializer = dumps
14+
preload_serializer(current_app)
15+
serializer = current_app.config.get('RESTPLUS_CACHED_SERIALIZER')
4216

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

flask_restplus/utils.py

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

1966

2067
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)