xtquant.xtbson.bson36.objectid

Tools for working with MongoDB ObjectIds .

  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
 15"""Tools for working with MongoDB `ObjectIds
 16<http://dochub.mongodb.org/core/objectids>`_.
 17"""
 18
 19import binascii
 20import calendar
 21import datetime
 22import os
 23import struct
 24import threading
 25import time
 26from random import SystemRandom
 27
 28from .errors import InvalidId
 29from .tz_util import utc
 30
 31_MAX_COUNTER_VALUE = 0xFFFFFF
 32
 33
 34def _raise_invalid_id(oid):
 35    raise InvalidId(
 36        "%r is not a valid ObjectId, it must be a 12-byte input"
 37        " or a 24-character hex string" % oid
 38    )
 39
 40
 41def _random_bytes():
 42    """Get the 5-byte random field of an ObjectId."""
 43    return os.urandom(5)
 44
 45
 46class ObjectId(object):
 47    """A MongoDB ObjectId."""
 48
 49    _pid = os.getpid()
 50
 51    _inc = SystemRandom().randint(0, _MAX_COUNTER_VALUE)
 52    _inc_lock = threading.Lock()
 53
 54    __random = _random_bytes()
 55
 56    __slots__ = ("__id",)
 57
 58    _type_marker = 7
 59
 60    def __init__(self, oid=None):
 61        """Initialize a new ObjectId.
 62
 63        An ObjectId is a 12-byte unique identifier consisting of:
 64
 65          - a 4-byte value representing the seconds since the Unix epoch,
 66          - a 5-byte random value,
 67          - a 3-byte counter, starting with a random value.
 68
 69        By default, ``ObjectId()`` creates a new unique identifier. The
 70        optional parameter `oid` can be an :class:`ObjectId`, or any 12
 71        :class:`bytes`.
 72
 73        For example, the 12 bytes b'foo-bar-quux' do not follow the ObjectId
 74        specification but they are acceptable input::
 75
 76          >>> ObjectId(b'foo-bar-quux')
 77          ObjectId('666f6f2d6261722d71757578')
 78
 79        `oid` can also be a :class:`str` of 24 hex digits::
 80
 81          >>> ObjectId('0123456789ab0123456789ab')
 82          ObjectId('0123456789ab0123456789ab')
 83
 84        Raises :class:`~bson.errors.InvalidId` if `oid` is not 12 bytes nor
 85        24 hex digits, or :class:`TypeError` if `oid` is not an accepted type.
 86
 87        :Parameters:
 88          - `oid` (optional): a valid ObjectId.
 89
 90        .. seealso:: The MongoDB documentation on `ObjectIds`_.
 91
 92        .. versionchanged:: 3.8
 93           :class:`~bson.objectid.ObjectId` now implements the `ObjectID
 94           specification version 0.2
 95           <https://github.com/mongodb/specifications/blob/master/source/
 96           objectid.rst>`_.
 97        """
 98        if oid is None:
 99            self.__generate()
