PYTHON-1785 Add bson.encode and bson.decode
This commit is contained in:
parent
57c7f8ccbb
commit
3bcdde34c9
@ -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():
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user