123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- # Copyright 2009-present MongoDB, Inc.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- from uuid import UUID
- from warnings import warn
- from bson.py3compat import PY3
- """Tools for representing BSON binary data.
- """
- BINARY_SUBTYPE = 0
- """BSON binary subtype for binary data.
- This is the default subtype for binary data.
- """
- FUNCTION_SUBTYPE = 1
- """BSON binary subtype for functions.
- """
- OLD_BINARY_SUBTYPE = 2
- """Old BSON binary subtype for binary data.
- This is the old default subtype, the current
- default is :data:`BINARY_SUBTYPE`.
- """
- OLD_UUID_SUBTYPE = 3
- """Old BSON binary subtype for a UUID.
- :class:`uuid.UUID` instances will automatically be encoded
- by :mod:`bson` using this subtype.
- .. versionadded:: 2.1
- """
- UUID_SUBTYPE = 4
- """BSON binary subtype for a UUID.
- This is the new BSON binary subtype for UUIDs. The
- current default is :data:`OLD_UUID_SUBTYPE`.
- .. versionchanged:: 2.1
- Changed to subtype 4.
- """
- class UuidRepresentation:
- UNSPECIFIED = 0
- """An unspecified UUID representation.
- When configured, :class:`uuid.UUID` instances will **not** be
- automatically encoded to or decoded from :class:`~bson.binary.Binary`.
- When encoding a :class:`uuid.UUID` instance, an error will be raised.
- To encode a :class:`uuid.UUID` instance with this configuration, it must
- be wrapped in the :class:`~bson.binary.Binary` class by the application
- code. When decoding a BSON binary field with a UUID subtype, a
- :class:`~bson.binary.Binary` instance will be returned instead of a
- :class:`uuid.UUID` instance.
-
- See :ref:`unspecified-representation-details` for details.
- .. versionadded:: 3.11
- """
- STANDARD = UUID_SUBTYPE
- """The standard UUID representation.
- :class:`uuid.UUID` instances will automatically be encoded to
- and decoded from BSON binary, using RFC-4122 byte order with
- binary subtype :data:`UUID_SUBTYPE`.
-
- See :ref:`standard-representation-details` for details.
- .. versionadded:: 3.11
- """
- PYTHON_LEGACY = OLD_UUID_SUBTYPE
- """The Python legacy UUID representation.
- :class:`uuid.UUID` instances will automatically be encoded to
- and decoded from BSON binary, using RFC-4122 byte order with
- binary subtype :data:`OLD_UUID_SUBTYPE`.
-
- See :ref:`python-legacy-representation-details` for details.
- .. versionadded:: 3.11
- """
- JAVA_LEGACY = 5
- """The Java legacy UUID representation.
- :class:`uuid.UUID` instances will automatically be encoded to
- and decoded from BSON binary subtype :data:`OLD_UUID_SUBTYPE`,
- using the Java driver's legacy byte order.
-
- See :ref:`java-legacy-representation-details` for details.
- .. versionadded:: 3.11
- """
- CSHARP_LEGACY = 6
- """The C#/.net legacy UUID representation.
- :class:`uuid.UUID` instances will automatically be encoded to
- and decoded from BSON binary subtype :data:`OLD_UUID_SUBTYPE`,
- using the C# driver's legacy byte order.
-
- See :ref:`csharp-legacy-representation-details` for details.
- .. versionadded:: 3.11
- """
- STANDARD = UuidRepresentation.STANDARD
- """An alias for :data:`UuidRepresentation.STANDARD`.
- .. versionadded:: 3.0
- """
- PYTHON_LEGACY = UuidRepresentation.PYTHON_LEGACY
- """An alias for :data:`UuidRepresentation.PYTHON_LEGACY`.
- .. versionadded:: 3.0
- """
- JAVA_LEGACY = UuidRepresentation.JAVA_LEGACY
- """An alias for :data:`UuidRepresentation.JAVA_LEGACY`.
- .. versionchanged:: 3.6
- BSON binary subtype 4 is decoded using RFC-4122 byte order.
- .. versionadded:: 2.3
- """
- CSHARP_LEGACY = UuidRepresentation.CSHARP_LEGACY
- """An alias for :data:`UuidRepresentation.CSHARP_LEGACY`.
- .. versionchanged:: 3.6
- BSON binary subtype 4 is decoded using RFC-4122 byte order.
- .. versionadded:: 2.3
- """
- ALL_UUID_SUBTYPES = (OLD_UUID_SUBTYPE, UUID_SUBTYPE)
- ALL_UUID_REPRESENTATIONS = (UuidRepresentation.UNSPECIFIED,
- UuidRepresentation.STANDARD,
- UuidRepresentation.PYTHON_LEGACY,
- UuidRepresentation.JAVA_LEGACY,
- UuidRepresentation.CSHARP_LEGACY)
- UUID_REPRESENTATION_NAMES = {
- UuidRepresentation.UNSPECIFIED: 'UuidRepresentation.UNSPECIFIED',
- UuidRepresentation.STANDARD: 'UuidRepresentation.STANDARD',
- UuidRepresentation.PYTHON_LEGACY: 'UuidRepresentation.PYTHON_LEGACY',
- UuidRepresentation.JAVA_LEGACY: 'UuidRepresentation.JAVA_LEGACY',
- UuidRepresentation.CSHARP_LEGACY: 'UuidRepresentation.CSHARP_LEGACY'}
- MD5_SUBTYPE = 5
- """BSON binary subtype for an MD5 hash.
- """
- USER_DEFINED_SUBTYPE = 128
- """BSON binary subtype for any user defined structure.
- """
- class Binary(bytes):
- """Representation of BSON binary data.
- This is necessary because we want to represent Python strings as
- the BSON string type. We need to wrap binary data so we can tell
- the difference between what should be considered binary data and
- what should be considered a string when we encode to BSON.
- Raises TypeError if `data` is not an instance of :class:`bytes`
- (:class:`str` in python 2) or `subtype` is not an instance of
- :class:`int`. Raises ValueError if `subtype` is not in [0, 256).
- .. note::
- In python 3 instances of Binary with subtype 0 will be decoded
- directly to :class:`bytes`.
- :Parameters:
- - `data`: the binary data to represent. Can be any bytes-like type
- that implements the buffer protocol.
- - `subtype` (optional): the `binary subtype
- <http://bsonspec.org/#/specification>`_
- 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(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)")
- # Support any type that implements the buffer protocol.
- self = bytes.__new__(cls, memoryview(data).tobytes())
- self.__subtype = subtype
- return self
- @classmethod
- def from_uuid(cls, uuid, uuid_representation=UuidRepresentation.STANDARD):
- """Create a BSON Binary object from a Python UUID.
- Creates a :class:`~bson.binary.Binary` object from a
- :class:`uuid.UUID` instance. Assumes that the native
- :class:`uuid.UUID` instance uses the byte-order implied by the
- provided ``uuid_representation``.
- Raises :exc:`TypeError` if `uuid` is not an instance of
- :class:`~uuid.UUID`.
- :Parameters:
- - `uuid`: A :class:`uuid.UUID` instance.
- - `uuid_representation`: A member of
- :class:`~bson.binary.UuidRepresentation`. Default:
- :const:`~bson.binary.UuidRepresentation.STANDARD`.
- See :ref:`handling-uuid-data-example` for details.
- .. versionadded:: 3.11
- """
- if not isinstance(uuid, UUID):
- raise TypeError("uuid must be an instance of uuid.UUID")
- if uuid_representation not in ALL_UUID_REPRESENTATIONS:
- raise ValueError("uuid_representation must be a value "
- "from bson.binary.UuidRepresentation")
- if uuid_representation == UuidRepresentation.UNSPECIFIED:
- raise ValueError(
- "cannot encode native uuid.UUID with "
- "UuidRepresentation.UNSPECIFIED. UUIDs can be manually "
- "converted to bson.Binary instances using "
- "bson.Binary.from_uuid() or a different UuidRepresentation "
- "can be configured. See the documentation for "
- "UuidRepresentation for more information.")
- subtype = OLD_UUID_SUBTYPE
- if uuid_representation == UuidRepresentation.PYTHON_LEGACY:
- payload = uuid.bytes
- elif uuid_representation == UuidRepresentation.JAVA_LEGACY:
- from_uuid = uuid.bytes
- payload = from_uuid[0:8][::-1] + from_uuid[8:16][::-1]
- elif uuid_representation == UuidRepresentation.CSHARP_LEGACY:
- payload = uuid.bytes_le
- else:
- # uuid_representation == UuidRepresentation.STANDARD
- subtype = UUID_SUBTYPE
- payload = uuid.bytes
- return cls(payload, subtype)
- def as_uuid(self, uuid_representation=UuidRepresentation.STANDARD):
- """Create a Python UUID from this BSON Binary object.
- Decodes this binary object as a native :class:`uuid.UUID` instance
- with the provided ``uuid_representation``.
- Raises :exc:`ValueError` if this :class:`~bson.binary.Binary` instance
- does not contain a UUID.
- :Parameters:
- - `uuid_representation`: A member of
- :class:`~bson.binary.UuidRepresentation`. Default:
- :const:`~bson.binary.UuidRepresentation.STANDARD`.
- See :ref:`handling-uuid-data-example` for details.
- .. versionadded:: 3.11
- """
- if self.subtype not in ALL_UUID_SUBTYPES:
- raise ValueError("cannot decode subtype %s as a uuid" % (
- self.subtype,))
- if uuid_representation not in ALL_UUID_REPRESENTATIONS:
- raise ValueError("uuid_representation must be a value from "
- "bson.binary.UuidRepresentation")
- if uuid_representation == UuidRepresentation.UNSPECIFIED:
- raise ValueError("uuid_representation cannot be UNSPECIFIED")
- elif uuid_representation == UuidRepresentation.PYTHON_LEGACY:
- if self.subtype == OLD_UUID_SUBTYPE:
- return UUID(bytes=self)
- elif uuid_representation == UuidRepresentation.JAVA_LEGACY:
- if self.subtype == OLD_UUID_SUBTYPE:
- return UUID(bytes=self[0:8][::-1] + self[8:16][::-1])
- elif uuid_representation == UuidRepresentation.CSHARP_LEGACY:
- if self.subtype == OLD_UUID_SUBTYPE:
- return UUID(bytes_le=self)
- else:
- # uuid_representation == UuidRepresentation.STANDARD
- if self.subtype == UUID_SUBTYPE:
- return UUID(bytes=self)
- raise ValueError("cannot decode subtype %s to %s" % (
- self.subtype, UUID_REPRESENTATION_NAMES[uuid_representation]))
- @property
- def subtype(self):
- """Subtype of this binary data.
- """
- return self.__subtype
- def __getnewargs__(self):
- # Work around http://bugs.python.org/issue7382
- data = super(Binary, self).__getnewargs__()[0]
- if PY3 and not isinstance(data, bytes):
- data = data.encode('latin-1')
- return data, self.__subtype
- def __eq__(self, other):
- if isinstance(other, Binary):
- return ((self.__subtype, bytes(self)) ==
- (other.subtype, bytes(other)))
- # We don't return NotImplemented here because if we did then
- # Binary("foo") == "foo" would return True, since Binary is a
- # subclass of str...
- return False
- def __hash__(self):
- return super(Binary, self).__hash__() ^ hash(self.__subtype)
- def __ne__(self, other):
- return not self == other
- def __repr__(self):
- return "Binary(%s, %s)" % (bytes.__repr__(self), self.__subtype)
- class UUIDLegacy(Binary):
- """**DEPRECATED** - UUID wrapper to support working with UUIDs stored as
- PYTHON_LEGACY.
- .. note:: This class has been deprecated and will be removed in
- PyMongo 4.0. Use :meth:`~bson.binary.Binary.from_uuid` and
- :meth:`~bson.binary.Binary.as_uuid` with the appropriate
- :class:`~bson.binary.UuidRepresentation` to handle legacy-formatted
- UUIDs instead.::
- from bson import Binary, UUIDLegacy, UuidRepresentation
- import uuid
- my_uuid = uuid.uuid4()
- legacy_uuid = UUIDLegacy(my_uuid)
- binary_uuid = Binary.from_uuid(
- my_uuid, UuidRepresentation.PYTHON_LEGACY)
- assert legacy_uuid == binary_uuid
- assert legacy_uuid.uuid == binary_uuid.as_uuid(
- UuidRepresentation.PYTHON_LEGACY)
- .. doctest::
- >>> import uuid
- >>> from bson.binary import Binary, UUIDLegacy, STANDARD
- >>> from bson.codec_options import CodecOptions
- >>> my_uuid = uuid.uuid4()
- >>> coll = db.get_collection('test',
- ... CodecOptions(uuid_representation=STANDARD))
- >>> coll.insert_one({'uuid': Binary(my_uuid.bytes, 3)}).inserted_id
- ObjectId('...')
- >>> coll.count_documents({'uuid': my_uuid})
- 0
- >>> coll.count_documents({'uuid': UUIDLegacy(my_uuid)})
- 1
- >>> coll.find({'uuid': UUIDLegacy(my_uuid)})[0]['uuid']
- UUID('...')
- >>>
- >>> # Convert from subtype 3 to subtype 4
- >>> doc = coll.find_one({'uuid': UUIDLegacy(my_uuid)})
- >>> coll.replace_one({"_id": doc["_id"]}, doc).matched_count
- 1
- >>> coll.count_documents({'uuid': UUIDLegacy(my_uuid)})
- 0
- >>> coll.count_documents({'uuid': {'$in': [UUIDLegacy(my_uuid), my_uuid]}})
- 1
- >>> coll.find_one({'uuid': my_uuid})['uuid']
- UUID('...')
- Raises :exc:`TypeError` if `obj` is not an instance of :class:`~uuid.UUID`.
- :Parameters:
- - `obj`: An instance of :class:`~uuid.UUID`.
- .. versionchanged:: 3.11
- Deprecated. The same functionality can be replicated using the
- :meth:`~Binary.from_uuid` and :meth:`~Binary.to_uuid` methods with
- :data:`~UuidRepresentation.PYTHON_LEGACY`.
- .. versionadded:: 2.1
- """
- def __new__(cls, obj):
- warn(
- "The UUIDLegacy class has been deprecated and will be removed "
- "in PyMongo 4.0. Use the Binary.from_uuid() and Binary.to_uuid() "
- "with the appropriate UuidRepresentation to handle "
- "legacy-formatted UUIDs instead.",
- DeprecationWarning, stacklevel=2)
- if not isinstance(obj, UUID):
- raise TypeError("obj must be an instance of uuid.UUID")
- self = Binary.__new__(cls, obj.bytes, OLD_UUID_SUBTYPE)
- self.__uuid = obj
- return self
- def __getnewargs__(self):
- # Support copy and deepcopy
- return (self.__uuid,)
- @property
- def uuid(self):
- """UUID instance wrapped by this UUIDLegacy instance.
- """
- return self.__uuid
- def __repr__(self):
- return "UUIDLegacy('%s')" % self.__uuid
|