raw_bson.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright 2015-present MongoDB, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Tools for representing raw BSON documents.
  15. Inserting and Retrieving RawBSONDocuments
  16. =========================================
  17. Example: Moving a document between different databases/collections
  18. .. doctest::
  19. >>> import bson
  20. >>> from pymongo import MongoClient
  21. >>> from bson.raw_bson import RawBSONDocument
  22. >>> client = MongoClient(document_class=RawBSONDocument)
  23. >>> client.drop_database('db')
  24. >>> client.drop_database('replica_db')
  25. >>> db = client.db
  26. >>> result = db.test.insert_many([{'a': 1},
  27. ... {'b': 1},
  28. ... {'c': 1},
  29. ... {'d': 1}])
  30. >>> replica_db = client.replica_db
  31. >>> for doc in db.test.find():
  32. ... print("raw document: %r" % (doc.raw,))
  33. ... result = replica_db.test.insert_one(doc)
  34. raw document: '...'
  35. raw document: '...'
  36. raw document: '...'
  37. raw document: '...'
  38. >>> for doc in replica_db.test.find(projection={'_id': 0}):
  39. ... print("decoded document: %r" % (bson.decode(doc.raw),))
  40. decoded document: {u'a': 1}
  41. decoded document: {u'b': 1}
  42. decoded document: {u'c': 1}
  43. decoded document: {u'd': 1}
  44. For use cases like moving documents across different databases or writing binary
  45. blobs to disk, using raw BSON documents provides better speed and avoids the
  46. overhead of decoding or encoding BSON.
  47. """
  48. from bson import _raw_to_dict, _get_object_size
  49. from bson.py3compat import abc, iteritems
  50. from bson.codec_options import (
  51. DEFAULT_CODEC_OPTIONS as DEFAULT, _RAW_BSON_DOCUMENT_MARKER)
  52. from bson.son import SON
  53. class RawBSONDocument(abc.Mapping):
  54. """Representation for a MongoDB document that provides access to the raw
  55. BSON bytes that compose it.
  56. Only when a field is accessed or modified within the document does
  57. RawBSONDocument decode its bytes.
  58. """
  59. __slots__ = ('__raw', '__inflated_doc', '__codec_options')
  60. _type_marker = _RAW_BSON_DOCUMENT_MARKER
  61. def __init__(self, bson_bytes, codec_options=None):
  62. """Create a new :class:`RawBSONDocument`
  63. :class:`RawBSONDocument` is a representation of a BSON document that
  64. provides access to the underlying raw BSON bytes. Only when a field is
  65. accessed or modified within the document does RawBSONDocument decode
  66. its bytes.
  67. :class:`RawBSONDocument` implements the ``Mapping`` abstract base
  68. class from the standard library so it can be used like a read-only
  69. ``dict``::
  70. >>> from bson import encode
  71. >>> raw_doc = RawBSONDocument(encode({'_id': 'my_doc'}))
  72. >>> raw_doc.raw
  73. b'...'
  74. >>> raw_doc['_id']
  75. 'my_doc'
  76. :Parameters:
  77. - `bson_bytes`: the BSON bytes that compose this document
  78. - `codec_options` (optional): An instance of
  79. :class:`~bson.codec_options.CodecOptions` whose ``document_class``
  80. must be :class:`RawBSONDocument`. The default is
  81. :attr:`DEFAULT_RAW_BSON_OPTIONS`.
  82. .. versionchanged:: 3.8
  83. :class:`RawBSONDocument` now validates that the ``bson_bytes``
  84. passed in represent a single bson document.
  85. .. versionchanged:: 3.5
  86. If a :class:`~bson.codec_options.CodecOptions` is passed in, its
  87. `document_class` must be :class:`RawBSONDocument`.
  88. """
  89. self.__raw = bson_bytes
  90. self.__inflated_doc = None
  91. # Can't default codec_options to DEFAULT_RAW_BSON_OPTIONS in signature,
  92. # it refers to this class RawBSONDocument.
  93. if codec_options is None:
  94. codec_options = DEFAULT_RAW_BSON_OPTIONS
  95. elif codec_options.document_class is not RawBSONDocument:
  96. raise TypeError(
  97. "RawBSONDocument cannot use CodecOptions with document "
  98. "class %s" % (codec_options.document_class, ))
  99. self.__codec_options = codec_options
  100. # Validate the bson object size.
  101. _get_object_size(bson_bytes, 0, len(bson_bytes))
  102. @property
  103. def raw(self):
  104. """The raw BSON bytes composing this document."""
  105. return self.__raw
  106. def items(self):
  107. """Lazily decode and iterate elements in this document."""
  108. return iteritems(self.__inflated)
  109. @property
  110. def __inflated(self):
  111. if self.__inflated_doc is None:
  112. # We already validated the object's size when this document was
  113. # created, so no need to do that again.
  114. # Use SON to preserve ordering of elements.
  115. self.__inflated_doc = _inflate_bson(
  116. self.__raw, self.__codec_options)
  117. return self.__inflated_doc
  118. def __getitem__(self, item):
  119. return self.__inflated[item]
  120. def __iter__(self):
  121. return iter(self.__inflated)
  122. def __len__(self):
  123. return len(self.__inflated)
  124. def __eq__(self, other):
  125. if isinstance(other, RawBSONDocument):
  126. return self.__raw == other.raw
  127. return NotImplemented
  128. def __repr__(self):
  129. return ("RawBSONDocument(%r, codec_options=%r)"
  130. % (self.raw, self.__codec_options))
  131. def _inflate_bson(bson_bytes, codec_options):
  132. """Inflates the top level fields of a BSON document.
  133. :Parameters:
  134. - `bson_bytes`: the BSON bytes that compose this document
  135. - `codec_options`: An instance of
  136. :class:`~bson.codec_options.CodecOptions` whose ``document_class``
  137. must be :class:`RawBSONDocument`.
  138. """
  139. # Use SON to preserve ordering of elements.
  140. return _raw_to_dict(
  141. bson_bytes, 4, len(bson_bytes)-1, codec_options, SON())
  142. DEFAULT_RAW_BSON_OPTIONS = DEFAULT.with_options(document_class=RawBSONDocument)
  143. """The default :class:`~bson.codec_options.CodecOptions` for
  144. :class:`RawBSONDocument`.
  145. """