100        elif isinstance(oid, bytes) and len(oid) == 12:
101            self.__id = oid
102        else:
103            self.__validate(oid)
104
105    @classmethod
106    def from_datetime(cls, generation_time):
107        """Create a dummy ObjectId instance with a specific generation time.
108
109        This method is useful for doing range queries on a field
110        containing :class:`ObjectId` instances.
111
112        .. warning::
113           It is not safe to insert a document containing an ObjectId
114           generated using this method. This method deliberately
115           eliminates the uniqueness guarantee that ObjectIds
116           generally provide. ObjectIds generated with this method
117           should be used exclusively in queries.
118
119        `generation_time` will be converted to UTC. Naive datetime
120        instances will be treated as though they already contain UTC.
121
122        An example using this helper to get documents where ``"_id"``
123        was generated before January 1, 2010 would be:
124
125        >>> gen_time = datetime.datetime(2010, 1, 1)
126        >>> dummy_id = ObjectId.from_datetime(gen_time)
127        >>> result = collection.find({"_id": {"$lt": dummy_id}})
128
129        :Parameters:
130          - `generation_time`: :class:`~datetime.datetime` to be used
131            as the generation time for the resulting ObjectId.
132        """
133        if generation_time.utcoffset() is not None:
134            generation_time = generation_time - generation_time.utcoffset()
135        timestamp = calendar.timegm(generation_time.timetuple())
136        oid = struct.pack(">I", int(timestamp)) + b"\x00\x00\x00\x00\x00\x00\x00\x00"
137        return cls(oid)
138
139    @classmethod
140    def is_valid(cls, oid):
141        """Checks if a `oid` string is valid or not.
142
143        :Parameters:
144          - `oid`: the object id to validate
145
146        .. versionadded:: 2.3
147        """
148        if not oid:
149            return False
150
151        try:
152            ObjectId(oid)
153            return True
154        except (InvalidId, TypeError):
155            return False
156
157    @classmethod
158    def _random(cls):
159        """Generate a 5-byte random number once per process."""
160        pid = os.getpid()
161        if pid != cls._pid:
162            cls._pid = pid
163            cls.__random = _random_bytes()
164        return cls.__random
165
166    def __generate(self):
167        """Generate a new value for this ObjectId."""
168
169        # 4 bytes current time
170        oid = struct.pack(">I", int(time.time()))
171
172        # 5 bytes random
173        oid += ObjectId._random()
174
175        # 3 bytes inc
176        with ObjectId._inc_lock:
177            oid += struct.pack(">I", ObjectId._inc)[1:4]
178            ObjectId._inc = (ObjectId._inc + 1) % (_MAX_COUNTER_VALUE + 1)
179
180        self.__id = oid
181
182    def __validate(self, oid):
183        """Validate and use the given id for this ObjectId.
184
185        Raises TypeError if id is not an instance of
186        (:class:`basestring` (:class:`str` or :class:`bytes`
187        in python 3), ObjectId) and InvalidId if it is not a
188        valid ObjectId.
189
190        :Parameters:
191          - `oid`: a valid ObjectId
192        """
193        if isinstance(oid, ObjectId):
194            self.__id = oid.binary
195        elif isinstance(oid, str):
196            if len(oid) == 24:
197                try:
198                    self.__id = bytes.fromhex(oid)
199                except (TypeError, ValueError):
200                    _raise_invalid_id(oid)
201            else:
202                _raise_invalid_id(oid)
203        else:
204            raise TypeError(
205                "id must be an instance of (bytes, str, ObjectId), " "not %s" % (type(oid),)
206            )
207
208    @property
209    def binary(self):
210        """12-byte binary representation of this ObjectId."""
211        return self.__id
212
213    @property
214    def generation_time(self):
215        """A :class:`datetime.datetime` instance representing the time of
216        generation for this :class:`ObjectId`.
217
218        The :class:`datetime.datetime` is timezone aware, and
219        represents the generation time in UTC. It is precise to the
220        second.
221        """
222        timestamp = struct.unpack(">I", self.__id[0:4])[0]
223        return datetime.datetime.fromtimestamp(timestamp, utc)
224
225    def __getstate__(self):
226        """return value of object for pickling.
227        needed explicitly because __slots__() defined.
228        """
229        return self.__id
230
231    def __setstate__(self, value):
232        """explicit state set from pickling"""
233        # Provide backwards compatability with OIDs
234        # pickled with pymongo-1.9 or older.
235        if isinstance(value, dict):
236            oid = value["_ObjectId__id"]
237        else:
238            oid = value
239        # ObjectIds pickled in python 2.x used `str` for __id.
240        # In python 3.x this has to be converted to `bytes`
241        # by encoding latin-1.
242        if isinstance(oid, str):
243            self.__id = oid.encode("latin-1")
244        else:
245            self.__id = oid
246
247    def __str__(self):
248        return binascii.hexlify(self.__id).decode()
249
250    def __repr__(self):
251        return "ObjectId('%s')" % (str(self),)
252
253    def __eq__(self, other):
254        if isinstance(other, ObjectId):
255            return self.__id == other.binary
256        return NotImplemented
257
258    def __ne__(self, other):
259        if isinstance(other, ObjectId):
260            return self.__id != other.binary
261        return NotImplemented
262
263    def __lt__(self, other):
264        if isinstance(other, ObjectId):
265            return self.__id < other.binary
266        return NotImplemented
267
268    def __le__(self, other):
269        if isinstance(other, ObjectId):
270            return self.__id <= other.binary
271        return NotImplemented
272
273    def __gt__(self, other):
274        if isinstance(other, ObjectId):
275            return self.__id > other.binary
276        return NotImplemented
277
278    def __ge__(self, other):
279        if isinstance(other, ObjectId):
280            return self.__id >= other.binary
281        return NotImplemented
282
283    def __hash__(self):
284        """Get a hash value for this :class:`ObjectId`."""
285        return hash(self.__id)
class ObjectId:
 47class ObjectId(object):
 48    """A MongoDB ObjectId."""
 49
 50    _pid = os.getpid()
 51
 52    _inc = SystemRandom().randint(0, _MAX_COUNTER_VALUE)
 53    _inc_lock = threading.Lock()
 54
 55    __random = _random_bytes()
 56
 57    __slots__ = ("__id",)
 58
 59    _type_marker = 7
 60
 61    def __init__(self, oid=None):
 62        """Initialize a new ObjectId.
 63
 64        An ObjectId is a 12-byte unique identifier consisting of:
 65
 66          - a 4-byte value representing the seconds since the Unix epoch,
 67          - a 5-byte random value,
 68          - a 3-byte counter, starting with a random value.
 69
 70        By default, ``ObjectId()`` creates a new unique identifier. The
 71        optional parameter `oid` can be an :class:`ObjectId`, or any 12
 72        :class:`bytes`.
 73
 74        For example, the 12 bytes b'foo-bar-quux' do not follow the ObjectId
 75        specification but they are acceptable input::
 76
 77          >>> ObjectId(b'foo-bar-quux')
 78          ObjectId('666f6f2d6261722d71757578')
 79
 80        `oid` can also be a :class:`str` of 24 hex digits::
 81
 82          >>> ObjectId('0123456789ab0123456789ab')
 83          ObjectId('0123456789ab0123456789ab')
 84
 85        Raises :class:`~bson.errors.InvalidId` if `oid` is not 12 bytes nor
 86        24 hex digits, or :class:`TypeError` if `oid` is not an accepted type.
 87
 88        :Parameters:
 89          - `oid` (optional): a valid ObjectId.
 90
 91        .. seealso:: The MongoDB documentation on `ObjectIds`_.
 92
 93        .. versionchanged:: 3.8
 94           :class:`~bson.objectid.ObjectId` now implements the `ObjectID
 95           specification version 0.2
 96           <https://github.com/mongodb/specifications/blob/master/source/
 97           objectid.rst>`_.
 98        """
 99        if oid is None:
