00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 """Tools for working with MongoDB `ObjectIds
00016 <http://dochub.mongodb.org/core/objectids>`_.
00017 """
00018
00019 import calendar
00020 import datetime
00021 try:
00022 import hashlib
00023 _md5func = hashlib.md5
00024 except ImportError:
00025 import md5
00026 _md5func = md5.new
00027 import os
00028 import socket
00029 import struct
00030 import threading
00031 import time
00032
00033 from bson.errors import InvalidId
00034 from bson.tz_util import utc
00035
00036
00037 def _machine_bytes():
00038 """Get the machine portion of an ObjectId.
00039 """
00040 machine_hash = _md5func()
00041 machine_hash.update(socket.gethostname())
00042 return machine_hash.digest()[0:3]
00043
00044
00045 class ObjectId(object):
00046 """A MongoDB ObjectId.
00047 """
00048
00049 _inc = 0
00050 _inc_lock = threading.Lock()
00051
00052 _machine_bytes = _machine_bytes()
00053
00054 __slots__ = ('__id')
00055
00056 def __init__(self, oid=None):
00057 """Initialize a new ObjectId.
00058
00059 If `oid` is ``None``, create a new (unique) ObjectId. If `oid`
00060 is an instance of (``basestring``, :class:`ObjectId`) validate
00061 it and use that. Otherwise, a :class:`TypeError` is
00062 raised. If `oid` is invalid,
00063 :class:`~bson.errors.InvalidId` is raised.
00064
00065 :Parameters:
00066 - `oid` (optional): a valid ObjectId (12 byte binary or 24 character
00067 hex string)
00068
00069 .. versionadded:: 1.2.1
00070 The `oid` parameter can be a ``unicode`` instance (that contains
00071 only hexadecimal digits).
00072
00073 .. mongodoc:: objectids
00074 """
00075 if oid is None:
00076 self.__generate()
00077 else:
00078 self.__validate(oid)
00079
00080 @classmethod
00081 def from_datetime(cls, generation_time):
00082 """Create a dummy ObjectId instance with a specific generation time.
00083
00084 This method is useful for doing range queries on a field
00085 containing :class:`ObjectId` instances.
00086
00087 .. warning::
00088 It is not safe to insert a document containing an ObjectId
00089 generated using this method. This method deliberately
00090 eliminates the uniqueness guarantee that ObjectIds
00091 generally provide. ObjectIds generated with this method
00092 should be used exclusively in queries.
00093
00094 `generation_time` will be converted to UTC. Naive datetime
00095 instances will be treated as though they already contain UTC.
00096
00097 An example using this helper to get documents where ``"_id"``
00098 was generated before January 1, 2010 would be:
00099
00100 >>> gen_time = datetime.datetime(2010, 1, 1)
00101 >>> dummy_id = ObjectId.from_datetime(gen_time)
00102 >>> result = collection.find({"_id": {"$lt": dummy_id}})
00103
00104 :Parameters:
00105 - `generation_time`: :class:`~datetime.datetime` to be used
00106 as the generation time for the resulting ObjectId.
00107
00108 .. versionchanged:: 1.8
00109 Properly handle timezone aware values for
00110 `generation_time`.
00111
00112 .. versionadded:: 1.6
00113 """
00114 if generation_time.utcoffset() is not None:
00115 generation_time = generation_time - generation_time.utcoffset()
00116 ts = calendar.timegm(generation_time.timetuple())
00117 oid = struct.pack(">i", int(ts)) + "\x00" * 8
00118 return cls(oid)
00119
00120 def __generate(self):
00121 """Generate a new value for this ObjectId.
00122 """
00123 oid = ""
00124
00125
00126 oid += struct.pack(">i", int(time.time()))
00127
00128
00129 oid += ObjectId._machine_bytes
00130
00131
00132 oid += struct.pack(">H", os.getpid() % 0xFFFF)
00133
00134
00135 ObjectId._inc_lock.acquire()
00136 oid += struct.pack(">i", ObjectId._inc)[1:4]
00137 ObjectId._inc = (ObjectId._inc + 1) % 0xFFFFFF
00138 ObjectId._inc_lock.release()
00139
00140 self.__id = oid
00141
00142 def __validate(self, oid):
00143 """Validate and use the given id for this ObjectId.
00144
00145 Raises TypeError if id is not an instance of (str, ObjectId) and
00146 InvalidId if it is not a valid ObjectId.
00147
00148 :Parameters:
00149 - `oid`: a valid ObjectId
00150 """
00151 if isinstance(oid, ObjectId):
00152 self.__id = oid.__id
00153 elif isinstance(oid, basestring):
00154 if len(oid) == 12:
00155 self.__id = oid
00156 elif len(oid) == 24:
00157 try:
00158 self.__id = oid.decode("hex")
00159 except TypeError:
00160 raise InvalidId("%s is not a valid ObjectId" % oid)
00161 else:
00162 raise InvalidId("%s is not a valid ObjectId" % oid)
00163 else:
00164 raise TypeError("id must be an instance of (str, ObjectId), "
00165 "not %s" % type(oid))
00166
00167 @property
00168 def binary(self):
00169 """12-byte binary representation of this ObjectId.
00170 """
00171 return self.__id
00172
00173 @property
00174 def generation_time(self):
00175 """A :class:`datetime.datetime` instance representing the time of
00176 generation for this :class:`ObjectId`.
00177
00178 The :class:`datetime.datetime` is timezone aware, and
00179 represents the generation time in UTC. It is precise to the
00180 second.
00181
00182 .. versionchanged:: 1.8
00183 Now return an aware datetime instead of a naive one.
00184
00185 .. versionadded:: 1.2
00186 """
00187 t = struct.unpack(">i", self.__id[0:4])[0]
00188 return datetime.datetime.fromtimestamp(t, utc)
00189
00190 def __str__(self):
00191 return self.__id.encode("hex")
00192
00193 def __repr__(self):
00194 return "ObjectId('%s')" % self.__id.encode("hex")
00195
00196 def __cmp__(self, other):
00197 if isinstance(other, ObjectId):
00198 return cmp(self.__id, other.__id)
00199 return NotImplemented
00200
00201 def __hash__(self):
00202 """Get a hash value for this :class:`ObjectId`.
00203
00204 .. versionadded:: 1.1
00205 """
00206 return hash(self.__id)