Skip to content

Commit 1b024ad

Browse files
ShaneHarveyjuliusgeo
authored andcommitted
PYTHON-2046 Change default JSONMode and dumps output from LEGACY to RELAXED (mongodb#711)
1 parent 3f3e600 commit 1b024ad

File tree

7 files changed

+149
-77
lines changed

7 files changed

+149
-77
lines changed

bson/json_util.py

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
This module provides two helper methods `dumps` and `loads` that wrap the
1818
native :mod:`json` methods and provide explicit BSON conversion to and from
1919
JSON. :class:`~bson.json_util.JSONOptions` provides a way to control how JSON
20-
is emitted and parsed, with the default being the legacy PyMongo format.
21-
:mod:`~bson.json_util` can also generate Canonical or Relaxed `Extended JSON`_
22-
when :const:`CANONICAL_JSON_OPTIONS` or :const:`RELAXED_JSON_OPTIONS` is
20+
is emitted and parsed, with the default being the Relaxed Extended JSON format.
21+
:mod:`~bson.json_util` can also generate Canonical or legacy `Extended JSON`_
22+
when :const:`CANONICAL_JSON_OPTIONS` or :const:`LEGACY_JSON_OPTIONS` is
2323
provided, respectively.
2424
2525
.. _Extended JSON: https://github.com/mongodb/specifications/blob/master/source/extended-json.rst
@@ -32,17 +32,17 @@
3232
>>> loads('[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": "80", "$binary": "AQIDBA=="}}]')
3333
[{'foo': [1, 2]}, {'bar': {'hello': 'world'}}, {'code': Code('function x() { return 1; }', {})}, {'bin': Binary(b'...', 128)}]
3434
35-
Example usage (serialization):
35+
Example usage with :const:`RELAXED_JSON_OPTIONS` (the default):
3636
3737
.. doctest::
3838
3939
>>> from bson import Binary, Code
4040
>>> from bson.json_util import dumps
4141
>>> dumps([{'foo': [1, 2]},
4242
... {'bar': {'hello': 'world'}},
43-
... {'code': Code("function x() { return 1; }", {})},
43+
... {'code': Code("function x() { return 1; }")},
4444
... {'bin': Binary(b"\x01\x02\x03\x04")}])
45-
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]'
45+
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
4646
4747
Example usage (with :const:`CANONICAL_JSON_OPTIONS`):
4848
@@ -57,18 +57,18 @@
5757
... json_options=CANONICAL_JSON_OPTIONS)
5858
'[{"foo": [{"$numberInt": "1"}, {"$numberInt": "2"}]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
5959
60-
Example usage (with :const:`RELAXED_JSON_OPTIONS`):
60+
Example usage (with :const:`LEGACY_JSON_OPTIONS`):
6161
6262
.. doctest::
6363
6464
>>> from bson import Binary, Code
65-
>>> from bson.json_util import dumps, RELAXED_JSON_OPTIONS
65+
>>> from bson.json_util import dumps, LEGACY_JSON_OPTIONS
6666
>>> dumps([{'foo': [1, 2]},
6767
... {'bar': {'hello': 'world'}},
68-
... {'code': Code("function x() { return 1; }")},
68+
... {'code': Code("function x() { return 1; }", {})},
6969
... {'bin': Binary(b"\x01\x02\x03\x04")}],
70-
... json_options=RELAXED_JSON_OPTIONS)
71-
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
70+
... json_options=LEGACY_JSON_OPTIONS)
71+
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]'
7272
7373
Alternatively, you can manually pass the `default` to :func:`json.dumps`.
7474
It won't handle :class:`~bson.binary.Binary` and :class:`~bson.code.Code`
@@ -238,23 +238,27 @@ class JSONOptions(CodecOptions):
238238
239239
.. seealso:: The specification for Relaxed and Canonical `Extended JSON`_.
240240
241-
.. versionadded:: 3.4
241+
.. versionchanged:: 4.0
242+
The default for `json_mode` was changed from :const:`JSONMode.LEGACY`
243+
to :const:`JSONMode.RELAXED`.
242244
243245
.. versionchanged:: 3.5
244246
Accepts the optional parameter `json_mode`.
245247
248+
.. versionadded:: 3.4
246249
"""
247250

248-
def __new__(cls, strict_number_long=False,
249-
datetime_representation=DatetimeRepresentation.LEGACY,
250-
strict_uuid=False, json_mode=JSONMode.LEGACY,
251+
def __new__(cls, strict_number_long=None,
252+
datetime_representation=None,
253+
strict_uuid=None, json_mode=JSONMode.RELAXED,
251254
*args, **kwargs):
252255
kwargs["tz_aware"] = kwargs.get("tz_aware", True)
253256
if kwargs["tz_aware"]:
254257
kwargs["tzinfo"] = kwargs.get("tzinfo", utc)
255258
if datetime_representation not in (DatetimeRepresentation.LEGACY,
256259
DatetimeRepresentation.NUMBERLONG,
257-
DatetimeRepresentation.ISO8601):
260+
DatetimeRepresentation.ISO8601,
261+
None):
258262
raise ConfigurationError(
259263
"JSONOptions.datetime_representation must be one of LEGACY, "
260264
"NUMBERLONG, or ISO8601 from DatetimeRepresentation.")
@@ -267,17 +271,47 @@ def __new__(cls, strict_number_long=False,
267271
"or CANONICAL from JSONMode.")
268272
self.json_mode = json_mode
269273
if self.json_mode == JSONMode.RELAXED:
274+
if strict_number_long:
275+
raise ConfigurationError(
276+
"Cannot specify strict_number_long=True with"
277+
" JSONMode.RELAXED")
278+
if datetime_representation not in (None,
279+
DatetimeRepresentation.ISO8601):
280+
raise ConfigurationError(
281+
"datetime_representation must be DatetimeRepresentation."
282+
"ISO8601 or omitted with JSONMode.RELAXED")
283+
if strict_uuid not in (None, True):
284+
raise ConfigurationError(
285+
"Cannot specify strict_uuid=False with JSONMode.RELAXED")
270286
self.strict_number_long = False
271287
self.datetime_representation = DatetimeRepresentation.ISO8601
272288
self.strict_uuid = True
273289
elif self.json_mode == JSONMode.CANONICAL:
290+
if strict_number_long not in (None, True):
291+
raise ConfigurationError(
292+
"Cannot specify strict_number_long=False with"
293+
" JSONMode.RELAXED")
294+
if datetime_representation not in (
295+
None, DatetimeRepresentation.NUMBERLONG):
296+
raise ConfigurationError(
297+
"datetime_representation must be DatetimeRepresentation."
298+
"NUMBERLONG or omitted with JSONMode.RELAXED")
299+
if strict_uuid not in (None, True):
300+
raise ConfigurationError(
301+
"Cannot specify strict_uuid=False with JSONMode.RELAXED")
274302
self.strict_number_long = True
275303
self.datetime_representation = DatetimeRepresentation.NUMBERLONG
276304
self.strict_uuid = True
277-
else:
278-
self.strict_number_long = strict_number_long
279-
self.datetime_representation = datetime_representation
280-
self.strict_uuid = strict_uuid
305+
else: # JSONMode.LEGACY
306+
self.strict_number_long = False
307+
self.datetime_representation = DatetimeRepresentation.LEGACY
308+
self.strict_uuid = False
309+
if strict_number_long is not None:
310+
self.strict_number_long = strict_number_long
311+
if datetime_representation is not None:
312+
self.datetime_representation = datetime_representation
313+
if strict_uuid is not None:
314+
self.strict_uuid = strict_uuid
281315
return self
282316

283317
def _arguments_repr(self):
@@ -307,7 +341,7 @@ def with_options(self, **kwargs):
307341
>>> from bson.json_util import CANONICAL_JSON_OPTIONS
308342
>>> CANONICAL_JSON_OPTIONS.tz_aware
309343
True
310-
>>> json_options = CANONICAL_JSON_OPTIONS.with_options(tz_aware=False)
344+
>>> json_options = CANONICAL_JSON_OPTIONS.with_options(tz_aware=False, tzinfo=None)
311345
>>> json_options.tz_aware
312346
False
313347
@@ -329,15 +363,6 @@ def with_options(self, **kwargs):
329363
.. versionadded:: 3.5
330364
"""
331365