100            self.__generate()
101        elif isinstance(oid, bytes) and len(oid) == 12:
102            self.__id = oid
103        else:
104            self.__validate(oid)
105
106    @classmethod
107    def from_datetime(cls, generation_time):
108        """Create a dummy ObjectId instance with a specific generation time.
109
110        This method is useful for doing range queries on a field
111        containing :class:`ObjectId` instances.
112
113        .. warning::
114           It is not safe to insert a document containing an ObjectId
115           generated using this method. This method deliberately
116           eliminates the uniqueness guarantee that ObjectIds
117           generally provide. ObjectIds generated with this method
118           should be used exclusively in queries.
119
120        `generation_time` will be converted to UTC. Naive datetime
121        instances will be treated as though they already contain UTC.
122
123        An example using this helper to get documents where ``"_id"``
124        was generated before January 1, 2010 would be:
125
126        >>> gen_time = datetime.datetime(2010, 1, 1)
127        >>> dummy_id = ObjectId.from_datetime(gen_time)
128        >>> result = collection.find({"_id": {"$lt": dummy_id}})
129
130        :Parameters:
131          - `generation_time`: :class:`~datetime.datetime` to be used
132            as the generation time for the resulting ObjectId.
133        """
134        if generation_time.utcoffset() is not None:
135            generation_time = generation_time - generation_time.utcoffset()
136        timestamp = calendar.timegm(generation_time.timetuple())
137        oid = struct.pack(">I", int(timestamp)) + b"\x00\x00\x00\x00\x00\x00\x00\x00"
138        return cls(oid)
139
140    @classmethod
141    def is_valid(cls, oid):
142        """Checks if a `oid` string is valid or not.
143
144        :Parameters:
145          - `oid`: the object id to validate
146
147        .. versionadded:: 2.3
148        """
149        if not oid:
150            return False
151
152        try:
153            ObjectId(oid)
154            return True
155        except (InvalidId, TypeError):
156            return False
157
158    @classmethod
159    def _random(cls):
160        """Generate a 5-byte random number once per process."""
161        pid = os.getpid()
162        if pid != cls._pid:
163            cls._pid = pid
164            cls.__random = _random_bytes()
165        return cls.__random
166
167    def __generate(self):
168        """Generate a new value for this ObjectId."""
169
170        # 4 bytes current time
171        oid = struct.pack(">I", int(time.time()))
172
173        # 5 bytes random
174        oid += ObjectId._random()
175
176        # 3 bytes inc
177        with ObjectId._inc_lock:
178            oid += struct.pack(">I", ObjectId._inc)[1:4]
179            ObjectId._inc = (ObjectId._inc + 1) % (_MAX_COUNTER_VALUE + 1)
180
181        self.__id = oid
182
183    def __validate(self, oid):
184        """Validate and use the given id for this ObjectId.
185
186        Raises TypeError if id is not an instance of
187        (:class:`basestring` (:class:`str` or :class:`bytes`
188        in python 3), ObjectId) and InvalidId if it is not a
189        valid ObjectId.
190
191        :Parameters:
192          - `oid`: a valid ObjectId
193        """
194        if isinstance(oid, ObjectId):
195            self.__id = oid.binary
196        elif isinstance(oid, str):
197            if len(oid) == 24:
198                try:
199                    self.__id = bytes.fromhex(oid)
200                except (TypeError, ValueError):
201                    _raise_invalid_id(oid)
202            else:
203                _raise_invalid_id(oid)
204        else:
205            raise TypeError(
206                "id must be an instance of (bytes, str, ObjectId), " "not %s" % (type(oid),)
207            )
208
209    @property
210    def binary(self):
211        """12-byte binary representation of this ObjectId."""
212        return self.__id
213
214    @property
215    def generation_time(self):
216        """A :class:`datetime.datetime` instance representing the time of
217        generation for this :class:`ObjectId`.
218
219        The :class:`datetime.datetime` is timezone aware, and
220        represents the generation time in UTC. It is precise to the
221        second.
222        """
223        timestamp = struct.unpack(">I", self.__id[0:4])[0]
224        return datetime.datetime.fromtimestamp(timestamp, utc)
225
226    def __getstate__(self):
227        """return value of object for pickling.
228        needed explicitly because __slots__() defined.
229        """
230        return self.__id
231
232    def __setstate__(self, value):
233        """explicit state set from pickling"""
234        # Provide backwards compatability with OIDs
235        # pickled with pymongo-1.9 or older.
236        if isinstance(value, dict):
237            oid = value["_ObjectId__id"]
238        else:
239            oid = value
240        # ObjectIds pickled in python 2.x used `str` for __id.
241        # In python 3.x this has to be converted to `bytes`
242        # by encoding latin-1.
243        if isinstance(oid, str):
244            self.__id = oid.encode("latin-1")
245        else:
246            self.__id = oid
247
248    def __str__(self):
249        return binascii.hexlify(self.__id).decode()
250
251    def __repr__(self):
252        return "ObjectId('%s')" % (str(self),)
253
254    def __eq__(self, other):
255        if isinstance(other, ObjectId):
256            return self.__id == other.binary
257        return NotImplemented
258
259    def __ne__(self, other):
260        if isinstance(other, ObjectId):
261            return self.__id != other.binary
262        return NotImplemented
263
264    def __lt__(self, other):
265        if isinstance(other, ObjectId):
266            return self.__id < other.binary
267        return NotImplemented
268
269    def __le__(self, other):
270        if isinstance(other, ObjectId):
271            return self.__id <= other.binary
272        return NotImplemented
273
274    def __gt__(self, other):
275        if isinstance(other, ObjectId):
276            return self.__id > other.binary
277        return NotImplemented
278
279    def __ge__(self, other):
280        if isinstance(other, ObjectId):
281            return self.__id >= other.binary
282        return NotImplemented
283
284    def __hash__(self):
285        """Get a hash value for this :class:`ObjectId`."""
286        return hash(self.__id)

