binary.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # Copyright 2009-2015 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. from uuid import UUID
  15. from bson.py3compat import PY3
  16. """Tools for representing BSON binary data.
  17. """
  18. BINARY_SUBTYPE = 0
  19. """BSON binary subtype for binary data.
  20. This is the default subtype for binary data.
  21. """
  22. FUNCTION_SUBTYPE = 1
  23. """BSON binary subtype for functions.
  24. """
  25. OLD_BINARY_SUBTYPE = 2
  26. """Old BSON binary subtype for binary data.
  27. This is the old default subtype, the current
  28. default is :data:`BINARY_SUBTYPE`.
  29. """
  30. OLD_UUID_SUBTYPE = 3
  31. """Old BSON binary subtype for a UUID.
  32. :class:`uuid.UUID` instances will automatically be encoded
  33. by :mod:`bson` using this subtype.
  34. .. versionadded:: 2.1
  35. """
  36. UUID_SUBTYPE = 4
  37. """BSON binary subtype for a UUID.
  38. This is the new BSON binary subtype for UUIDs. The
  39. current default is :data:`OLD_UUID_SUBTYPE` but will
  40. change to this in a future release.
  41. .. versionchanged:: 2.1
  42. Changed to subtype 4.
  43. """
  44. STANDARD = UUID_SUBTYPE
  45. """The standard UUID representation.
  46. :class:`uuid.UUID` instances will automatically be encoded to
  47. and decoded from BSON binary, using RFC-4122 byte order with
  48. binary subtype :data:`UUID_SUBTYPE`.
  49. .. versionadded:: 3.0
  50. """
  51. PYTHON_LEGACY = OLD_UUID_SUBTYPE
  52. """The Python legacy UUID representation.
  53. :class:`uuid.UUID` instances will automatically be encoded to
  54. and decoded from BSON binary, using RFC-4122 byte order with
  55. binary subtype :data:`OLD_UUID_SUBTYPE`.
  56. .. versionadded:: 3.0
  57. """
  58. JAVA_LEGACY = 5
  59. """The Java legacy UUID representation.
  60. :class:`uuid.UUID` instances will automatically be encoded to
  61. and decoded from BSON binary, using the Java driver's legacy
  62. byte order with binary subtype :data:`OLD_UUID_SUBTYPE`.
  63. .. versionadded:: 2.3
  64. """
  65. CSHARP_LEGACY = 6
  66. """The C#/.net legacy UUID representation.
  67. :class:`uuid.UUID` instances will automatically be encoded to
  68. and decoded from BSON binary, using the C# driver's legacy
  69. byte order and binary subtype :data:`OLD_UUID_SUBTYPE`.
  70. .. versionadded:: 2.3
  71. """
  72. ALL_UUID_SUBTYPES = (OLD_UUID_SUBTYPE, UUID_SUBTYPE)
  73. ALL_UUID_REPRESENTATIONS = (STANDARD, PYTHON_LEGACY, JAVA_LEGACY, CSHARP_LEGACY)
  74. UUID_REPRESENTATION_NAMES = {
  75. PYTHON_LEGACY: 'PYTHON_LEGACY',
  76. STANDARD: 'STANDARD',
  77. JAVA_LEGACY: 'JAVA_LEGACY',
  78. CSHARP_LEGACY: 'CSHARP_LEGACY'}
  79. MD5_SUBTYPE = 5
  80. """BSON binary subtype for an MD5 hash.
  81. """
  82. USER_DEFINED_SUBTYPE = 128
  83. """BSON binary subtype for any user defined structure.
  84. """
  85. class Binary(bytes):
  86. """Representation of BSON binary data.
  87. This is necessary because we want to represent Python strings as
  88. the BSON string type. We need to wrap binary data so we can tell
  89. the difference between what should be considered binary data and
  90. what should be considered a string when we encode to BSON.
  91. Raises TypeError if `data` is not an instance of :class:`str`
  92. (:class:`bytes` in python 3) or `subtype` is not an instance of
  93. :class:`int`. Raises ValueError if `subtype` is not in [0, 256).
  94. .. note::
  95. In python 3 instances of Binary with subtype 0 will be decoded
  96. directly to :class:`bytes`.
  97. :Parameters:
  98. - `data`: the binary data to represent
  99. - `subtype` (optional): the `binary subtype
  100. <http://bsonspec.org/#/specification>`_
  101. to use
  102. """
  103. _type_marker = 5
  104. def __new__(cls, data, subtype=BINARY_SUBTYPE):
  105. if not isinstance(data, bytes):
  106. raise TypeError("data must be an instance of bytes")
  107. if not isinstance(subtype, int):
  108. raise TypeError("subtype must be an instance of int")
  109. if subtype >= 256 or subtype < 0:
  110. raise ValueError("subtype must be contained in [0, 256)")
  111. self = bytes.__new__(cls, data)
  112. self.__subtype = subtype
  113. return self
  114. @property
  115. def subtype(self):
  116. """Subtype of this binary data.
  117. """
  118. return self.__subtype
  119. def __getnewargs__(self):
  120. # Work around http://bugs.python.org/issue7382
  121. data = super(Binary, self).__getnewargs__()[0]
  122. if PY3 and not isinstance(data, bytes):
  123. data = data.encode('latin-1')
  124. return data, self.__subtype
  125. def __eq__(self, other):
  126. if isinstance(other, Binary):
  127. return ((self.__subtype, bytes(self)) ==
  128. (other.subtype, bytes(other)))
  129. # We don't return NotImplemented here because if we did then
  130. # Binary("foo") == "foo" would return True, since Binary is a
  131. # subclass of str...
  132. return False
  133. def __ne__(self, other):
  134. return not self == other
  135. def __repr__(self):
  136. return "Binary(%s, %s)" % (bytes.__repr__(self), self.__subtype)
  137. class UUIDLegacy(Binary):
  138. """UUID wrapper to support working with UUIDs stored as PYTHON_LEGACY.
  139. .. doctest::
  140. >>> import uuid
  141. >>> from bson.binary import Binary, UUIDLegacy, STANDARD
  142. >>> from bson.codec_options import CodecOptions
  143. >>> my_uuid = uuid.uuid4()
  144. >>> coll = db.get_collection('test',
  145. ... CodecOptions(uuid_representation=STANDARD))
  146. >>> coll.insert_one({'uuid': Binary(my_uuid.bytes, 3)}).inserted_id
  147. ObjectId('...')
  148. >>> coll.find({'uuid': my_uuid}).count()
  149. 0
  150. >>> coll.find({'uuid': UUIDLegacy(my_uuid)}).count()
  151. 1
  152. >>> coll.find({'uuid': UUIDLegacy(my_uuid)})[0]['uuid']
  153. UUID('...')
  154. >>>
  155. >>> # Convert from subtype 3 to subtype 4
  156. >>> doc = coll.find_one({'uuid': UUIDLegacy(my_uuid)})
  157. >>> coll.replace_one({"_id": doc["_id"]}, doc).matched_count
  158. 1
  159. >>> coll.find({'uuid': UUIDLegacy(my_uuid)}).count()
  160. 0
  161. >>> coll.find({'uuid': {'$in': [UUIDLegacy(my_uuid), my_uuid]}}).count()
  162. 1
  163. >>> coll.find_one({'uuid': my_uuid})['uuid']
  164. UUID('...')
  165. Raises TypeError if `obj` is not an instance of :class:`~uuid.UUID`.
  166. :Parameters:
  167. - `obj`: An instance of :class:`~uuid.UUID`.
  168. """
  169. def __new__(cls, obj):
  170. if not isinstance(obj, UUID):
  171. raise TypeError("obj must be an instance of uuid.UUID")
  172. self = Binary.__new__(cls, obj.bytes, OLD_UUID_SUBTYPE)
  173. self.__uuid = obj
  174. return self
  175. def __getnewargs__(self):
  176. # Support copy and deepcopy
  177. return (self.__uuid,)
  178. @property
  179. def uuid(self):
  180. """UUID instance wrapped by this UUIDLegacy instance.
  181. """
  182. return self.__uuid
  183. def __repr__(self):
  184. return "UUIDLegacy('%s')" % self.__uuid