Spaces:
Running
Running
# Copyright 2009-2015 MongoDB, Inc. | |
# Modifications copyright (C) 2018 Gabriel Leopoldino | |
# | |
# 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. | |
"""Tools for working with MongoDB `ObjectIds | |
<http://dochub.mongodb.org/core/objectids>`_. | |
""" | |
import binascii | |
import calendar | |
import datetime | |
import os | |
import random | |
import socket | |
import struct | |
import threading | |
import time | |
from bson.py3compat import PY3, bytes_from_hex, string_type, text_type | |
from bson.tz_util import utc | |
# fnv_1a_24 adaptation taken from MongoDB Python Driver at https://github.com/mongodb/mongo-python-driver/commit/61850357a0e0eeec1a30e1adc0bbf7ebee807358 | |
if PY3: | |
_ord = lambda x: x | |
else: | |
_ord = ord | |
# http://isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a | |
def _fnv_1a_24(data, _ord=_ord): | |
"""FNV-1a 24 bit hash""" | |
# http://www.isthe.com/chongo/tech/comp/fnv/index.html#xor-fold | |
# Start with FNV-1a 32 bit. | |
hash_size = 2 ** 32 | |
fnv_32_prime = 16777619 | |
fnv_1a_hash = 2166136261 # 32-bit FNV-1 offset basis | |
for elt in data: | |
fnv_1a_hash = fnv_1a_hash ^ _ord(elt) | |
fnv_1a_hash = (fnv_1a_hash * fnv_32_prime) % hash_size | |
# xor-fold the result to 24 bit. | |
return (fnv_1a_hash >> 24) ^ (fnv_1a_hash & 0xffffff) | |
def _machine_bytes(): | |
"""Get the machine portion of an ObjectId. | |
""" | |
# gethostname() returns a unicode string in python 3.x | |
# We only need 3 bytes, and _fnv_1a_24 returns a 24 bit integer. | |
# Remove the padding byte. | |
return struct.pack("<I", _fnv_1a_24(socket.gethostname().encode()))[:3] | |
class InvalidId(ValueError): | |
"""Raised when trying to create an ObjectId from invalid data. | |
""" | |
def _raise_invalid_id(oid): | |
raise InvalidId( | |
"%r is not a valid ObjectId, it must be a 12-byte input" | |
" or a 24-character hex string" % oid) | |
class ObjectId(object): | |
"""A MongoDB ObjectId. | |
""" | |
_inc = random.randint(0, 0xFFFFFF) | |
_inc_lock = threading.Lock() | |
_machine_bytes = _machine_bytes() | |
__slots__ = ('__id') | |
_type_marker = 7 | |
def __init__(self, oid=None): | |
"""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 3-byte machine identifier, | |
- a 2-byte process id, and | |
- a 3-byte counter, starting with a random value. | |
By default, ``ObjectId()`` creates a new unique identifier. The | |
optional parameter `oid` can be an :class:`ObjectId`, or any 12 | |
:class:`bytes` or, in Python 2, any 12-character :class:`str`. | |
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 :class:`unicode` or :class:`str` of 24 hex digits:: | |
>>> ObjectId('0123456789ab0123456789ab') | |
ObjectId('0123456789ab0123456789ab') | |
>>> | |
>>> # A u-prefixed unicode literal: | |
>>> ObjectId(u'0123456789ab0123456789ab') | |
ObjectId('0123456789ab0123456789ab') | |
Raises :class:`~bson.errors.InvalidId` if `oid` is not 12 bytes nor | |
24 hex digits, or :class:`TypeError` if `oid` is not an accepted type. | |
:Parameters: | |
- `oid` (optional): a valid ObjectId. | |
.. mongodoc:: objectids | |
""" | |
if oid is None: | |
self.__generate() | |
elif isinstance(oid, bytes) and len(oid) == 12: | |
self.__id = oid | |
else: | |
self.__validate(oid) | |
def from_datetime(cls, generation_time): | |
"""Create a dummy ObjectId instance with a specific generation time. | |
This method is useful for doing range queries on a field | |
containing :class:`ObjectId` instances. | |
.. warning:: | |
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`: :class:`~datetime.datetime` to be used | |
as the generation time for the resulting ObjectId. | |
""" | |
if generation_time.utcoffset() is not None: | |
generation_time = generation_time - generation_time.utcoffset() | |
timestamp = calendar.timegm(generation_time.timetuple()) | |
oid = struct.pack( | |
">i", int(timestamp)) + b"\x00\x00\x00\x00\x00\x00\x00\x00" | |
return cls(oid) | |
def is_valid(cls, oid): | |
"""Checks if a `oid` string is valid or not. | |
:Parameters: | |
- `oid`: the object id to validate | |
.. versionadded:: 2.3 | |
""" | |
if not oid: | |
return False | |
try: | |
ObjectId(oid) | |
return True | |
except (InvalidId, TypeError): | |
return False | |
def __generate(self): | |
"""Generate a new value for this ObjectId. | |
""" | |
# 4 bytes current time | |
oid = struct.pack(">i", int(time.time())) | |
# 3 bytes machine | |
oid += ObjectId._machine_bytes | |
# 2 bytes pid | |
oid += struct.pack(">H", os.getpid() % 0xFFFF) | |
# 3 bytes inc | |
with ObjectId._inc_lock: | |
oid += struct.pack(">i", ObjectId._inc)[1:4] | |
ObjectId._inc = (ObjectId._inc + 1) % 0xFFFFFF | |
self.__id = oid | |
def __validate(self, oid): | |
"""Validate and use the given id for this ObjectId. | |
Raises TypeError if id is not an instance of | |
(:class:`basestring` (:class:`str` or :class:`bytes` | |
in python 3), ObjectId) and InvalidId if it is not a | |
valid ObjectId. | |
:Parameters: | |
- `oid`: a valid ObjectId | |
""" | |
if isinstance(oid, ObjectId): | |
self.__id = oid.binary | |
# bytes or unicode in python 2, str in python 3 | |
elif isinstance(oid, string_type): | |
if len(oid) == 24: | |
try: | |
self.__id = bytes_from_hex(oid) | |
except (TypeError, ValueError): | |
_raise_invalid_id(oid) | |
else: | |
_raise_invalid_id(oid) | |
else: | |
raise TypeError("id must be an instance of (bytes, %s, ObjectId), " | |
"not %s" % (text_type.__name__, type(oid))) | |
def binary(self): | |
"""12-byte binary representation of this ObjectId. | |
""" | |
return self.__id | |
def generation_time(self): | |
"""A :class:`datetime.datetime` instance representing the time of | |
generation for this :class:`ObjectId`. | |
The :class:`datetime.datetime` is timezone aware, and | |
represents the generation time in UTC. It is precise to the | |
second. | |
""" | |
timestamp = struct.unpack(">i", self.__id[0:4])[0] | |
return datetime.datetime.fromtimestamp(timestamp, utc) | |
def __getstate__(self): | |
"""return value of object for pickling. | |
needed explicitly because __slots__() defined. | |
""" | |
return self.__id | |
def __setstate__(self, value): | |
"""explicit state set from pickling | |
""" | |
# Provide backwards compatability with OIDs | |
# pickled with pymongo-1.9 or older. | |
if isinstance(value, dict): | |
oid = value["_ObjectId__id"] | |
else: | |
oid = value | |
# ObjectIds pickled in python 2.x used `str` for __id. | |
# In python 3.x this has to be converted to `bytes` | |
# by encoding latin-1. | |
if PY3 and isinstance(oid, text_type): | |
self.__id = oid.encode('latin-1') | |
else: | |
self.__id = oid | |
def __str__(self): | |
if PY3: | |
return binascii.hexlify(self.__id).decode() | |
return binascii.hexlify(self.__id) | |
def __repr__(self): | |
return "ObjectId('%s')" % (str(self),) | |
def __eq__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id == other.binary | |
return NotImplemented | |
def __ne__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id != other.binary | |
return NotImplemented | |
def __lt__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id < other.binary | |
return NotImplemented | |
def __le__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id <= other.binary | |
return NotImplemented | |
def __gt__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id > other.binary | |
return NotImplemented | |
def __ge__(self, other): | |
if isinstance(other, ObjectId): | |
return self.__id >= other.binary | |
return NotImplemented | |
def __hash__(self): | |
"""Get a hash value for this :class:`ObjectId`.""" | |
return hash(self.__id) | |