From e9fe2c194c6b5fd4597a86d0875b395a6fa92f18 Mon Sep 17 00:00:00 2001 From: Dan Obermiller Date: Fri, 11 Sep 2015 15:49:41 -0700 Subject: [PATCH 1/4] Function to jsonify BSON objects - Add function to jsonify BSON objects - Add test Issue #62 --- flask_pymongo/__init__.py | 40 ++++++++++++++++++++++++++++++++++-- tests/test_util_functions.py | 20 ++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/test_util_functions.py diff --git a/flask_pymongo/__init__.py b/flask_pymongo/__init__.py index 82c7f52..6cf9574 100644 --- a/flask_pymongo/__init__.py +++ b/flask_pymongo/__init__.py @@ -24,11 +24,12 @@ # POSSIBILITY OF SUCH DAMAGE. -__all__ = ('PyMongo', 'ASCENDING', 'DESCENDING') +__all__ = ('PyMongo', 'ASCENDING', 'DESCENDING', 'bson_to_json') from bson.errors import InvalidId from bson.objectid import ObjectId -from flask import abort, current_app, request +from bson import BSON +from flask import abort, current_app, request, jsonify as flask_jsonify from gridfs import GridFS, NoFile from mimetypes import guess_type from pymongo import uri_parser @@ -339,3 +340,38 @@ def save_upload(filename): storage = GridFS(self.db, base) storage.put(fileobj, filename=filename, content_type=content_type) + + +def bson_to_json(bson_obj, as_class=dict, tz_aware=False, uuid_subtype=3, + compile_re=True, *args, **kwargs): + """Convert a BSON object to a JSON formatted one. Uses the same + argument structure as `flask.json.jsonify` and returns a + `flask.Response` object. + + .. code-block:: python + + @app.route('/get_bson_doc') + def get_bson_doc(): + data = bson.BSON.encode({'a': 1}) + return bson_to_json(data) + + :param BSON bson_obj: the BSON object to be jsonified + :param type as_class (optional): the class to use for the resulting + document + :param bool tz_aware (optional): if `True`, return timezone-aware + `datetime.datetime` instances + :param int uuid_subtype (optional): The BSON representation to use + for UUIDs. See the `bson.binary` module for all options. + :param bool compile_re (optional): if `False`, don’t attempt to + compile BSON regular expressions into Python regular expressions. + Return instances of `Regex` instead. Can avoid `InvalidBSON` + errors when receiving Python-incompatible regular expressions. + :return Response response: The response object to be returned. + """ + + decoded_obj = BSON.decode( + bson_obj, as_class=as_class, tz_aware=tz_aware, + uuid_subtype=uuid_subtype, compile_re=compile_re + ) + + return flask_jsonify(**decoded_obj) diff --git a/tests/test_util_functions.py b/tests/test_util_functions.py new file mode 100644 index 0000000..6935b1b --- /dev/null +++ b/tests/test_util_functions.py @@ -0,0 +1,20 @@ +from tests import util + +from flask import Flask, jsonify +from flask_pymongo import bson_to_json +from bson import BSON + + +class BsonJsonifyTest(util.FlaskRequestTest): + + def setUp(self): + self.app = Flask('test') + self.context = self.app.test_request_context('/') + self.context.push() + + def tearDown(self): + self.context.pop() + + def test_jsonify_bson_object(self): + bson_obj = BSON.encode({'a': 1}) + assert bson_to_json(bson_obj) == jsonify(a=1) From bc53574cea2a79ae9ce2c53aa97693d0447fed81 Mon Sep 17 00:00:00 2001 From: Dan Obermiller Date: Fri, 11 Sep 2015 20:54:07 -0700 Subject: [PATCH 2/4] Fix functionality - Fix functionality - Add many more tests Issue: 62 --- flask_pymongo/__init__.py | 49 +++++++++++-------------- tests/test_util_functions.py | 71 +++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 33 deletions(-) diff --git a/flask_pymongo/__init__.py b/flask_pymongo/__init__.py index 6cf9574..bea59a1 100644 --- a/flask_pymongo/__init__.py +++ b/flask_pymongo/__init__.py @@ -26,9 +26,14 @@ __all__ = ('PyMongo', 'ASCENDING', 'DESCENDING', 'bson_to_json') +try: + import simplejson as json +except ImportError: + import json + +from bson import ObjectId from bson.errors import InvalidId -from bson.objectid import ObjectId -from bson import BSON +from bson.json_util import dumps as bson_dumps from flask import abort, current_app, request, jsonify as flask_jsonify from gridfs import GridFS, NoFile from mimetypes import guess_type @@ -54,6 +59,12 @@ num_type = int +def _str_function(): + if PY2: + return unicode + return str + + DESCENDING = pymongo.DESCENDING """Descending sort order.""" @@ -342,36 +353,20 @@ def save_upload(filename): storage.put(fileobj, filename=filename, content_type=content_type) -def bson_to_json(bson_obj, as_class=dict, tz_aware=False, uuid_subtype=3, - compile_re=True, *args, **kwargs): - """Convert a BSON object to a JSON formatted one. Uses the same - argument structure as `flask.json.jsonify` and returns a - `flask.Response` object. +def jsonify(obj, *args, **kwargs): + """Same call signature as `flask.jsonify`. Works with MongoDB + BSON objects. .. code-block:: python @app.route('/get_bson_doc') def get_bson_doc(): - data = bson.BSON.encode({'a': 1}) - return bson_to_json(data) - - :param BSON bson_obj: the BSON object to be jsonified - :param type as_class (optional): the class to use for the resulting - document - :param bool tz_aware (optional): if `True`, return timezone-aware - `datetime.datetime` instances - :param int uuid_subtype (optional): The BSON representation to use - for UUIDs. See the `bson.binary` module for all options. - :param bool compile_re (optional): if `False`, don’t attempt to - compile BSON regular expressions into Python regular expressions. - Return instances of `Regex` instead. Can avoid `InvalidBSON` - errors when receiving Python-incompatible regular expressions. + data = bson.BSON.encode({'a': 1, 'id_': ObjectId()}) + return jsonify(data) + + :param object obj: The object to be jsonified. + :return Response response: The response object to be returned. """ - decoded_obj = BSON.decode( - bson_obj, as_class=as_class, tz_aware=tz_aware, - uuid_subtype=uuid_subtype, compile_re=compile_re - ) - - return flask_jsonify(**decoded_obj) + return flask_jsonify(**json.loads(bson_dumps(obj, *args, **kwargs))) \ No newline at end of file diff --git a/tests/test_util_functions.py b/tests/test_util_functions.py index 6935b1b..e7e504d 100644 --- a/tests/test_util_functions.py +++ b/tests/test_util_functions.py @@ -1,8 +1,15 @@ +import sys +import datetime + from tests import util -from flask import Flask, jsonify -from flask_pymongo import bson_to_json -from bson import BSON +from flask import Flask, jsonify as flask_jsonify +from flask.ext.pymongo import jsonify +from bson import ObjectId, Binary, Code, Regex, DBRef + + +if sys.version_info[0] == 2: + str = unicode class BsonJsonifyTest(util.FlaskRequestTest): @@ -15,6 +22,58 @@ def setUp(self): def tearDown(self): self.context.pop() - def test_jsonify_bson_object(self): - bson_obj = BSON.encode({'a': 1}) - assert bson_to_json(bson_obj) == jsonify(a=1) + def test_jsonify_ObjectId(self): + objectid = ObjectId(b'foo-bar-quuz') + json = {'a': 1, 'id_': objectid} + safe_json = {'a': 1, 'id_': {'$oid': str(objectid)}} + + jsonified_bson = jsonify(json).response + jsonified = flask_jsonify(safe_json).response + + assert jsonified_bson == jsonified + + def test_jsonify_Binary(self): + binary = Binary(b"hello") + json = {'a': 1, 'bin': binary} + safe_json = {'a': 1, 'bin': {'$binary': "aGVsbG8=", "$type": "00"}} + + jsonified_bson = jsonify(json).response + jsonified = flask_jsonify(safe_json).response + + assert jsonified_bson == jsonified + + def test_jsonify_Code(self): + code = Code("function () { console.log('Hello, world!'); }();") + json = {'a': 1, 'code': code} + safe_json = {'a': 1, 'code': {'$code': str(code), '$scope': {}}} + + jsonified_bson = jsonify(json).response + jsonified = flask_jsonify(safe_json).response + + assert jsonified_bson == jsonified + + def test_jsonify_Regex(self): + regex = Regex("bb|[^b]{2}") + json = {'a': 1, 'regex': regex} + safe_json = {'a': 1, 'regex': {'$regex': "bb|[^b]{2}", "$options": ""}} + + jsonified_bson = jsonify(json).response + jsonified = flask_jsonify(safe_json).response + + assert jsonified_bson == jsonified + + def test_jsonify_DBRef(self): + dbref = DBRef("fake_document", "helloworld") + json = {'a': 1, 'dbref': dbref} + safe_json = { + 'a': 1, + 'dbref': { + '$id': 'helloworld', + '$ref': 'fake_document' + } + } + + jsonified_bson = jsonify(json).response + jsonified = flask_jsonify(safe_json).response + + assert jsonified_bson == jsonified From a692e34f0506119e19e035f91f26940b824fa2d6 Mon Sep 17 00:00:00 2001 From: Dan Obermiller Date: Fri, 11 Sep 2015 20:58:37 -0700 Subject: [PATCH 3/4] Fix import, __all__ - remove unused import - Fix name of function in __all__ Issue: 62 --- flask_pymongo/__init__.py | 2 +- tests/test_util_functions.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/flask_pymongo/__init__.py b/flask_pymongo/__init__.py index bea59a1..bd41422 100644 --- a/flask_pymongo/__init__.py +++ b/flask_pymongo/__init__.py @@ -24,7 +24,7 @@ # POSSIBILITY OF SUCH DAMAGE. -__all__ = ('PyMongo', 'ASCENDING', 'DESCENDING', 'bson_to_json') +__all__ = ('PyMongo', 'ASCENDING', 'DESCENDING', 'jsonify') try: import simplejson as json diff --git a/tests/test_util_functions.py b/tests/test_util_functions.py index e7e504d..1d1bdc0 100644 --- a/tests/test_util_functions.py +++ b/tests/test_util_functions.py @@ -1,5 +1,4 @@ import sys -import datetime from tests import util From d944615a5517f1544e8ae156ab896e7b595ac6a0 Mon Sep 17 00:00:00 2001 From: Dan Obermiller Date: Fri, 11 Sep 2015 21:00:11 -0700 Subject: [PATCH 4/4] Remove unnecessary function Issue: 62 --- flask_pymongo/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/flask_pymongo/__init__.py b/flask_pymongo/__init__.py index bd41422..c4e5144 100644 --- a/flask_pymongo/__init__.py +++ b/flask_pymongo/__init__.py @@ -59,12 +59,6 @@ num_type = int -def _str_function(): - if PY2: - return unicode - return str - - DESCENDING = pymongo.DESCENDING """Descending sort order."""