332-
DEFAULT_JSON_OPTIONS = LEGACY_JSON_OPTIONS
333-
"""The default :class:`JSONOptions` for JSON encoding/decoding.
334-
335-
The same as :const:`LEGACY_JSON_OPTIONS`. This will change to
336-
:const:`RELAXED_JSON_OPTIONS` in a future release.
337-
338-
.. versionadded:: 3.4
339-
"""
340-
341366
CANONICAL_JSON_OPTIONS = JSONOptions(json_mode=JSONMode.CANONICAL)
342367
""":class:`JSONOptions` for Canonical Extended JSON.
343368
@@ -354,18 +379,16 @@ def with_options(self, **kwargs):
354379
.. versionadded:: 3.5
355380
"""
356381

357-
STRICT_JSON_OPTIONS = JSONOptions(
358-
strict_number_long=True,
359-
datetime_representation=DatetimeRepresentation.ISO8601,
360-
strict_uuid=True)
361-
"""**DEPRECATED** - :class:`JSONOptions` for MongoDB Extended JSON's *Strict
362-
mode* encoding.
382+
DEFAULT_JSON_OPTIONS = RELAXED_JSON_OPTIONS
383+
"""The default :class:`JSONOptions` for JSON encoding/decoding.
363384
364-
.. versionadded:: 3.4
385+
The same as :const:`RELAXED_JSON_OPTIONS`.
386+
387+
.. versionchanged:: 4.0
388+
Changed from :const:`LEGACY_JSON_OPTIONS` to
389+
:const:`RELAXED_JSON_OPTIONS`.
365390
366-
.. versionchanged:: 3.5
367-
Deprecated. Use :const:`RELAXED_JSON_OPTIONS` or
368-
:const:`CANONICAL_JSON_OPTIONS` instead.
391+
.. versionadded:: 3.4
369392
"""
370393

371394

@@ -380,6 +403,10 @@ def dumps(obj, *args, **kwargs):
380403
encoding of MongoDB Extended JSON types. Defaults to
381404
:const:`DEFAULT_JSON_OPTIONS`.
382405
406+
.. versionchanged:: 4.0
407+
Now outputs MongoDB Relaxed Extended JSON by default (using
408+
:const:`DEFAULT_JSON_OPTIONS`).
409+
383410
.. versionchanged:: 3.4
384411
Accepts optional parameter `json_options`. See :class:`JSONOptions`.
385412
"""

