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('