PYTHON-1785 Add bson.encode and bson.decode

This commit is contained in:
Bernie Hackett 2019-07-19 15:22:39 -07:00
parent 57c7f8ccbb
commit 3bcdde34c9
3 changed files with 87 additions and 11 deletions

View File

@ -903,7 +903,7 @@ def _millis_to_datetime(millis, opts):
micros = diff * 1000
if opts.tz_aware:
dt = EPOCH_AWARE + datetime.timedelta(seconds=seconds,
microseconds=micros)
microseconds=micros)
if opts.tzinfo:
dt = dt.astimezone(opts.tzinfo)
return dt
@ -924,6 +924,65 @@ _CODEC_OPTIONS_TYPE_ERROR = TypeError(
"codec_options must be an instance of CodecOptions")
def encode(document, check_keys=False, codec_options=DEFAULT_CODEC_OPTIONS):
"""Encode a document to BSON.
A document can be any mapping type (like :class:`dict`).
Raises :class:`TypeError` if `document` is not a mapping type,
or contains keys that are not instances of
:class:`basestring` (:class:`str` in python 3). Raises
:class:`~bson.errors.InvalidDocument` if `document` cannot be
converted to :class:`BSON`.
:Parameters:
- `document`: mapping type representing a document
- `check_keys` (optional): check if keys start with '$' or
contain '.', raising :class:`~bson.errors.InvalidDocument` in
either case
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionadded:: 3.9
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return _dict_to_bson(document, check_keys, codec_options)
def decode(data, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode BSON to a document.
By default, returns a BSON document represented as a Python
:class:`dict`. To use a different :class:`MutableMapping` class,
configure a :class:`~bson.codec_options.CodecOptions`::
>>> import collections # From Python standard library.
>>> import bson
>>> from bson.codec_options import CodecOptions
>>> data = bson.encode({'a': 1})
>>> decoded_doc = bson.decode(data)
<type 'dict'>
>>> options = CodecOptions(document_class=collections.OrderedDict)
>>> decoded_doc = bson.decode(data, codec_options=options)
>>> type(decoded_doc)
<class 'collections.OrderedDict'>
:Parameters:
- `data`: the BSON to decode. Any bytes-like object that implements
the buffer protocol.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionadded:: 3.9
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return _bson_to_dict(data, codec_options)
def decode_all(data, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode BSON data to multiple documents.
@ -935,7 +994,7 @@ def decode_all(data, codec_options=DEFAULT_CODEC_OPTIONS):
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionchanges:: 3.9
.. versionchanged:: 3.9
Supports bytes-like objects that implement the buffer protocol.
.. versionchanged:: 3.0
@ -1137,6 +1196,10 @@ def is_valid(bson):
class BSON(bytes):
"""BSON (Binary JSON) data.
.. warning:: Using this class to encode and decode BSON adds a performance
cost. For better performance use the module level functions
:func:`encode` and :func:`decode` instead.
"""
@classmethod
@ -1163,10 +1226,7 @@ class BSON(bytes):
.. versionchanged:: 3.0
Replaced `uuid_subtype` option with `codec_options`.
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return cls(_dict_to_bson(document, check_keys, codec_options))
return cls(encode(document, check_keys, codec_options))
def decode(self, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode this BSON data.
@ -1208,10 +1268,7 @@ class BSON(bytes):
.. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return _bson_to_dict(self, codec_options)
return decode(self, codec_options)
def has_c():

View File

@ -60,6 +60,7 @@ 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`.
- New BSON utility functions :func:`~bson.encode` and :func:`~bson.decode`
- :class:`~bson.binary.Binary` now supports any bytes-like type that implements
the buffer protocol.
- Resume tokens can now be accessed from a ``ChangeStream`` cursor using the

View File

@ -26,9 +26,11 @@ sys.path[0:0] = [""]
import bson
from bson import (BSON,
decode,
decode_all,
decode_file_iter,
decode_iter,
encode,
EPOCH_AWARE,
is_valid,
Regex)
@ -124,6 +126,8 @@ class TestBSON(unittest.TestCase):
def helper(doc):
self.assertEqual(doc, (BSON.encode(doc_class(doc))).decode())
self.assertEqual(doc, decode(encode(doc)))
helper({})
helper({"test": u"hello"})
self.assertTrue(isinstance(BSON.encode({"hello": "world"})
@ -283,7 +287,7 @@ class TestBSON(unittest.TestCase):
b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00"
b"\x05\x00\x00\x00\x00"))))
def test_buffer_protocol(self):
def test_decode_all_buffer_protocol(self):
docs = [{'foo': 'bar'}, {}]
bs = b"".join(map(BSON.encode, docs))
self.assertEqual(docs, decode_all(bytearray(bs)))
@ -297,6 +301,20 @@ class TestBSON(unittest.TestCase):
mm.seek(0)
self.assertEqual(docs, decode_all(mm))
def test_decode_buffer_protocol(self):
doc = {'foo': 'bar'}
bs = encode(doc)
self.assertEqual(doc, decode(bs))
self.assertEqual(doc, decode(bytearray(bs)))
self.assertEqual(doc, decode(memoryview(bs)))
if PY3:
import array
import mmap
self.assertEqual(doc, decode(array.array('B', bs)))
with mmap.mmap(-1, len(bs)) as mm:
mm.write(bs)
mm.seek(0)
self.assertEqual(doc, decode(mm))
def test_invalid_decodes(self):
# Invalid object size (not enough bytes in document for even