Skip to content

Commit d283967

Browse files
committed
Add support of new JsonProviders in Flask 2.2+
1 parent 64248c1 commit d283967

File tree

2 files changed

+98
-7
lines changed

2 files changed

+98
-7
lines changed

flask_mongoengine/json.py

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,43 @@
1+
"""Flask application JSON extension functions."""
2+
from functools import lru_cache
3+
14
from bson import DBRef, ObjectId, json_util
2-
from flask.json import JSONEncoder
35
from mongoengine.base import BaseDocument
46
from mongoengine.queryset import QuerySet
57
from pymongo.command_cursor import CommandCursor
68

79

10+
@lru_cache(maxsize=1)
11+
def use_json_provider() -> bool:
12+
"""Split Flask before 2.2.0 and after, to use/not use JSON provider approach."""
13+
from flask import __version__
14+
15+
version = list(__version__.split("."))
16+
return int(version[0]) > 2 or (int(version[0]) == 2 and int(version[1]) > 1)
17+
18+
819
def _make_encoder(superclass):
20+
"""Extend Flask JSON Encoder 'default' method with support of Mongo objects."""
21+
import warnings
22+
23+
warnings.warn(
24+
(
25+
"JSONEncoder/JSONDecoder are deprecated in Flask 2.2 and will be removed "
26+
"in Flask 2.3."
27+
),
28+
DeprecationWarning,
29+
stacklevel=2,
30+
)
31+
932
class MongoEngineJSONEncoder(superclass):
1033
"""
1134
A JSONEncoder which provides serialization of MongoEngine
1235
documents and queryset objects.
1336
"""
1437

38+
# noinspection PyProtectedMember, DuplicatedCode
1539
def default(self, obj):
40+
"""Extend JSONEncoder default method, with Mongo objects."""
1641
if isinstance(obj, BaseDocument):
1742
return json_util._json_convert(obj.to_mongo())
1843
elif isinstance(obj, QuerySet):
@@ -28,7 +53,44 @@ def default(self, obj):
2853
return MongoEngineJSONEncoder
2954

3055

31-
MongoEngineJSONEncoder = _make_encoder(JSONEncoder)
56+
def _update_json_provider(superclass):
57+
"""Extend Flask Provider 'default' static method with support of Mongo objects."""
58+
59+
class MongoEngineJSONProvider(superclass):
60+
"""A JSON Provider update for Flask 2.2.0+"""
61+
62+
# noinspection PyProtectedMember, DuplicatedCode
63+
@staticmethod
64+
def default(obj):
65+
"""Extend JSONProvider default static method, with Mongo objects."""
66+
if isinstance(obj, BaseDocument):
67+
return json_util._json_convert(obj.to_mongo())
68+
elif isinstance(obj, QuerySet):
69+
return json_util._json_convert(obj.as_pymongo())
70+
elif isinstance(obj, CommandCursor):
71+
return json_util._json_convert(obj)
72+
elif isinstance(obj, DBRef):
73+
return obj.id
74+
elif isinstance(obj, ObjectId):
75+
return obj.__str__()
76+
return super().default(obj)
77+
78+
return MongoEngineJSONProvider
79+
80+
81+
# Compatibility code for Flask 2.2.0+ support
82+
MongoEngineJSONEncoder = None
83+
MongoEngineJSONProvider = None
84+
85+
if use_json_provider():
86+
from flask.json.provider import DefaultJSONProvider
87+
88+
MongoEngineJSONProvider = _update_json_provider(DefaultJSONProvider)
89+
else:
90+
from flask.json import JSONEncoder
91+
92+
MongoEngineJSONEncoder = _make_encoder(JSONEncoder)
93+
# End of compatibility code
3294

3395

3496
def override_json_encoder(app):
@@ -42,4 +104,9 @@ def override_json_encoder(app):
42104
NOTE: This does not cover situations where users override
43105
an instance's json_encoder after calling init_app.
44106
"""
45-
app.json_encoder = _make_encoder(app.json_encoder)
107+
108+
if use_json_provider():
109+
app.json_provider_class = _update_json_provider(app.json_provider_class)
110+
app.json = app.json_provider_class(app)
111+
else:
112+
app.json_encoder = _make_encoder(app.json_encoder)

tests/test_json.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
"""Extension of app JSON capabilities."""
12
import flask
23
import pytest
34

45
from flask_mongoengine import MongoEngine
6+
from flask_mongoengine.json import use_json_provider
57

68

79
@pytest.fixture()
810
def extended_db(app):
9-
app.json_encoder = DummyEncoder
11+
"""Provider config fixture."""
12+
if use_json_provider():
13+
app.json_provider_class = DummyProvider
14+
else:
15+
app.json_encoder = DummyEncoder
1016
app.config["MONGODB_SETTINGS"] = [
1117
{
1218
"db": "flask_mongoengine_test_db",
@@ -43,11 +49,29 @@ class DummyEncoder(flask.json.JSONEncoder):
4349
"""
4450

4551

52+
DummyProvider = None
53+
if use_json_provider():
54+
55+
class DummyProvider(flask.json.provider.DefaultJSONProvider):
56+
"""Dummy Provider, to test correct MRO in new flask versions."""
57+
58+
59+
@pytest.mark.skipif(condition=use_json_provider(), reason="New flask use other test")
4660
@pytest.mark.usefixtures("extended_db")
47-
def test_inheritance(app):
61+
def test_inheritance_old_flask(app):
4862
assert issubclass(app.json_encoder, DummyEncoder)
4963
json_encoder_name = app.json_encoder.__name__
5064

51-
# Since the class is dynamically derrived, must compare class names
52-
# rather than class objects.
5365
assert json_encoder_name == "MongoEngineJSONEncoder"
66+
67+
68+
@pytest.mark.skipif(
69+
condition=not use_json_provider(), reason="Old flask use other test"
70+
)
71+
@pytest.mark.usefixtures("extended_db")
72+
def test_inheritance(app):
73+
assert issubclass(app.json_provider_class, DummyProvider)
74+
json_provider_class = app.json_provider_class.__name__
75+
76+
assert json_provider_class == "MongoEngineJSONProvider"
77+
assert isinstance(app.json, DummyProvider)

0 commit comments

Comments
 (0)