diff --git a/bson/json_util.py b/bson/json_util.py index ae79188e3..8f5938922 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -112,7 +112,6 @@ import re import sys import uuid -_HAS_OBJECT_PAIRS_HOOK = True if sys.version_info[:2] == (2, 6): # In Python 2.6, json does not include object_pairs_hook. Use simplejson # instead. @@ -120,7 +119,6 @@ if sys.version_info[:2] == (2, 6): import simplejson as json except ImportError: import json - _HAS_OBJECT_PAIRS_HOOK = False else: import json @@ -145,6 +143,13 @@ from bson.timestamp import Timestamp from bson.tz_util import utc +try: + json.loads("{}", object_pairs_hook=dict) + _HAS_OBJECT_PAIRS_HOOK = True +except TypeError: + _HAS_OBJECT_PAIRS_HOOK = False + + _RE_OPT_TABLE = { "i": re.I, "l": re.L, @@ -241,8 +246,8 @@ class JSONOptions(CodecOptions): """Encapsulates JSON options for :func:`dumps` and :func:`loads`. Raises :exc:`~pymongo.errors.ConfigurationError` on Python 2.6 if - `simplejson `_ is not installed - and document_class is not the default (:class:`dict`). + `simplejson >= 2.1.0 `_ is not + installed and document_class is not the default (:class:`dict`). :Parameters: - `strict_number_long`: If ``True``, :class:`~bson.int64.Int64` objects @@ -299,7 +304,7 @@ class JSONOptions(CodecOptions): if not _HAS_OBJECT_PAIRS_HOOK and self.document_class != dict: raise ConfigurationError( "Support for JSONOptions.document_class on Python 2.6 " - "requires simplejson " + "requires simplejson >= 2.1.0" "(https://pypi.python.org/pypi/simplejson) to be installed.") if json_mode not in (JSONMode.LEGACY, JSONMode.RELAXED, diff --git a/test/test_json_util.py b/test/test_json_util.py index 385a89915..6c5fc777a 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -20,19 +20,14 @@ import re import sys import uuid -try: - import simplejson - HAS_SIMPLE_JSON = True -except ImportError: - HAS_SIMPLE_JSON = False - sys.path[0:0] = [""] from pymongo.errors import ConfigurationError from bson import json_util, EPOCH_AWARE, EPOCH_NAIVE, SON from bson.json_util import (DatetimeRepresentation, - STRICT_JSON_OPTIONS) + STRICT_JSON_OPTIONS, + _HAS_OBJECT_PAIRS_HOOK) from bson.binary import (ALL_UUID_REPRESENTATIONS, Binary, MD5_SUBTYPE, USER_DEFINED_SUBTYPE, JAVA_LEGACY, CSHARP_LEGACY, STANDARD) @@ -49,7 +44,6 @@ from bson.tz_util import FixedOffset, utc from test import unittest, IntegrationTest PY3 = sys.version_info[0] == 3 -PY26 = sys.version_info[:2] == (2, 6) class TestJsonUtil(unittest.TestCase): @@ -70,10 +64,11 @@ class TestJsonUtil(unittest.TestCase): self.round_trip({"ref": DBRef("foo", 5, "db")}) self.round_trip({"ref": DBRef("foo", ObjectId())}) - # Check order. - self.assertEqual( - '{"$ref": "collection", "$id": 1, "$db": "db"}', - json_util.dumps(DBRef('collection', 1, 'db'))) + if _HAS_OBJECT_PAIRS_HOOK: + # Check order. + self.assertEqual( + '{"$ref": "collection", "$id": 1, "$db": "db"}', + json_util.dumps(DBRef('collection', 1, 'db'))) def test_datetime(self): # only millis, not micros @@ -231,14 +226,15 @@ class TestJsonUtil(unittest.TestCase): json_util.loads( '{"r": {"$regex": ".*", "$options": "ilm"}}')['r']) - # Check order. - self.assertEqual( - '{"$regex": ".*", "$options": "mx"}', - json_util.dumps(Regex('.*', re.M | re.X))) + if _HAS_OBJECT_PAIRS_HOOK: + # Check order. + self.assertEqual( + '{"$regex": ".*", "$options": "mx"}', + json_util.dumps(Regex('.*', re.M | re.X))) - self.assertEqual( - '{"$regex": ".*", "$options": "mx"}', - json_util.dumps(re.compile(b'.*', re.M | re.X))) + self.assertEqual( + '{"$regex": ".*", "$options": "mx"}', + json_util.dumps(re.compile(b'.*', re.M | re.X))) def test_minkey(self): self.round_trip({"m": MinKey()}) @@ -249,34 +245,45 @@ class TestJsonUtil(unittest.TestCase): def test_timestamp(self): dct = {"ts": Timestamp(4, 13)} res = json_util.dumps(dct, default=json_util.default) - self.assertEqual('{"ts": {"$timestamp": {"t": 4, "i": 13}}}', res) - rtdct = json_util.loads(res) self.assertEqual(dct, rtdct) + if _HAS_OBJECT_PAIRS_HOOK: + self.assertEqual('{"ts": {"$timestamp": {"t": 4, "i": 13}}}', res) + def test_uuid(self): doc = {'uuid': uuid.UUID('f47ac10b-58cc-4372-a567-0e02b2c3d479')} self.round_trip(doc) self.assertEqual( '{"uuid": {"$uuid": "f47ac10b58cc4372a5670e02b2c3d479"}}', json_util.dumps(doc)) - self.assertEqual( - '{"uuid": {"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}', - json_util.dumps(doc, json_options=json_util.STRICT_JSON_OPTIONS)) - self.assertEqual( - '{"uuid": {"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', - json_util.dumps(doc, json_options=json_util.JSONOptions( - strict_uuid=True, uuid_representation=STANDARD))) - self.assertEqual(doc, json_util.loads( - '{"uuid": {"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}')) + + if _HAS_OBJECT_PAIRS_HOOK: + self.assertEqual( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}', + json_util.dumps( + doc, json_options=json_util.STRICT_JSON_OPTIONS)) + self.assertEqual( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', + json_util.dumps( + doc, json_options=json_util.JSONOptions( + strict_uuid=True, uuid_representation=STANDARD))) + self.assertEqual( + doc, json_util.loads( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}')) for uuid_representation in ALL_UUID_REPRESENTATIONS: options = json_util.JSONOptions( strict_uuid=True, uuid_representation=uuid_representation) self.round_trip(doc, json_options=options) # Ignore UUID representation when decoding BSON binary subtype 4. - self.assertEqual(doc, json_util.loads( - '{"uuid": {"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": ' - '"04"}}', json_options=options)) + if _HAS_OBJECT_PAIRS_HOOK: + self.assertEqual(doc, json_util.loads( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', + json_options=options)) def test_binary(self): if PY3: @@ -306,22 +313,23 @@ class TestJsonUtil(unittest.TestCase): self.assertEqual(bin_type_dict, json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}')) - json_bin_dump = json_util.dumps(md5_type_dict) - # Check order. - self.assertEqual( - '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",' - + ' "$type": "05"}}', - json_bin_dump) + if _HAS_OBJECT_PAIRS_HOOK: + json_bin_dump = json_util.dumps(md5_type_dict) + # Check order. + self.assertEqual( + '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",' + + ' "$type": "05"}}', + json_bin_dump) - self.assertEqual(md5_type_dict, - json_util.loads('{"md5": {"$type": 5, "$binary":' - ' "IG43GK8JL9HRL4DK53HMrA=="}}')) + self.assertEqual(md5_type_dict, + json_util.loads('{"md5": {"$type": 5, "$binary":' + ' "IG43GK8JL9HRL4DK53HMrA=="}}')) - json_bin_dump = json_util.dumps(custom_type_dict) - self.assertTrue('"$type": "80"' in json_bin_dump) - self.assertEqual(custom_type_dict, - json_util.loads('{"custom": {"$type": 128, "$binary":' - ' "aGVsbG8="}}')) + json_bin_dump = json_util.dumps(custom_type_dict) + self.assertTrue('"$type": "80"' in json_bin_dump) + self.assertEqual(custom_type_dict, + json_util.loads('{"custom": {"$type": 128, "$binary":' + ' "aGVsbG8="}}')) # Handle mongoexport where subtype >= 128 self.assertEqual(128, @@ -339,12 +347,13 @@ class TestJsonUtil(unittest.TestCase): res = json_util.dumps(code) self.assertEqual(code, json_util.loads(res)) - # Check order. - self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) + if _HAS_OBJECT_PAIRS_HOOK: + # Check order. + self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) - no_scope = Code('function() {}') - self.assertEqual( - '{"$code": "function() {}"}', json_util.dumps(no_scope)) + no_scope = Code('function() {}') + self.assertEqual( + '{"$code": "function() {}"}', json_util.dumps(no_scope)) def test_undefined(self): jsn = '{"name": {"$undefined": true}}' @@ -366,7 +375,7 @@ class TestJsonUtil(unittest.TestCase): self.assertEqual({"foo": "bar"}, json_util.loads( '{"foo": "bar"}', json_options=json_util.JSONOptions(document_class=dict))) - if PY26 and not HAS_SIMPLE_JSON: + if not _HAS_OBJECT_PAIRS_HOOK: self.assertRaises( ConfigurationError, json_util.JSONOptions, document_class=SON) else: