Skip to content

Commit 17e8662

Browse files
committed
Merge pull request #91 from wcdolphin/master
Supply a Flask JSON Encoder
2 parents 0dfd36d + 9a2c9d4 commit 17e8662

File tree

5 files changed

+208
-2
lines changed

5 files changed

+208
-2
lines changed

.travis.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@ python:
88
env:
99
- PYMONGO=dev MONGOENGINE=dev
1010
- PYMONGO=dev MONGOENGINE=0.7.10
11-
- PYMONGO=dev MONGOENGINE=0.8.0RC2
11+
- PYMONGO=dev MONGOENGINE=0.8.0
12+
- PYMONGO=dev MONGOENGINE=0.8.7
1213
- PYMONGO=2.5 MONGOENGINE=dev
1314
- PYMONGO=2.5 MONGOENGINE=0.7.10
14-
- PYMONGO=2.5 MONGOENGINE=0.8.0RC2
15+
- PYMONGO=2.5 MONGOENGINE=0.8.0
16+
- PYMONGO=2.5 MONGOENGINE=0.8.7
17+
- PYMONGO=2.7 MONGOENGINE=dev
18+
- PYMONGO=2.7 MONGOENGINE=0.7.10
19+
- PYMONGO=2.7 MONGOENGINE=0.8.0
20+
- PYMONGO=2.7 MONGOENGINE=0.8.7
21+
1522
install:
1623
- if [[ $PYMONGO == 'dev' ]]; then pip install https://github.com/mongodb/mongo-python-driver/tarball/master; true; fi
1724
- if [[ $PYMONGO != 'dev' ]]; then pip install pymongo==$PYMONGO --use-mirrors; true; fi

flask_mongoengine/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from .sessions import *
1212
from .pagination import *
13+
from .json import overide_json_encoder
1314

1415

1516
def _include_mongoengine(obj):
@@ -67,6 +68,8 @@ def init_app(self, app):
6768
app.extensions = getattr(app, 'extensions', {})
6869
app.extensions['mongoengine'] = self
6970
self.app = app
71+
overide_json_encoder(app)
72+
7073

