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)
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.
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>
_.
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.
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.
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.