diff --git a/0001-PYTHON-1918-Stop-using-BSON.encode-and-BSON.decode-f.patch b/0001-PYTHON-1918-Stop-using-BSON.encode-and-BSON.decode-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..58ea8132d85ee7413ba6b67629707c9b0cfc3aea --- /dev/null +++ b/0001-PYTHON-1918-Stop-using-BSON.encode-and-BSON.decode-f.patch @@ -0,0 +1,1335 @@ +From ef3df1663cb33cad857f5c7f9fce5565b25bd268 Mon Sep 17 00:00:00 2001 +From: Prashant Mital +Date: Fri, 9 Aug 2019 15:04:06 -0700 +Subject: [PATCH] PYTHON-1918 Stop using BSON.encode and BSON.decode functions + +--- + bson/raw_bson.py | 3 +- + doc/migrate-to-pymongo3.rst | 9 +- + pymongo/message.py | 6 +- + test/performance/perf_test.py | 6 +- + test/test_binary.py | 57 +++---- + test/test_bson.py | 311 +++++++++++++++++----------------- + test/test_bson_corpus.py | 8 +- + test/test_change_stream.py | 4 +- + test/test_client.py | 4 +- + test/test_collection.py | 9 +- + test/test_custom_types.py | 41 ++--- + test/test_encryption.py | 4 +- + test/test_raw_bson.py | 16 +- + test/unicode/test_utf8.py | 4 +- + 14 files changed, 237 insertions(+), 245 deletions(-) + +diff --git a/bson/raw_bson.py b/bson/raw_bson.py +index f83ac40d..6a7cf504 100644 +--- a/bson/raw_bson.py ++++ b/bson/raw_bson.py +@@ -45,7 +45,8 @@ class RawBSONDocument(abc.Mapping): + class from the standard library so it can be used like a read-only + ``dict``:: + +- >>> raw_doc = RawBSONDocument(BSON.encode({'_id': 'my_doc'})) ++ >>> from bson import encode ++ >>> raw_doc = RawBSONDocument(encode({'_id': 'my_doc'})) + >>> raw_doc.raw + b'...' + >>> raw_doc['_id'] +diff --git a/doc/migrate-to-pymongo3.rst b/doc/migrate-to-pymongo3.rst +index 85f495ea..bb396dde 100644 +--- a/doc/migrate-to-pymongo3.rst ++++ b/doc/migrate-to-pymongo3.rst +@@ -492,7 +492,10 @@ BSON + ...................................................... + + The `as_class`, `tz_aware`, and `uuid_subtype` parameters have been +-removed from the functions provided in :mod:`bson`. Code like this:: ++removed from the functions provided in :mod:`bson`. Furthermore, the ++:func:`~bson.encode` and :func:`~bson.decode` functions have been added ++as more performant alternatives to the :meth:`bson.BSON.encode` and ++:meth:`bson.BSON.decode` methods. Code like this:: + + >>> from bson import BSON + >>> from bson.son import SON +@@ -502,10 +505,10 @@ can be replaced by this in PyMongo 2.9 or later: + + .. doctest:: + +- >>> from bson import BSON ++ >>> from bson import encode + >>> from bson.codec_options import CodecOptions + >>> from bson.son import SON +- >>> encoded = BSON.encode({"a": 1}, codec_options=CodecOptions(SON)) ++ >>> encoded = encode({"a": 1}, codec_options=CodecOptions(SON)) + + Removed features with no migration path + --------------------------------------- +diff --git a/pymongo/message.py b/pymongo/message.py +index fae75a4d..56f86369 100644 +--- a/pymongo/message.py ++++ b/pymongo/message.py +@@ -26,6 +26,8 @@ import struct + + import bson + from bson import (CodecOptions, ++ decode, ++ encode, + _dict_to_bson, + _make_c_string) + from bson.codec_options import DEFAULT_CODEC_OPTIONS +@@ -1397,7 +1399,7 @@ def _batched_write_command_impl( + + # Where to write command document length + command_start = buf.tell() +- buf.write(bson.BSON.encode(command)) ++ buf.write(encode(command)) + + # Start of payload + buf.seek(-1, 2) +@@ -1418,7 +1420,7 @@ def _batched_write_command_impl( + for doc in docs: + # Encode the current operation + key = b(str(idx)) +- value = bson.BSON.encode(doc, check_keys, opts) ++ value = encode(doc, check_keys, opts) + # Is there enough room to add this document? max_cmd_size accounts for + # the two trailing null bytes. + doc_too_large = len(value) > max_cmd_size +diff --git a/test/performance/perf_test.py b/test/performance/perf_test.py +index 392c64ca..536b5441 100644 +--- a/test/performance/perf_test.py ++++ b/test/performance/perf_test.py +@@ -27,7 +27,7 @@ except ImportError: + + sys.path[0:0] = [""] + +-from bson import BSON ++from bson import encode + from bson.json_util import loads + from gridfs import GridFSBucket + from pymongo import MongoClient +@@ -133,7 +133,7 @@ class BsonEncodingTest(PerformanceTest): + + def do_task(self): + for _ in range(NUM_DOCS): +- BSON.encode(self.document) ++ encode(self.document) + + + class BsonDecodingTest(PerformanceTest): +@@ -142,7 +142,7 @@ class BsonDecodingTest(PerformanceTest): + with open( + os.path.join(TEST_PATH, + os.path.join('extended_bson', self.dataset))) as data: +- self.document = BSON.encode(json.loads(data.read())) ++ self.document = encode(json.loads(data.read())) + + def do_task(self): + for _ in range(NUM_DOCS): +diff --git a/test/test_binary.py b/test/test_binary.py +index b5fbb92a..392cd97c 100644 +--- a/test/test_binary.py ++++ b/test/test_binary.py +@@ -26,6 +26,7 @@ sys.path[0:0] = [""] + + import bson + ++from bson import decode, encode + from bson.binary import * + from bson.codec_options import CodecOptions + from bson.py3compat import PY3 +@@ -146,10 +147,10 @@ class TestBinary(unittest.TestCase): + """uuid_representation should be ignored when decoding subtype 4.""" + expected_uuid = uuid.uuid4() + doc = {"uuid": Binary(expected_uuid.bytes, 4)} +- encoded = bson.BSON.encode(doc) ++ encoded = encode(doc) + for uuid_representation in ALL_UUID_REPRESENTATIONS: + options = CodecOptions(uuid_representation=uuid_representation) +- self.assertEqual(expected_uuid, encoded.decode(options)["uuid"]) ++ self.assertEqual(expected_uuid, decode(encoded, options)["uuid"]) + + def test_legacy_java_uuid(self): + # Test decoding +@@ -172,31 +173,23 @@ class TestBinary(unittest.TestCase): + + # Test encoding + encoded = b''.join([ +- bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=PYTHON_LEGACY)) ++ encode(doc, False, CodecOptions(uuid_representation=PYTHON_LEGACY)) + for doc in docs]) + self.assertNotEqual(data, encoded) + +- encoded = b''.join( +- [bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=STANDARD)) +- for doc in docs]) ++ encoded = b''.join([ ++ encode(doc, False, CodecOptions(uuid_representation=STANDARD)) ++ for doc in docs]) + self.assertNotEqual(data, encoded) + +- encoded = b''.join( +- [bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=CSHARP_LEGACY)) +- for doc in docs]) ++ encoded = b''.join([ ++ encode(doc, False, CodecOptions(uuid_representation=CSHARP_LEGACY)) ++ for doc in docs]) + self.assertNotEqual(data, encoded) + +- encoded = b''.join( +- [bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=JAVA_LEGACY)) +- for doc in docs]) ++ encoded = b''.join([ ++ encode(doc, False, CodecOptions(uuid_representation=JAVA_LEGACY)) ++ for doc in docs]) + self.assertEqual(data, encoded) + + @client_context.require_connection +@@ -242,31 +235,23 @@ class TestBinary(unittest.TestCase): + + # Test encoding + encoded = b''.join([ +- bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=PYTHON_LEGACY)) ++ encode(doc, False, CodecOptions(uuid_representation=PYTHON_LEGACY)) + for doc in docs]) + self.assertNotEqual(data, encoded) + + encoded = b''.join([ +- bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=STANDARD)) ++ encode(doc, False, CodecOptions(uuid_representation=STANDARD)) + for doc in docs]) + self.assertNotEqual(data, encoded) + +- encoded = b''.join( +- [bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=JAVA_LEGACY)) +- for doc in docs]) ++ encoded = b''.join([ ++ encode(doc, False, CodecOptions(uuid_representation=JAVA_LEGACY)) ++ for doc in docs]) + self.assertNotEqual(data, encoded) + +- encoded = b''.join( +- [bson.BSON.encode(doc, +- False, +- CodecOptions(uuid_representation=CSHARP_LEGACY)) +- for doc in docs]) ++ encoded = b''.join([ ++ encode(doc, False, CodecOptions(uuid_representation=CSHARP_LEGACY)) ++ for doc in docs]) + self.assertEqual(data, encoded) + + @client_context.require_connection +diff --git a/test/test_bson.py b/test/test_bson.py +index 3380ff0c..0e1b8e1b 100644 +--- a/test/test_bson.py ++++ b/test/test_bson.py +@@ -116,23 +116,23 @@ class DSTAwareTimezone(datetime.tzinfo): + + class TestBSON(unittest.TestCase): + def assertInvalid(self, data): +- self.assertRaises(InvalidBSON, bson.BSON(data).decode) ++ self.assertRaises(InvalidBSON, decode, data) + +- def check_encode_then_decode(self, doc_class=dict): ++ def check_encode_then_decode(self, doc_class=dict, decoder=decode, ++ encoder=encode): + + # Work around http://bugs.jython.org/issue1728 + if sys.platform.startswith('java'): + doc_class = SON + + def helper(doc): +- self.assertEqual(doc, (BSON.encode(doc_class(doc))).decode()) +- self.assertEqual(doc, decode(encode(doc))) ++ self.assertEqual(doc, (decoder(encoder(doc_class(doc))))) ++ self.assertEqual(doc, decoder(encoder(doc))) + + helper({}) + helper({"test": u"hello"}) +- self.assertTrue(isinstance(BSON.encode({"hello": "world"}) +- .decode()["hello"], +- text_type)) ++ self.assertTrue(isinstance(decoder(encoder( ++ {"hello": "world"}))["hello"], text_type)) + helper({"mike": -10120}) + helper({"long": Int64(10)}) + helper({"really big long": 2147483648}) +@@ -160,8 +160,8 @@ class TestBSON(unittest.TestCase): + helper({"$field": Code("return function(){ return x; }", scope={'x': False})}) + + def encode_then_decode(doc): +- return doc_class(doc) == BSON.encode(doc).decode( +- CodecOptions(document_class=doc_class)) ++ return doc_class(doc) == decoder(encode(doc), CodecOptions( ++ document_class=doc_class)) + + qcheck.check_unittest(self, encode_then_decode, + qcheck.gen_mongo_dict(3)) +@@ -172,9 +172,19 @@ class TestBSON(unittest.TestCase): + def test_encode_then_decode_any_mapping(self): + self.check_encode_then_decode(doc_class=NotADict) + ++ def test_encode_then_decode_legacy(self): ++ self.check_encode_then_decode( ++ encoder=BSON.encode, ++ decoder=lambda *args: BSON(args[0]).decode(*args[1:])) ++ ++ def test_encode_then_decode_any_mapping_legacy(self): ++ self.check_encode_then_decode( ++ doc_class=NotADict, encoder=BSON.encode, ++ decoder=lambda *args: BSON(args[0]).decode(*args[1:])) ++ + def test_encoding_defaultdict(self): + dct = collections.defaultdict(dict, [('foo', 'bar')]) +- BSON.encode(dct) ++ encode(dct) + self.assertEqual(dct, collections.defaultdict(dict, [('foo', 'bar')])) + + def test_basic_validation(self): +@@ -266,9 +276,9 @@ class TestBSON(unittest.TestCase): + + def test_basic_decode(self): + self.assertEqual({"test": u"hello world"}, +- BSON(b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74\x00\x0C" +- b"\x00\x00\x00\x68\x65\x6C\x6C\x6F\x20\x77\x6F" +- b"\x72\x6C\x64\x00\x00").decode()) ++ decode(b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74\x00\x0C" ++ b"\x00\x00\x00\x68\x65\x6C\x6C\x6F\x20\x77\x6F" ++ b"\x72\x6C\x64\x00\x00")) + self.assertEqual([{"test": u"hello world"}, {}], + decode_all(b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" + b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" +@@ -289,7 +299,7 @@ class TestBSON(unittest.TestCase): + + def test_decode_all_buffer_protocol(self): + docs = [{'foo': 'bar'}, {}] +- bs = b"".join(map(BSON.encode, docs)) ++ bs = b"".join(map(encode, docs)) + self.assertEqual(docs, decode_all(bytearray(bs))) + self.assertEqual(docs, decode_all(memoryview(bs))) + self.assertEqual(docs, decode_all(memoryview(b'1' + bs + b'1')[1:-1])) +@@ -363,79 +373,80 @@ class TestBSON(unittest.TestCase): + + def test_data_timestamp(self): + self.assertEqual({"test": Timestamp(4, 20)}, +- BSON(b"\x13\x00\x00\x00\x11\x74\x65\x73\x74\x00\x14" +- b"\x00\x00\x00\x04\x00\x00\x00\x00").decode()) ++ decode(b"\x13\x00\x00\x00\x11\x74\x65\x73\x74\x00\x14" ++ b"\x00\x00\x00\x04\x00\x00\x00\x00")) + + def test_basic_encode(self): +- self.assertRaises(TypeError, BSON.encode, 100) +- self.assertRaises(TypeError, BSON.encode, "hello") +- self.assertRaises(TypeError, BSON.encode, None) +- self.assertRaises(TypeError, BSON.encode, []) +- +- self.assertEqual(BSON.encode({}), BSON(b"\x05\x00\x00\x00\x00")) +- self.assertEqual(BSON.encode({"test": u"hello world"}), ++ self.assertRaises(TypeError, encode, 100) ++ self.assertRaises(TypeError, encode, "hello") ++ self.assertRaises(TypeError, encode, None) ++ self.assertRaises(TypeError, encode, []) ++ ++ self.assertEqual(encode({}), BSON(b"\x05\x00\x00\x00\x00")) ++ self.assertEqual(encode({}), b"\x05\x00\x00\x00\x00") ++ self.assertEqual(encode({"test": u"hello world"}), + b"\x1B\x00\x00\x00\x02\x74\x65\x73\x74\x00\x0C\x00" + b"\x00\x00\x68\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C" + b"\x64\x00\x00") +- self.assertEqual(BSON.encode({u"mike": 100}), ++ self.assertEqual(encode({u"mike": 100}), + b"\x0F\x00\x00\x00\x10\x6D\x69\x6B\x65\x00\x64\x00" + b"\x00\x00\x00") +- self.assertEqual(BSON.encode({"hello": 1.5}), ++ self.assertEqual(encode({"hello": 1.5}), + b"\x14\x00\x00\x00\x01\x68\x65\x6C\x6C\x6F\x00\x00" + b"\x00\x00\x00\x00\x00\xF8\x3F\x00") +- self.assertEqual(BSON.encode({"true": True}), ++ self.assertEqual(encode({"true": True}), + b"\x0C\x00\x00\x00\x08\x74\x72\x75\x65\x00\x01\x00") +- self.assertEqual(BSON.encode({"false": False}), ++ self.assertEqual(encode({"false": False}), + b"\x0D\x00\x00\x00\x08\x66\x61\x6C\x73\x65\x00\x00" + b"\x00") +- self.assertEqual(BSON.encode({"empty": []}), ++ self.assertEqual(encode({"empty": []}), + b"\x11\x00\x00\x00\x04\x65\x6D\x70\x74\x79\x00\x05" + b"\x00\x00\x00\x00\x00") +- self.assertEqual(BSON.encode({"none": {}}), ++ self.assertEqual(encode({"none": {}}), + b"\x10\x00\x00\x00\x03\x6E\x6F\x6E\x65\x00\x05\x00" + b"\x00\x00\x00\x00") +- self.assertEqual(BSON.encode({"test": Binary(b"test", 0)}), ++ self.assertEqual(encode({"test": Binary(b"test", 0)}), + b"\x14\x00\x00\x00\x05\x74\x65\x73\x74\x00\x04\x00" + b"\x00\x00\x00\x74\x65\x73\x74\x00") +- self.assertEqual(BSON.encode({"test": Binary(b"test", 2)}), ++ self.assertEqual(encode({"test": Binary(b"test", 2)}), + b"\x18\x00\x00\x00\x05\x74\x65\x73\x74\x00\x08\x00" + b"\x00\x00\x02\x04\x00\x00\x00\x74\x65\x73\x74\x00") +- self.assertEqual(BSON.encode({"test": Binary(b"test", 128)}), ++ self.assertEqual(encode({"test": Binary(b"test", 128)}), + b"\x14\x00\x00\x00\x05\x74\x65\x73\x74\x00\x04\x00" + b"\x00\x00\x80\x74\x65\x73\x74\x00") +- self.assertEqual(BSON.encode({"test": None}), ++ self.assertEqual(encode({"test": None}), + b"\x0B\x00\x00\x00\x0A\x74\x65\x73\x74\x00\x00") +- self.assertEqual(BSON.encode({"date": datetime.datetime(2007, 1, 8, ++ self.assertEqual(encode({"date": datetime.datetime(2007, 1, 8, + 0, 30, 11)}), + b"\x13\x00\x00\x00\x09\x64\x61\x74\x65\x00\x38\xBE" + b"\x1C\xFF\x0F\x01\x00\x00\x00") +- self.assertEqual(BSON.encode({"regex": re.compile(b"a*b", ++ self.assertEqual(encode({"regex": re.compile(b"a*b", + re.IGNORECASE)}), + b"\x12\x00\x00\x00\x0B\x72\x65\x67\x65\x78\x00\x61" + b"\x2A\x62\x00\x69\x00\x00") +- self.assertEqual(BSON.encode({"$where": Code("test")}), ++ self.assertEqual(encode({"$where": Code("test")}), + b"\x16\x00\x00\x00\r$where\x00\x05\x00\x00\x00test" + b"\x00\x00") +- self.assertEqual(BSON.encode({"$field": ++ self.assertEqual(encode({"$field": + Code("function(){ return true;}", scope=None)}), + b"+\x00\x00\x00\r$field\x00\x1a\x00\x00\x00" + b"function(){ return true;}\x00\x00") +- self.assertEqual(BSON.encode({"$field": ++ self.assertEqual(encode({"$field": + Code("return function(){ return x; }", + scope={'x': False})}), + b"=\x00\x00\x00\x0f$field\x000\x00\x00\x00\x1f\x00" + b"\x00\x00return function(){ return x; }\x00\t\x00" + b"\x00\x00\x08x\x00\x00\x00\x00") + unicode_empty_scope = Code(u"function(){ return 'héllo';}", {}) +- self.assertEqual(BSON.encode({'$field': unicode_empty_scope}), ++ self.assertEqual(encode({'$field': unicode_empty_scope}), + b"8\x00\x00\x00\x0f$field\x00+\x00\x00\x00\x1e\x00" + b"\x00\x00function(){ return 'h\xc3\xa9llo';}\x00\x05" + b"\x00\x00\x00\x00\x00") + a = ObjectId(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B") +- self.assertEqual(BSON.encode({"oid": a}), ++ self.assertEqual(encode({"oid": a}), + b"\x16\x00\x00\x00\x07\x6F\x69\x64\x00\x00\x01\x02" + b"\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x00") +- self.assertEqual(BSON.encode({"ref": DBRef("coll", a)}), ++ self.assertEqual(encode({"ref": DBRef("coll", a)}), + b"\x2F\x00\x00\x00\x03ref\x00\x25\x00\x00\x00\x02" + b"$ref\x00\x05\x00\x00\x00coll\x00\x07$id\x00\x00" + b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x00" +@@ -452,7 +463,7 @@ class TestBSON(unittest.TestCase): + b'\x00\x14foo\x00\x01\x00\x00\x00\x00\x00\x00')] + for bs in docs: + try: +- bson.BSON(bs).decode() ++ decode(bs) + except Exception as exc: + self.assertTrue(isinstance(exc, InvalidBSON)) + self.assertTrue(part in str(exc)) +@@ -470,15 +481,15 @@ class TestBSON(unittest.TestCase): + b"\x00\x00RY\xb5j\xfa[\xd8A\xd6X]\x99\x00") + + self.assertEqual({'': DBRef('', ObjectId('5259b56afa5bd841d6585d99'))}, +- bson.BSON(bs).decode()) ++ decode(bs)) + + def test_bad_dbref(self): + ref_only = {'ref': {'$ref': 'collection'}} + id_only = {'ref': {'$id': ObjectId()}} + + self.assertEqual(DBRef('collection', id=None), +- BSON.encode(ref_only).decode()['ref']) +- self.assertEqual(id_only, BSON.encode(id_only).decode()) ++ decode(encode(ref_only))['ref']) ++ self.assertEqual(id_only, decode(encode(id_only))) + + def test_bytes_as_keys(self): + doc = {b"foo": 'bar'} +@@ -486,33 +497,33 @@ class TestBSON(unittest.TestCase): + # as keys in python 3.x. Using binary data as a key makes + # no sense in BSON anyway and little sense in python. + if PY3: +- self.assertRaises(InvalidDocument, BSON.encode, doc) ++ self.assertRaises(InvalidDocument, encode, doc) + else: +- self.assertTrue(BSON.encode(doc)) ++ self.assertTrue(encode(doc)) + + def test_datetime_encode_decode(self): + # Negative timestamps + dt1 = datetime.datetime(1, 1, 1, 1, 1, 1, 111000) +- dt2 = BSON.encode({"date": dt1}).decode()["date"] ++ dt2 = decode(encode({"date": dt1}))["date"] + self.assertEqual(dt1, dt2) + + dt1 = datetime.datetime(1959, 6, 25, 12, 16, 59, 999000) +- dt2 = BSON.encode({"date": dt1}).decode()["date"] ++ dt2 = decode(encode({"date": dt1}))["date"] + self.assertEqual(dt1, dt2) + + # Positive timestamps + dt1 = datetime.datetime(9999, 12, 31, 23, 59, 59, 999000) +- dt2 = BSON.encode({"date": dt1}).decode()["date"] ++ dt2 = decode(encode({"date": dt1}))["date"] + self.assertEqual(dt1, dt2) + + dt1 = datetime.datetime(2011, 6, 14, 10, 47, 53, 444000) +- dt2 = BSON.encode({"date": dt1}).decode()["date"] ++ dt2 = decode(encode({"date": dt1}))["date"] + self.assertEqual(dt1, dt2) + + def test_large_datetime_truncation(self): + # Ensure that a large datetime is truncated correctly. + dt1 = datetime.datetime(9999, 1, 1, 1, 1, 1, 999999) +- dt2 = BSON.encode({"date": dt1}).decode()["date"] ++ dt2 = decode(encode({"date": dt1}))["date"] + self.assertEqual(dt2.microsecond, 999000) + self.assertEqual(dt2.second, dt1.second) + +@@ -522,8 +533,8 @@ class TestBSON(unittest.TestCase): + as_utc = (aware - aware.utcoffset()).replace(tzinfo=utc) + self.assertEqual(datetime.datetime(1993, 4, 3, 16, 45, tzinfo=utc), + as_utc) +- after = BSON.encode({"date": aware}).decode( +- CodecOptions(tz_aware=True))["date"] ++ after = decode(encode({"date": aware}), CodecOptions(tz_aware=True))[ ++ "date"] + self.assertEqual(utc, after.tzinfo) + self.assertEqual(as_utc, after) + +@@ -536,29 +547,27 @@ class TestBSON(unittest.TestCase): + tzinfo=tz) + options = CodecOptions(tz_aware=True, tzinfo=tz) + # Encode with this timezone, then decode to UTC. +- encoded = BSON.encode({'date': local}, codec_options=options) ++ encoded = encode({'date': local}, codec_options=options) + self.assertEqual(local.replace(hour=1, tzinfo=None), +- encoded.decode()['date']) ++ decode(encoded)['date']) + + # It's DST. + local = datetime.datetime(year=2025, month=4, hour=1, day=1, + tzinfo=tz) +- encoded = BSON.encode({'date': local}, codec_options=options) ++ encoded = encode({'date': local}, codec_options=options) + self.assertEqual(local.replace(month=3, day=31, hour=23, tzinfo=None), +- encoded.decode()['date']) ++ decode(encoded)['date']) + + # Encode UTC, then decode in a different timezone. +- encoded = BSON.encode({'date': local.replace(tzinfo=utc)}) +- decoded = encoded.decode(options)['date'] ++ encoded = encode({'date': local.replace(tzinfo=utc)}) ++ decoded = decode(encoded, options)['date'] + self.assertEqual(local.replace(hour=3), decoded) + self.assertEqual(tz, decoded.tzinfo) + + # Test round-tripping. + self.assertEqual( +- local, +- (BSON +- .encode({'date': local}, codec_options=options) +- .decode(options)['date'])) ++ local, decode(encode( ++ {'date': local}, codec_options=options), options)['date']) + + # Test around the Unix Epoch. + epochs = ( +@@ -572,25 +581,25 @@ class TestBSON(unittest.TestCase): + # We always retrieve datetimes in UTC unless told to do otherwise. + self.assertEqual( + EPOCH_AWARE, +- BSON.encode(doc).decode(codec_options=utc_co)['epoch']) ++ decode(encode(doc), codec_options=utc_co)['epoch']) + # Round-trip the epoch. + local_co = CodecOptions(tz_aware=True, tzinfo=epoch.tzinfo) + self.assertEqual( + epoch, +- BSON.encode(doc).decode(codec_options=local_co)['epoch']) ++ decode(encode(doc), codec_options=local_co)['epoch']) + + def test_naive_decode(self): + aware = datetime.datetime(1993, 4, 4, 2, + tzinfo=FixedOffset(555, "SomeZone")) + naive_utc = (aware - aware.utcoffset()).replace(tzinfo=None) + self.assertEqual(datetime.datetime(1993, 4, 3, 16, 45), naive_utc) +- after = BSON.encode({"date": aware}).decode()["date"] ++ after = decode(encode({"date": aware}))["date"] + self.assertEqual(None, after.tzinfo) + self.assertEqual(naive_utc, after) + + def test_dst(self): + d = {"x": datetime.datetime(1993, 4, 4, 2)} +- self.assertEqual(d, BSON.encode(d).decode()) ++ self.assertEqual(d, decode(encode(d))) + + def test_bad_encode(self): + if not PY3: +@@ -598,7 +607,7 @@ class TestBSON(unittest.TestCase): + # an exception. If we passed the string as bytes instead we + # still wouldn't get an error since we store bytes as BSON + # binary subtype 0. +- self.assertRaises(InvalidStringData, BSON.encode, ++ self.assertRaises(InvalidStringData, encode, + {"lalala": '\xf4\xe0\xf0\xe1\xc0 Color Touch'}) + # Work around what seems like a regression in python 3.5.0. + # See http://bugs.python.org/issue25222 +@@ -608,25 +617,25 @@ class TestBSON(unittest.TestCase): + evil_dict = {} + evil_dict['a'] = evil_dict + for evil_data in [evil_dict, evil_list]: +- self.assertRaises(Exception, BSON.encode, evil_data) ++ self.assertRaises(Exception, encode, evil_data) + + def test_overflow(self): +- self.assertTrue(BSON.encode({"x": long(9223372036854775807)})) +- self.assertRaises(OverflowError, BSON.encode, ++ self.assertTrue(encode({"x": long(9223372036854775807)})) ++ self.assertRaises(OverflowError, encode, + {"x": long(9223372036854775808)}) + +- self.assertTrue(BSON.encode({"x": long(-9223372036854775808)})) +- self.assertRaises(OverflowError, BSON.encode, ++ self.assertTrue(encode({"x": long(-9223372036854775808)})) ++ self.assertRaises(OverflowError, encode, + {"x": long(-9223372036854775809)}) + + def test_small_long_encode_decode(self): +- encoded1 = BSON.encode({'x': 256}) +- decoded1 = BSON(encoded1).decode()['x'] ++ encoded1 = encode({'x': 256}) ++ decoded1 = decode(encoded1)['x'] + self.assertEqual(256, decoded1) + self.assertEqual(type(256), type(decoded1)) + +- encoded2 = BSON.encode({'x': Int64(256)}) +- decoded2 = BSON(encoded2).decode()['x'] ++ encoded2 = encode({'x': Int64(256)}) ++ decoded2 = decode(encoded2)['x'] + expected = Int64(256) + self.assertEqual(expected, decoded2) + self.assertEqual(type(expected), type(decoded2)) +@@ -635,12 +644,12 @@ class TestBSON(unittest.TestCase): + + def test_tuple(self): + self.assertEqual({"tuple": [1, 2]}, +- BSON.encode({"tuple": (1, 2)}).decode()) ++ decode(encode({"tuple": (1, 2)}))) + + def test_uuid(self): + + id = uuid.uuid4() +- transformed_id = (BSON.encode({"id": id})).decode()["id"] ++ transformed_id = decode(encode({"id": id}))["id"] + + self.assertTrue(isinstance(transformed_id, uuid.UUID)) + self.assertEqual(id, transformed_id) +@@ -651,7 +660,7 @@ class TestBSON(unittest.TestCase): + id = uuid.uuid4() + legacy = UUIDLegacy(id) + self.assertEqual(3, legacy.subtype) +- transformed = (BSON.encode({"uuid": legacy})).decode()["uuid"] ++ transformed = decode(encode({"uuid": legacy}))["uuid"] + self.assertTrue(isinstance(transformed, uuid.UUID)) + self.assertEqual(id, transformed) + self.assertNotEqual(UUIDLegacy(uuid.uuid4()), UUIDLegacy(transformed)) +@@ -660,67 +669,67 @@ class TestBSON(unittest.TestCase): + # that doesn't really test anything but the lack of a segfault. + def test_unicode_regex(self): + regex = re.compile(u'revisi\xf3n') +- BSON.encode({"regex": regex}).decode() ++ decode(encode({"regex": regex})) + + def test_non_string_keys(self): +- self.assertRaises(InvalidDocument, BSON.encode, {8.9: "test"}) ++ self.assertRaises(InvalidDocument, encode, {8.9: "test"}) + + def test_utf8(self): + w = {u"aéあ": u"aéあ"} +- self.assertEqual(w, BSON.encode(w).decode()) ++ self.assertEqual(w, decode(encode(w))) + + # b'a\xe9' == u"aé".encode("iso-8859-1") + iso8859_bytes = b'a\xe9' + y = {"hello": iso8859_bytes} + if PY3: + # Stored as BSON binary subtype 0. +- out = BSON.encode(y).decode() ++ out = decode(encode(y)) + self.assertTrue(isinstance(out['hello'], bytes)) + self.assertEqual(out['hello'], iso8859_bytes) + else: + # Python 2. + try: +- BSON.encode(y) ++ encode(y) + except InvalidStringData as e: + self.assertTrue(repr(iso8859_bytes) in str(e)) + + # The next two tests only make sense in python 2.x since + # you can't use `bytes` type as document keys in python 3.x. + x = {u"aéあ".encode("utf-8"): u"aéあ".encode("utf-8")} +- self.assertEqual(w, BSON.encode(x).decode()) ++ self.assertEqual(w, decode(encode(x))) + + z = {iso8859_bytes: "hello"} +- self.assertRaises(InvalidStringData, BSON.encode, z) ++ self.assertRaises(InvalidStringData, encode, z) + + def test_null_character(self): + doc = {"a": "\x00"} +- self.assertEqual(doc, BSON.encode(doc).decode()) ++ self.assertEqual(doc, decode(encode(doc))) + + # This test doesn't make much sense in Python2 + # since {'a': '\x00'} == {'a': u'\x00'}. + # Decoding here actually returns {'a': '\x00'} + doc = {"a": u"\x00"} +- self.assertEqual(doc, BSON.encode(doc).decode()) ++ self.assertEqual(doc, decode(encode(doc))) + +- self.assertRaises(InvalidDocument, BSON.encode, {b"\x00": "a"}) +- self.assertRaises(InvalidDocument, BSON.encode, {u"\x00": "a"}) ++ self.assertRaises(InvalidDocument, encode, {b"\x00": "a"}) ++ self.assertRaises(InvalidDocument, encode, {u"\x00": "a"}) + +- self.assertRaises(InvalidDocument, BSON.encode, ++ self.assertRaises(InvalidDocument, encode, + {"a": re.compile(b"ab\x00c")}) +- self.assertRaises(InvalidDocument, BSON.encode, ++ self.assertRaises(InvalidDocument, encode, + {"a": re.compile(u"ab\x00c")}) + + def test_move_id(self): + self.assertEqual(b"\x19\x00\x00\x00\x02_id\x00\x02\x00\x00\x00a\x00" + b"\x02a\x00\x02\x00\x00\x00a\x00\x00", +- BSON.encode(SON([("a", "a"), ("_id", "a")]))) ++ encode(SON([("a", "a"), ("_id", "a")]))) + + self.assertEqual(b"\x2c\x00\x00\x00" + b"\x02_id\x00\x02\x00\x00\x00b\x00" + b"\x03b\x00" + b"\x19\x00\x00\x00\x02a\x00\x02\x00\x00\x00a\x00" + b"\x02_id\x00\x02\x00\x00\x00a\x00\x00\x00", +- BSON.encode(SON([("b", ++ encode(SON([("b", + SON([("a", "a"), ("_id", "a")])), + ("_id", "b")]))) + +@@ -728,7 +737,7 @@ class TestBSON(unittest.TestCase): + doc = {"early": datetime.datetime(1686, 5, 5), + "late": datetime.datetime(2086, 5, 5)} + try: +- self.assertEqual(doc, BSON.encode(doc).decode()) ++ self.assertEqual(doc, decode(encode(doc))) + except ValueError: + # Ignore ValueError when no C ext, since it's probably + # a problem w/ 32-bit Python - we work around this in the +@@ -737,20 +746,17 @@ class TestBSON(unittest.TestCase): + raise + + def test_custom_class(self): +- self.assertIsInstance(BSON.encode({}).decode(), dict) +- self.assertNotIsInstance(BSON.encode({}).decode(), SON) ++ self.assertIsInstance(decode(encode({})), dict) ++ self.assertNotIsInstance(decode(encode({})), SON) + self.assertIsInstance( +- BSON.encode({}).decode(CodecOptions(document_class=SON)), +- SON) ++ decode(encode({}), CodecOptions(document_class=SON)), SON) + + self.assertEqual( +- 1, +- BSON.encode({"x": 1}).decode( +- CodecOptions(document_class=SON))["x"]) ++ 1, decode(encode({"x": 1}), CodecOptions(document_class=SON))["x"]) + +- x = BSON.encode({"x": [{"y": 1}]}) ++ x = encode({"x": [{"y": 1}]}) + self.assertIsInstance( +- x.decode(CodecOptions(document_class=SON))["x"][0], SON) ++ decode(x, CodecOptions(document_class=SON))["x"][0], SON) + + def test_subclasses(self): + # make sure we can serialize subclasses of native Python types. +@@ -766,7 +772,7 @@ class TestBSON(unittest.TestCase): + d = {'a': _myint(42), 'b': _myfloat(63.9), + 'c': _myunicode('hello world') + } +- d2 = BSON.encode(d).decode() ++ d2 = decode(encode(d)) + for key, value in iteritems(d2): + orig_value = d[key] + orig_type = orig_value.__class__.__bases__[0] +@@ -780,8 +786,7 @@ class TestBSON(unittest.TestCase): + raise SkipTest("No OrderedDict") + d = OrderedDict([("one", 1), ("two", 2), ("three", 3), ("four", 4)]) + self.assertEqual( +- d, +- BSON.encode(d).decode(CodecOptions(document_class=OrderedDict))) ++ d, decode(encode(d), CodecOptions(document_class=OrderedDict))) + + def test_bson_regex(self): + # Invalid Python regex, though valid PCRE. +@@ -795,8 +800,8 @@ class TestBSON(unittest.TestCase): + b'\x0br\x00[\\w-\\.]\x00\x00' # r: regex + b'\x00') # document terminator + +- self.assertEqual(doc1_bson, BSON.encode(doc1)) +- self.assertEqual(doc1, BSON(doc1_bson).decode()) ++ self.assertEqual(doc1_bson, encode(doc1)) ++ self.assertEqual(doc1, decode(doc1_bson)) + + # Valid Python regex, with flags. + re2 = re.compile(u'.*', re.I | re.M | re.S | re.U | re.X) +@@ -809,11 +814,11 @@ class TestBSON(unittest.TestCase): + b"\x0br\x00.*\x00imsux\x00" # r: regex + b"\x00") # document terminator + +- self.assertEqual(doc2_bson, BSON.encode(doc2_with_re)) +- self.assertEqual(doc2_bson, BSON.encode(doc2_with_bson_re)) ++ self.assertEqual(doc2_bson, encode(doc2_with_re)) ++ self.assertEqual(doc2_bson, encode(doc2_with_bson_re)) + +- self.assertEqual(re2.pattern, BSON(doc2_bson).decode()['r'].pattern) +- self.assertEqual(re2.flags, BSON(doc2_bson).decode()['r'].flags) ++ self.assertEqual(re2.pattern, decode(doc2_bson)['r'].pattern) ++ self.assertEqual(re2.flags, decode(doc2_bson)['r'].flags) + + def test_regex_from_native(self): + self.assertEqual('.*', Regex.from_native(re.compile('.*')).pattern) +@@ -930,22 +935,22 @@ class TestBSON(unittest.TestCase): + doc_bson = (b'\x10\x00\x00\x00' + b'\x11a\x00\xff\xff\xff\xff\xff\xff\xff\xff' + b'\x00') +- self.assertEqual(doc_bson, BSON.encode(doc)) +- self.assertEqual(doc, BSON(doc_bson).decode()) ++ self.assertEqual(doc_bson, encode(doc)) ++ self.assertEqual(doc, decode(doc_bson)) + + def test_bad_id_keys(self): +- self.assertRaises(InvalidDocument, BSON.encode, ++ self.assertRaises(InvalidDocument, encode, + {"_id": {"$bad": 123}}, True) +- self.assertRaises(InvalidDocument, BSON.encode, ++ self.assertRaises(InvalidDocument, encode, + {"_id": {'$oid': "52d0b971b3ba219fdeb4170e"}}, True) +- BSON.encode({"_id": {'$oid': "52d0b971b3ba219fdeb4170e"}}) ++ encode({"_id": {'$oid': "52d0b971b3ba219fdeb4170e"}}) + + def test_bson_encode_thread_safe(self): + + def target(i): + for j in range(1000): + my_int = type('MyInt_%s_%s' % (i, j), (int,), {}) +- bson.BSON.encode({'my_int': my_int()}) ++ bson.encode({'my_int': my_int()}) + + threads = [ExceptionCatchingThread(target=target, args=(i,)) + for i in range(3)] +@@ -970,7 +975,7 @@ class TestBSON(unittest.TestCase): + with self.assertRaisesRegex( + InvalidDocument, + "cannot encode object: 1, of type: " + repr(Wrapper)): +- BSON.encode({'t': Wrapper(1)}) ++ encode({'t': Wrapper(1)}) + + + class TestCodecOptions(unittest.TestCase): +@@ -1011,77 +1016,73 @@ class TestCodecOptions(unittest.TestCase): + 'uuid': uuid.uuid4(), + 'dt': datetime.datetime.utcnow()} + +- decoded = bson.decode_all(bson.BSON.encode(doc))[0] ++ decoded = bson.decode_all(bson.encode(doc))[0] + self.assertIsInstance(decoded['sub_document'], dict) + self.assertEqual(decoded['uuid'], doc['uuid']) + self.assertIsNone(decoded['dt'].tzinfo) + + def test_unicode_decode_error_handler(self): +- enc = BSON.encode({"keystr": "foobar"}) ++ enc = encode({"keystr": "foobar"}) + + # Test handling of bad key value. +- invalid_key = BSON(enc[:7] + b'\xe9' + enc[8:]) ++ invalid_key = enc[:7] + b'\xe9' + enc[8:] + replaced_key = b'ke\xe9str'.decode('utf-8', 'replace') + ignored_key = b'ke\xe9str'.decode('utf-8', 'ignore') + +- dec = BSON(invalid_key).decode(CodecOptions( +- unicode_decode_error_handler="replace")) ++ dec = decode(invalid_key, ++ CodecOptions(unicode_decode_error_handler="replace")) + self.assertEqual(dec, {replaced_key: u"foobar"}) + +- dec = BSON(invalid_key).decode(CodecOptions( +- unicode_decode_error_handler="ignore")) ++ dec = decode(invalid_key, ++ CodecOptions(unicode_decode_error_handler="ignore")) + self.assertEqual(dec, {ignored_key: u"foobar"}) + +- self.assertRaises(InvalidBSON, BSON(invalid_key).decode, CodecOptions( ++ self.assertRaises(InvalidBSON, decode, invalid_key, CodecOptions( + unicode_decode_error_handler="strict")) +- self.assertRaises(InvalidBSON, BSON(invalid_key).decode, +- CodecOptions()) +- self.assertRaises(InvalidBSON, BSON(invalid_key).decode) ++ self.assertRaises(InvalidBSON, decode, invalid_key, CodecOptions()) ++ self.assertRaises(InvalidBSON, decode, invalid_key) + + # Test handing of bad string value. + invalid_val = BSON(enc[:18] + b'\xe9' + enc[19:]) + replaced_val = b'fo\xe9bar'.decode('utf-8', 'replace') + ignored_val = b'fo\xe9bar'.decode('utf-8', 'ignore') + +- dec = BSON(invalid_val).decode(CodecOptions( +- unicode_decode_error_handler="replace")) ++ dec = decode(invalid_val, ++ CodecOptions(unicode_decode_error_handler="replace")) + self.assertEqual(dec, {u"keystr": replaced_val}) + +- dec = BSON(invalid_val).decode(CodecOptions( +- unicode_decode_error_handler="ignore")) ++ dec = decode(invalid_val, ++ CodecOptions(unicode_decode_error_handler="ignore")) + self.assertEqual(dec, {u"keystr": ignored_val}) + +- self.assertRaises(InvalidBSON, BSON(invalid_val).decode, CodecOptions( ++ self.assertRaises(InvalidBSON, decode, invalid_val, CodecOptions( + unicode_decode_error_handler="strict")) +- self.assertRaises(InvalidBSON, BSON(invalid_val).decode, +- CodecOptions()) +- self.assertRaises(InvalidBSON, BSON(invalid_val).decode) ++ self.assertRaises(InvalidBSON, decode, invalid_val, CodecOptions()) ++ self.assertRaises(InvalidBSON, decode, invalid_val) + + # Test handing bad key + bad value. +- invalid_both = BSON( +- enc[:7] + b'\xe9' + enc[8:18] + b'\xe9' + enc[19:]) ++ invalid_both = enc[:7] + b'\xe9' + enc[8:18] + b'\xe9' + enc[19:] + +- dec = BSON(invalid_both).decode(CodecOptions( +- unicode_decode_error_handler="replace")) ++ dec = decode(invalid_both, ++ CodecOptions(unicode_decode_error_handler="replace")) + self.assertEqual(dec, {replaced_key: replaced_val}) + +- dec = BSON(invalid_both).decode(CodecOptions( +- unicode_decode_error_handler="ignore")) ++ dec = decode(invalid_both, ++ CodecOptions(unicode_decode_error_handler="ignore")) + self.assertEqual(dec, {ignored_key: ignored_val}) + +- self.assertRaises(InvalidBSON, BSON(invalid_both).decode, CodecOptions( ++ self.assertRaises(InvalidBSON, decode, invalid_both, CodecOptions( + unicode_decode_error_handler="strict")) +- self.assertRaises(InvalidBSON, BSON(invalid_both).decode, +- CodecOptions()) +- self.assertRaises(InvalidBSON, BSON(invalid_both).decode) ++ self.assertRaises(InvalidBSON, decode, invalid_both, CodecOptions()) ++ self.assertRaises(InvalidBSON, decode, invalid_both) + + # Test handling bad error mode. +- dec = BSON(enc).decode(CodecOptions( +- unicode_decode_error_handler="junk")) ++ dec = decode(enc, ++ CodecOptions(unicode_decode_error_handler="junk")) + self.assertEqual(dec, {"keystr": "foobar"}) + +- self.assertRaises(InvalidBSON, BSON(invalid_both).decode, +- CodecOptions(unicode_decode_error_handler="junk")) ++ self.assertRaises(InvalidBSON, decode, invalid_both, CodecOptions( ++ unicode_decode_error_handler="junk")) + + + if __name__ == "__main__": +diff --git a/test/test_bson_corpus.py b/test/test_bson_corpus.py +index d836724d..0c461cf4 100644 +--- a/test/test_bson_corpus.py ++++ b/test/test_bson_corpus.py +@@ -26,7 +26,7 @@ from decimal import DecimalException + + sys.path[0:0] = [""] + +-from bson import BSON, json_util ++from bson import decode, encode, json_util + from bson.binary import STANDARD + from bson.codec_options import CodecOptions + from bson.decimal128 import Decimal128 +@@ -81,10 +81,10 @@ to_extjson_iso8601 = functools.partial(json_util.dumps, + json_options=json_options_iso8601) + to_relaxed_extjson = functools.partial( + json_util.dumps, json_options=json_util.RELAXED_JSON_OPTIONS) +-to_bson_uuid_04 = functools.partial(BSON.encode, ++to_bson_uuid_04 = functools.partial(encode, + codec_options=codec_options_uuid_04) +-to_bson = functools.partial(BSON.encode, codec_options=codec_options) +-decode_bson = lambda bbytes: BSON(bbytes).decode(codec_options=codec_options) ++to_bson = functools.partial(encode, codec_options=codec_options) ++decode_bson = lambda bbytes: decode(bbytes, codec_options=codec_options) + decode_extjson = functools.partial( + json_util.loads, + json_options=json_util.JSONOptions(json_mode=JSONMode.CANONICAL, +diff --git a/test/test_change_stream.py b/test/test_change_stream.py +index c9415c46..ced8af8c 100644 +--- a/test/test_change_stream.py ++++ b/test/test_change_stream.py +@@ -27,7 +27,7 @@ from itertools import product + + sys.path[0:0] = [''] + +-from bson import BSON, ObjectId, SON, Timestamp, json_util ++from bson import ObjectId, SON, Timestamp, encode, json_util + from bson.binary import (ALL_UUID_REPRESENTATIONS, + Binary, + STANDARD, +@@ -965,7 +965,7 @@ class TestCollectionChangeStream(TestChangeStreamBase, APITestsMixin, + raw_coll = self.watched_collection( + codec_options=DEFAULT_RAW_BSON_OPTIONS) + with raw_coll.watch() as change_stream: +- raw_doc = RawBSONDocument(BSON.encode({'_id': 1})) ++ raw_doc = RawBSONDocument(encode({'_id': 1})) + self.watched_collection().insert_one(raw_doc) + change = next(change_stream) + self.assertIsInstance(change, RawBSONDocument) +diff --git a/test/test_client.py b/test/test_client.py +index 94856484..775402f5 100644 +--- a/test/test_client.py ++++ b/test/test_client.py +@@ -28,7 +28,7 @@ import warnings + + sys.path[0:0] = [""] + +-from bson import BSON ++from bson import encode + from bson.codec_options import CodecOptions, TypeEncoder, TypeRegistry + from bson.py3compat import thread + from bson.son import SON +@@ -1512,7 +1512,7 @@ class TestExhaustCursor(IntegrationTest): + + # responseFlags bit 1 is QueryFailure. + msg = struct.pack(' - 3.9.0-5 +- PYTHON-1918 Stop using BSON.encode and BSON.decode functions + * Thu Sep 17 2020 liuweibo - 3.9.0-4 - Fix Source0