7174
class BaseQuerySet(QuerySet):
7275
"""

flask_mongoengine/json.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from flask.json import JSONEncoder
2+
from bson import json_util
3+
from mongoengine.base import BaseDocument
4+
try:
5+
from mongoengine.base import BaseQuerySet
6+
except ImportError as ie: # support mongoengine < 0.7
7+
from mongoengine.queryset import QuerySet as BaseQuerySet
8+
9+
def _make_encoder(superclass):
10+
class MongoEngineJSONEncoder(superclass):
11+
'''
12+
A JSONEncoder which provides serialization of MongoEngine
13+
documents and queryset objects.
14+
'''
15+
def default(self, obj):
16+
if isinstance(obj, BaseDocument):
17+
return json_util._json_convert(obj.to_mongo())
18+
elif isinstance(obj, BaseQuerySet):
19+
return json_util._json_convert(obj.as_pymongo())
20+
return superclass.default(self, obj)
21+
return MongoEngineJSONEncoder
22+
23+
MongoEngineJSONEncoder = _make_encoder(JSONEncoder)
24+
25+
26+
def overide_json_encoder(app):
27+
'''
28+
A function to dynamically create a new MongoEngineJSONEncoder class
29+
based upon a custom base class.
30+
This function allows us to combine MongoEngine serialization with
31+
any changes to Flask's JSONEncoder which a user may have made
32+
prior to calling init_app.
33+
34+
NOTE: This does not cover situations where users override
35+
an instance's json_encoder after calling init_app.
36+
'''
37+
app.json_encoder = _make_encoder(app.json_encoder)

tests/test_json.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import sys
2+
sys.path[0:0] = [""]
3+
4+
import unittest
5+
import datetime
6+
import flask
7+
8+
from flask.ext.mongoengine import MongoEngine
9+
from flask.ext.mongoengine.json import MongoEngineJSONEncoder
10+
11+
12+
class DummyEncoder(flask.json.JSONEncoder):
13+
'''
14+
An example encoder which a user may create and override
15+
the apps json_encoder with.
16+
This class is a NO-OP, but used to test proper inheritance.
17+
'''
18+
19+
20+
class JSONAppTestCase(unittest.TestCase):
21+
22+
def dictContains(self,superset,subset):
23+
for k,v in subset.items():
24+
if not superset[k] == v:
25+
return False
26+
return True
27+
28+
def assertDictContains(self,superset,subset):
29+
return self.assertTrue(self.dictContains(superset,subset))
30+
31+
def setUp(self):
32+
app = flask.Flask(__name__)
33+
app.config['MONGODB_DB'] = 'testing'
34+
app.config['TESTING'] = True
35+
app.json_encoder = DummyEncoder
36+
db = MongoEngine()
37+
db.init_app(app)
38+
39+
self.app = app
40+
self.db = db
41+
42+
def test_inheritance(self):
43+
self.assertTrue(issubclass(self.app.json_encoder, DummyEncoder))
44+
json_encoder_name = self.app.json_encoder.__name__
45+
46+
# Since the class is dynamically derrived, must compare class names
47+
# rather than class objects.
48+
self.assertEqual(json_encoder_name, 'MongoEngineJSONEncoder')

tests/test_json_app.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import sys
2+
sys.path[0:0] = [""]
3+
4+
import unittest
5+
import datetime
6+
import flask
7+
8+
from flask.ext.mongoengine import MongoEngine
9+
10+
class JSONAppTestCase(unittest.TestCase):
11+
12+
def dictContains(self,superset,subset):
13+
for k,v in subset.items():
14+
if not superset[k] == v:
15+
return False
16+
return True
17+
18+
def assertDictContains(self,superset,subset):
19+
return self.assertTrue(self.dictContains(superset,subset))
20+
21+
def setUp(self):
22+
app = flask.Flask(__name__)
23+
app.config['MONGODB_DB'] = 'testing'
24+
app.config['TESTING'] = True
25+
db = MongoEngine()
26+
27+
class Todo(db.Document):
28+
title = db.StringField(max_length=60)
29+
text = db.StringField()
30+
done = db.BooleanField(default=False)
31+
pub_date = db.DateTimeField(default=datetime.datetime.now)
32+
33+
db.init_app(app)
34+
35+
Todo.drop_collection()
36+
self.Todo = Todo
37+
38+
@app.route('/')
39+
def index():
40+
return flask.jsonify(result=self.Todo.objects())
41+
42+
@app.route('/add', methods=['POST'])
43+
def add():
44+
form = flask.request.form
45+
todo = self.Todo(title=form['title'],
46+
text=form['text'])
47+
todo.save()
48+
return flask.jsonify(result=todo)
49+
50+
@app.route('/show/<id>/')
51+
def show(id):
52+
return flask.jsonify(result=self.Todo.objects.get_or_404(id=id))
53+
54+
55+
self.app = app
56+
self.db = db
57+
58+
59+
def test_connection_kwargs(self):
60+
app = flask.Flask(__name__)
61+
app.config['MONGODB_SETTINGS'] = {
62+
'DB': 'testing_tz_aware',
63+
'alias': 'tz_aware_true',
64+
'TZ_AWARE': True,
65+
}
66+
app.config['TESTING'] = True
67+
db = MongoEngine()
68+
db.init_app(app)
69+
self.assertTrue(db.connection.tz_aware)
70+
71+
app.config['MONGODB_SETTINGS'] = {
72+
'DB': 'testing',
73+
'alias': 'tz_aware_false',
74+
}
75+
db.init_app(app)
76+
self.assertFalse(db.connection.tz_aware)
77+
78+
def test_with_id(self):
79+
c = self.app.test_client()
80+
resp = c.get('/show/38783728378090/')
81+
self.assertEqual(resp.status_code, 404)
82+
83+
rv = c.post('/add', data={'title': 'First Item', 'text': 'The text'})
84+
self.assertEqual(rv.status_code,200)
85+
86+
resp = c.get('/show/%s/' % self.Todo.objects.first().id)
87+
self.assertEqual(resp.status_code, 200)
88+
res = flask.json.loads(resp.data).get('result')
89+
self.assertDictContains(res,{
90+
'title': 'First Item',
91+
'text': 'The text'
92+
})
93+
94+
def test_basic_insert(self):
95+
c = self.app.test_client()
96+
d1 = {'title': 'First Item', 'text': 'The text'}
97+
d2 = {'title': '2nd Item', 'text': 'The text'}
98+
c.post('/add', data=d1)
99+
c.post('/add', data=d2)
100+
rv = c.get('/')
101+
result = flask.json.loads(rv.data).get('result')
102+
103+
self.assertEqual(len(result),2)
104+
105+
# ensure each of the objects is one of the two we already
106+
# inserted
107+
for obj in result:
108+
self.assertTrue(any([
109+
self.dictContains(obj,d1),
110+
self.dictContains(obj,d2)
111+
]))

0 commit comments

Comments
 (0)