From 6e2ecc181753282e2ed41ebd0e33dd63a9fc6529 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Thu, 28 Jul 2016 11:13:10 -0700 Subject: [PATCH] PYTHON-1111 JSONOptions.document_class requires simplejson in Python2.6 --- bson/json_util.py | 34 ++++++++++++++++++++++++++++++---- doc/changelog.rst | 19 +++++++++++++++++++ test/test_json_util.py | 23 ++++++++++++++++++++--- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/bson/json_util.py b/bson/json_util.py index cfa67ae7c..448b61dea 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -70,10 +70,24 @@ but it will be faster as there is less recursion. import base64 import collections import datetime -import json 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. + try: + import simplejson as json + except ImportError: + import json + _HAS_OBJECT_PAIRS_HOOK = False +else: + import json + +from pymongo.errors import ConfigurationError + import bson from bson import EPOCH_AWARE, RE_TYPE, SON from bson.binary import (Binary, JAVA_LEGACY, CSHARP_LEGACY, OLD_UUID_SUBTYPE, @@ -106,6 +120,10 @@ _RE_OPT_TABLE = { 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`). + :Parameters: - `strict_number_long`: If ``True``, :class:`~bson.int64.Int64` objects are encoded to MongoDB Extended JSON's *Strict mode* type @@ -145,6 +163,11 @@ class JSONOptions(CodecOptions): if kwargs["tz_aware"]: kwargs["tzinfo"] = kwargs.get("tzinfo", utc) self = super(JSONOptions, cls).__new__(cls, *args, **kwargs) + if not _HAS_OBJECT_PAIRS_HOOK and self.document_class != dict: + raise ConfigurationError( + "Support for JSONOptions.document_class on Python 2.6 " + "requires simplejson " + "(https://pypi.python.org/pypi/simplejson) to be installed.") self.strict_number_long = strict_number_long self.strict_date = strict_date self.strict_uuid = strict_uuid @@ -177,7 +200,7 @@ def dumps(obj, *args, **kwargs): Recursive function that handles all BSON types including :class:`~bson.binary.Binary` and :class:`~bson.code.Code`. - Raises :class:`~bson.errors.InvalidDatetime` if `obj` contains a + Raises :exc:`~bson.errors.InvalidDatetime` if `obj` contains a :class:`datetime.datetime` without a timezone and `json_options.strict_date` is ``True``. @@ -211,8 +234,11 @@ def loads(s, *args, **kwargs): Accepts optional parameter `json_options`. See :class:`JSONOptions`. """ json_options = kwargs.pop("json_options", DEFAULT_JSON_OPTIONS) - kwargs["object_pairs_hook"] = lambda pairs: object_pairs_hook(pairs, - json_options) + if _HAS_OBJECT_PAIRS_HOOK: + kwargs["object_pairs_hook"] = lambda pairs: object_pairs_hook( + pairs, json_options) + else: + kwargs["object_hook"] = lambda obj: object_hook(obj, json_options) return json.loads(s, *args, **kwargs) diff --git a/doc/changelog.rst b/doc/changelog.rst index 68b2d40e5..95580615c 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,25 @@ Changelog ========= +Changes in Version 3.4 +---------------------- + +Version 3.4 implements the new server features introduced in MongoDB 3.4: + +Highlights include: + +- Finer control over JSON encoding/decoding with + :class:`~bson.json_util.JSONOptions`. + + +Issues Resolved +............... + +See the `PyMongo 3.4 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 3.4 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/16594 + Changes in Version 3.3 ---------------------- diff --git a/test/test_json_util.py b/test/test_json_util.py index 1df9bb3ce..ce8a419d9 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -20,8 +20,16 @@ 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.binary import (Binary, MD5_SUBTYPE, USER_DEFINED_SUBTYPE, JAVA_LEGACY, CSHARP_LEGACY, STANDARD) @@ -39,6 +47,7 @@ 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): @@ -321,9 +330,17 @@ class TestJsonUtil(unittest.TestCase): jsn) def test_loads_document_class(self): - self.assertEqual(SON([("foo", "bar"), ("b", 1)]), json_util.loads( - '{"foo": "bar", "b": 1}', - json_options=json_util.JSONOptions(document_class=SON))) + # document_class dict should always work + self.assertEqual({"foo": "bar"}, json_util.loads( + '{"foo": "bar"}', + json_options=json_util.JSONOptions(document_class=dict))) + if PY26 and not HAS_SIMPLE_JSON: + self.assertRaises( + ConfigurationError, json_util.JSONOptions, document_class=SON) + else: + self.assertEqual(SON([("foo", "bar"), ("b", 1)]), json_util.loads( + '{"foo": "bar", "b": 1}', + json_options=json_util.JSONOptions(document_class=SON))) class TestJsonUtilRoundtrip(IntegrationTest):