doc/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ Breaking Changes in 4.0
9898
- Removed :exc:`pymongo.errors.CertificateError`.
9999
- Removed :attr:`pymongo.GEOHAYSTACK`.
100100
- Removed :class:`bson.binary.UUIDLegacy`.
101+
- Removed :const:`bson.json_util.STRICT_JSON_OPTIONS`. Use
102+
:const:`~bson.json_util.RELAXED_JSON_OPTIONS` or
103+
:const:`~bson.json_util.CANONICAL_JSON_OPTIONS` instead.
104+
- Changed the default JSON encoding representation from legacy to relaxed.
105+
The json_mode parameter for :const:`bson.json_util.dumps` now defaults to
106+
:const:`~bson.json_util.RELAXED_JSON_OPTIONS`.
101107
- The "tls" install extra is no longer necessary or supported and will be
102108
ignored by pip.
103109
- ``directConnection`` URI option and keyword argument to :class:`~pymongo

doc/migrate-to-pymongo3.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,7 @@ modifier. Code like this::
124124
# Set a 5 second select() timeout.
125125
>>> cursor = collection.find({"a": 1}, network_timeout=5)
126126

127-
can be changed to this with PyMongo 2.9 or later:
128-
129-
.. doctest::
127+
can be changed to this with PyMongo 2.9 or later::
130128

131129
# Set a 5 second (5000 millisecond) server side query timeout.
132130
>>> cursor = collection.find({"a": 1}, modifiers={"$maxTimeMS": 5000})

doc/migrate-to-pymongo4.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,13 @@ can be changed to this::
685685
uu = uuid.uuid4()
686686
uuid_legacy = Binary.from_uuid(uu, PYTHON_LEGACY)
687687

688+
Default JSONMode changed from LEGACY to RELAXED
689+
-----------------------------------------------
690+
691+
Changed the default JSON encoding representation from legacy to relaxed.
692+
The json_mode parameter for :const:`bson.json_util.dumps` now defaults to
693+
:const:`~bson.json_util.RELAXED_JSON_OPTIONS`.
694+
688695
Removed features with no migration path
689696
---------------------------------------
690697

gridfs/grid_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ def seekable(self):
658658
def __iter__(self):
659659
"""Return an iterator over all of this file's data.
660660
661-
The iterator will return lines (delimited by b'\n') of
661+
The iterator will return lines (delimited by ``b'\\n'``) of
662662
:class:`bytes`. This can be useful when serving files
663663
using a webserver that handles such an iterator efficiently.
664664

test/test_bson_corpus.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
json_options_uuid_04 = json_util.JSONOptions(json_mode=JSONMode.CANONICAL,
7272
uuid_representation=STANDARD)
7373
json_options_iso8601 = json_util.JSONOptions(
74-
datetime_representation=json_util.DatetimeRepresentation.ISO8601)
74+
datetime_representation=json_util.DatetimeRepresentation.ISO8601,
75+
json_mode=JSONMode.LEGACY)
7576
to_extjson = functools.partial(json_util.dumps,
7677
json_options=json_util.CANONICAL_JSON_OPTIONS)
7778
to_extjson_uuid_04 = functools.partial(json_util.dumps,

0 commit comments

Comments
 (0)