A MongoDB ObjectId.

ObjectId(oid=None)
 61    def __init__(self, oid=None):
 62        """Initialize a new ObjectId.
 63
 64        An ObjectId is a 12-byte unique identifier consisting of:
 65
 66          - a 4-byte value representing the seconds since the Unix epoch,
 67          - a 5-byte random value,
 68          - a 3-byte counter, starting with a random value.
 69
 70        By default, ``ObjectId()`` creates a new unique identifier. The
 71        optional parameter `oid` can be an :class:`ObjectId`, or any 12
 72        :class:`bytes`.
 73
 74        For example, the 12 bytes b'foo-bar-quux' do not follow the ObjectId
 75        specification but they are acceptable input::
 76
 77          >>> ObjectId(b'foo-bar-quux')
 78          ObjectId('666f6f2d6261722d71757578')
 79
 80        `oid` can also be a :class:`str` of 24 hex digits::
 81
 82          >>> ObjectId('0123456789ab0123456789ab')
 83          ObjectId('0123456789ab0123456789ab')
 84
 85        Raises :class:`~bson.errors.InvalidId` if `oid` is not 12 bytes nor
 86        24 hex digits, or :class:`TypeError` if `oid` is not an accepted type.
 87
 88        :Parameters:
 89          - `oid` (optional): a valid ObjectId.
 90
 91        .. seealso:: The MongoDB documentation on `ObjectIds`_.
 92
 93        .. versionchanged:: 3.8
 94           :class:`~bson.objectid.ObjectId` now implements the `ObjectID
 95           specification version 0.2
 96           <https://github.com/mongodb/specifications/blob/master/source/
 97           objectid.rst>`_.
 98        """
 99        if oid is None:
