From e3771587c37d5ebe715c907c8b7115dc83196601 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 14 Sep 2021 16:54:11 -0700 Subject: [PATCH] PYTHON-1949 CodecOptions and JSONOptions should have the same default value for tz_aware (#720) --- bson/json_util.py | 7 +-- doc/changelog.rst | 4 ++ doc/migrate-to-pymongo4.rst | 8 ++++ test/test_bson_corpus.py | 3 +- test/test_json_util.py | 85 +++++++++++++++---------------------- 5 files changed, 53 insertions(+), 54 deletions(-) diff --git a/bson/json_util.py b/bson/json_util.py index 557388a51..cc6dfbace 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -226,7 +226,7 @@ class JSONOptions(CodecOptions): - `tz_aware`: If ``True``, MongoDB Extended JSON's *Strict mode* type `Date` will be decoded to timezone aware instances of :class:`datetime.datetime`. Otherwise they will be naive. Defaults - to ``True``. + to ``False``. - `tzinfo`: A :class:`datetime.tzinfo` subclass that specifies the timezone from which :class:`~datetime.datetime` objects should be decoded. Defaults to :const:`~bson.tz_util.utc`. @@ -245,14 +245,15 @@ class JSONOptions(CodecOptions): .. versionchanged:: 3.5 Accepts the optional parameter `json_mode`. - .. versionadded:: 3.4 + .. versionchanged:: 4.0 + Changed default value of `tz_aware` to False. """ def __new__(cls, strict_number_long=None, datetime_representation=None, strict_uuid=None, json_mode=JSONMode.RELAXED, *args, **kwargs): - kwargs["tz_aware"] = kwargs.get("tz_aware", True) + kwargs["tz_aware"] = kwargs.get("tz_aware", False) if kwargs["tz_aware"]: kwargs["tzinfo"] = kwargs.get("tzinfo", utc) if datetime_representation not in (DatetimeRepresentation.LEGACY, diff --git a/doc/changelog.rst b/doc/changelog.rst index 6adff2e71..ea8cd19ba 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -123,6 +123,9 @@ Breaking Changes in 4.0 :class:`~bson.dbref.DBRef`. - The "tls" install extra is no longer necessary or supported and will be ignored by pip. +- ``tz_aware``, an argument for :class:`~bson.json_util.JSONOptions`, + now defaults to ``False`` instead of ``True``. ``json_util.loads`` now +decodes datetime as naive by default. - ``directConnection`` URI option and keyword argument to :class:`~pymongo.mongo_client.MongoClient` defaults to ``False`` instead of ``None``, allowing for the automatic discovery of replica sets. This means that if you @@ -132,6 +135,7 @@ Breaking Changes in 4.0 with :meth:`~pymongo.collection.Collection.find`. - ``name`` is now a required argument for the :class:`pymongo.driver_info.DriverInfo` class. + d Notable improvements .................... diff --git a/doc/migrate-to-pymongo4.rst b/doc/migrate-to-pymongo4.rst index 2baac8eee..8300a0991 100644 --- a/doc/migrate-to-pymongo4.rst +++ b/doc/migrate-to-pymongo4.rst @@ -183,6 +183,14 @@ can be changed to this:: names = client.list_database_names() +``tz_aware`` defaults to ``False`` +.................................. + +``tz_aware``, an argument for :class:`~bson.json_util.JSONOptions`, +now defaults to ``False`` instead of ``True``. ``json_util.loads`` now +decodes datetime as naive by default. + + Database -------- diff --git a/test/test_bson_corpus.py b/test/test_bson_corpus.py index 7893395c6..cbb702e40 100644 --- a/test/test_bson_corpus.py +++ b/test/test_bson_corpus.py @@ -74,6 +74,7 @@ _DEPRECATED_BSON_TYPES = { # Need to set tz_aware=True in order to use "strict" dates in extended JSON. codec_options = CodecOptions(tz_aware=True, document_class=SON) +codec_options_no_tzaware = CodecOptions(document_class=SON) # We normally encode UUID as binary subtype 0x03, # but we'll need to encode to subtype 0x04 for one of the tests. codec_options_uuid_04 = codec_options._replace(uuid_representation=STANDARD) @@ -93,7 +94,7 @@ to_relaxed_extjson = functools.partial( to_bson_uuid_04 = functools.partial(encode, codec_options=codec_options_uuid_04) to_bson = functools.partial(encode, codec_options=codec_options) -decode_bson = lambda bbytes: decode(bbytes, codec_options=codec_options) +decode_bson = functools.partial(decode, codec_options=codec_options_no_tzaware) decode_extjson = functools.partial( json_util.loads, json_options=json_util.JSONOptions(json_mode=JSONMode.CANONICAL, diff --git a/test/test_json_util.py b/test/test_json_util.py index 791e3de18..f28b75c9b 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -22,7 +22,7 @@ import uuid sys.path[0:0] = [""] -from bson import json_util, EPOCH_AWARE, SON +from bson import json_util, EPOCH_AWARE, EPOCH_NAIVE, SON from bson.json_util import (DatetimeRepresentation, JSONMode, JSONOptions, @@ -103,61 +103,45 @@ class TestJsonUtil(unittest.TestCase): json_util.dumps(DBRef('collection', 1, 'db'))) def test_datetime(self): + tz_aware_opts = json_util.DEFAULT_JSON_OPTIONS.with_options( + tz_aware=True) # only millis, not micros + self.round_trip({"date": datetime.datetime(2009, 12, 9, 15, 49, 45, + 191000, utc)}, json_options=tz_aware_opts) self.round_trip({"date": datetime.datetime(2009, 12, 9, 15, - 49, 45, 191000, utc)}) + 49, 45, 191000)}) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000+0000"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000000+0000"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000+00:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000000+00:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000000+00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000Z"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000000Z"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00Z"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - # No explicit offset - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T00:00:00.000000"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - # Localtime behind UTC - jsn = '{"dt": { "$date" : "1969-12-31T16:00:00.000-0800"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1969-12-31T16:00:00.000000-0800"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1969-12-31T16:00:00.000-08:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1969-12-31T16:00:00.000000-08:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1969-12-31T16:00:00.000000-08"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - # Localtime ahead of UTC - jsn = '{"dt": { "$date" : "1970-01-01T01:00:00.000+0100"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T01:00:00.000000+0100"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T01:00:00.000+01:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T01:00:00.000000+01:00"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) - jsn = '{"dt": { "$date" : "1970-01-01T01:00:00.000000+01"}}' - self.assertEqual(EPOCH_AWARE, json_util.loads(jsn)["dt"]) + for jsn in ['{"dt": { "$date" : "1970-01-01T00:00:00.000+0000"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000000+0000"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000+00:00"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000000+00:00"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000000+00"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000Z"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000000Z"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00Z"}}', + '{"dt": {"$date": "1970-01-01T00:00:00.000"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00"}}', + '{"dt": { "$date" : "1970-01-01T00:00:00.000000"}}', + '{"dt": { "$date" : "1969-12-31T16:00:00.000-0800"}}', + '{"dt": { "$date" : "1969-12-31T16:00:00.000000-0800"}}', + '{"dt": { "$date" : "1969-12-31T16:00:00.000-08:00"}}', + '{"dt": { "$date" : "1969-12-31T16:00:00.000000-08:00"}}', + '{"dt": { "$date" : "1969-12-31T16:00:00.000000-08"}}', + '{"dt": { "$date" : "1970-01-01T01:00:00.000+0100"}}', + '{"dt": { "$date" : "1970-01-01T01:00:00.000000+0100"}}', + '{"dt": { "$date" : "1970-01-01T01:00:00.000+01:00"}}', + '{"dt": { "$date" : "1970-01-01T01:00:00.000000+01:00"}}', + '{"dt": { "$date" : "1970-01-01T01:00:00.000000+01"}}' + ]: + self.assertEqual(EPOCH_AWARE, json_util.loads( + jsn, json_options=tz_aware_opts)["dt"]) + self.assertEqual(EPOCH_NAIVE, json_util.loads(jsn)["dt"]) dtm = datetime.datetime(1, 1, 1, 1, 1, 1, 0, utc) jsn = '{"dt": {"$date": -62135593139000}}' - self.assertEqual(dtm, json_util.loads(jsn)["dt"]) + self.assertEqual(dtm, json_util.loads(jsn, json_options=tz_aware_opts)["dt"]) jsn = '{"dt": {"$date": {"$numberLong": "-62135593139000"}}}' - self.assertEqual(dtm, json_util.loads(jsn)["dt"]) + self.assertEqual(dtm, json_util.loads(jsn, json_options=tz_aware_opts)["dt"]) # Test dumps format pre_epoch = {"dt": datetime.datetime(1, 1, 1, 1, 1, 1, 10000, utc)} @@ -207,7 +191,8 @@ class TestJsonUtil(unittest.TestCase): self.assertEqual( datetime.datetime(1972, 1, 1, 1, 1, 1, 10000, utc), json_util.loads( - '{"dt": {"$date": "1972-01-01T01:01:01.010+0000"}}')["dt"]) + '{"dt": {"$date": "1972-01-01T01:01:01.010+0000"}}', + json_options=tz_aware_opts)["dt"]) self.assertEqual( datetime.datetime(1972, 1, 1, 1, 1, 1, 10000, utc), json_util.loads(