diff --git a/bson/binary.py b/bson/binary.py index 1360e74e6..1c833b5a5 100644 --- a/bson/binary.py +++ b/bson/binary.py @@ -133,22 +133,25 @@ class Binary(bytes): directly to :class:`bytes`. :Parameters: - - `data`: the binary data to represent + - `data`: the binary data to represent. Can be any bytes-like type + that implements the buffer protocol. - `subtype` (optional): the `binary subtype `_ to use + + .. versionchanged:: 3.9 + Support any bytes-like type that implements the buffer protocol. """ _type_marker = 5 def __new__(cls, data, subtype=BINARY_SUBTYPE): - if not isinstance(data, bytes): - raise TypeError("data must be an instance of bytes") if not isinstance(subtype, int): raise TypeError("subtype must be an instance of int") if subtype >= 256 or subtype < 0: raise ValueError("subtype must be contained in [0, 256)") - self = bytes.__new__(cls, data) + # Support any type that implements the buffer protocol. + self = bytes.__new__(cls, memoryview(data).tobytes()) self.__subtype = subtype return self diff --git a/doc/changelog.rst b/doc/changelog.rst index c3fd1e344..89f10e69f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -60,6 +60,8 @@ Version 3.9 adds support for MongoDB 4.2. Highlights include: :meth:`~pymongo.collection.Collection.find_one_and_update`, :meth:`~pymongo.operations.UpdateOne`, and :meth:`~pymongo.operations.UpdateMany`. +- :class:`~bson.binary.Binary` now supports any bytes-like type that implements + the buffer protocol. .. _URI options specification: https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst diff --git a/test/test_binary.py b/test/test_binary.py index 7df3f5c68..b5fbb92ac 100644 --- a/test/test_binary.py +++ b/test/test_binary.py @@ -14,9 +14,11 @@ """Tests for the Binary wrapper.""" +import array import base64 import copy import pickle +import platform import sys import uuid @@ -26,6 +28,7 @@ import bson from bson.binary import * from bson.codec_options import CodecOptions +from bson.py3compat import PY3 from bson.son import SON from pymongo.mongo_client import MongoClient from test import client_context, unittest @@ -81,7 +84,6 @@ class TestBinary(unittest.TestCase): def test_exceptions(self): self.assertRaises(TypeError, Binary, None) - self.assertRaises(TypeError, Binary, u"hello") self.assertRaises(TypeError, Binary, 5) self.assertRaises(TypeError, Binary, 10.2) self.assertRaises(TypeError, Binary, b"hello", None) @@ -90,6 +92,10 @@ class TestBinary(unittest.TestCase): self.assertRaises(ValueError, Binary, b"hello", 256) self.assertTrue(Binary(b"hello", 0)) self.assertTrue(Binary(b"hello", 255)) + if platform.python_implementation() != "Jython": + # Jython's memoryview accepts unicode strings... + # https://bugs.jython.org/issue2784 + self.assertRaises(TypeError, Binary, u"hello") def test_subtype(self): one = Binary(b"hello") @@ -356,6 +362,22 @@ class TestBinary(unittest.TestCase): for proto in range(pickle.HIGHEST_PROTOCOL + 1): self.assertEqual(uul, pickle.loads(pickle.dumps(uul, proto))) + def test_buffer_protocol(self): + b0 = Binary(b'123', 2) + + self.assertEqual(b0, Binary(memoryview(b'123'), 2)) + self.assertEqual(b0, Binary(bytearray(b'123'), 2)) + # mmap.mmap and array.array only expose the + # buffer interface in python 3.x + if PY3: + # No mmap module in Jython + import mmap + with mmap.mmap(-1, len(b'123')) as mm: + mm.write(b'123') + mm.seek(0) + self.assertEqual(b0, Binary(mm, 2)) + self.assertEqual(b0, Binary(array.array('B', b'123'), 2)) + if __name__ == "__main__": unittest.main()