100            self.__generate()
101        elif isinstance(oid, bytes) and len(oid) == 12:
102            self.__id = oid
103        else:
104            self.__validate(oid)

Initialize a new ObjectId.

An ObjectId is a 12-byte unique identifier consisting of:

  • a 4-byte value representing the seconds since the Unix epoch,
  • a 5-byte random value,
  • a 3-byte counter, starting with a random value.

By default, ObjectId() creates a new unique identifier. The optional parameter oid can be an ObjectId, or any 12 bytes.

For example, the 12 bytes b'foo-bar-quux' do not follow the ObjectId specification but they are acceptable input::

>>> ObjectId(b'foo-bar-quux')
ObjectId('666f6f2d6261722d71757578')

oid can also be a str of 24 hex digits::

>>> ObjectId('0123456789ab0123456789ab')
ObjectId('0123456789ab0123456789ab')

Raises ~bson.errors.InvalidId if oid is not 12 bytes nor 24 hex digits, or TypeError if oid is not an accepted type.

:Parameters:

  • oid (optional): a valid ObjectId.

seealso The MongoDB documentation on ObjectIds_..

Changed in version 3.8: ~bson.objectid.ObjectId now implements the ObjectID specification version 0.2 <https://github.com/mongodb/specifications/blob/master/source/ objectid.rst>_.

@classmethod
def from_datetime(cls, generation_time):
106    @classmethod
107    def from_datetime(cls, generation_time):
108        """Create a dummy ObjectId instance with a specific generation time.
109
110        This method is useful for doing range queries on a field
111        containing :class:`ObjectId` instances.
112
113        .. warning::
114           It is not safe to insert a document containing an ObjectId
115           generated using this method. This method deliberately
116           eliminates the uniqueness guarantee that ObjectIds
117           generally provide. ObjectIds generated with this method
118           should be used exclusively in queries.
119
120        `generation_time` will be converted to UTC. Naive datetime
121        instances will be treated as though they already contain UTC.
122
123        An example using this helper to get documents where ``"_id"``
124        was generated before January 1, 2010 would be:
125
126        >>> gen_time = datetime.datetime(2010, 1, 1)
127        >>> dummy_id = ObjectId.from_datetime(gen_time)
128        >>> result = collection.find({"_id": {"$lt": dummy_id}})
129
130        :Parameters:
131          - `generation_time`: :class:`~datetime.datetime` to be used
132            as the generation time for the resulting ObjectId.
133        """
134        if generation_time.utcoffset() is not None:
135            generation_time = generation_time - generation_time.utcoffset()
136        timestamp = calendar.timegm(generation_time.timetuple())
137        oid = struct.pack(">I", int(timestamp)) + b"\x00\x00\x00\x00\x00\x00\x00\x00"
138        return cls(oid)

Create a dummy ObjectId instance with a specific generation time.

This method is useful for doing range queries on a field containing ObjectId instances.

It is not safe to insert a document containing an ObjectId generated using this method. This method deliberately eliminates the uniqueness guarantee that ObjectIds generally provide. ObjectIds generated with this method should be used exclusively in queries.

generation_time will be converted to UTC. Naive datetime instances will be treated as though they already contain UTC.

An example using this helper to get documents where "_id" was generated before January 1, 2010 would be:

>>> gen_time = datetime.datetime(2010, 1, 1)
>>> dummy_id = ObjectId.from_datetime(gen_time)
>>> result = collection.find({"_id": {"$lt": dummy_id}})

:Parameters:

  • generation_time: ~datetime.datetime to be used as the generation time for the resulting ObjectId.
@classmethod
def is_valid(cls, oid):
140    @classmethod
141    def is_valid(cls, oid):
142        """Checks if a `oid` string is valid or not.
143
144        :Parameters:
145          - `oid`: the object id to validate
146
147        .. versionadded:: 2.3
148        """
149        if not oid:
150            return False
151
152        try:
153            ObjectId(oid)
154            return True
155        except (InvalidId, TypeError):
156            return False

Checks if a oid string is valid or not.

:Parameters:

  • oid: the object id to validate

New in version 2.3.

binary

12-byte binary representation of this ObjectId.

generation_time

A datetime.datetime instance representing the time of generation for this ObjectId.

The datetime.datetime is timezone aware, and represents the generation time in UTC. It is precise to the second.