added deps
This commit is contained in:
69
modules/bson/__init__.py
Normal file
69
modules/bson/__init__.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python -OOOO
|
||||
# vim: set fileencoding=utf8 shiftwidth=4 tabstop=4 textwidth=80 foldmethod=marker :
|
||||
# Copyright (c) 2010, Kou Man Tong. All rights reserved.
|
||||
# For licensing, see LICENSE file included in the package.
|
||||
"""
|
||||
BSON serialization and deserialization logic.
|
||||
Specifications taken from: http://bsonspec.org/#/specification
|
||||
The following types are unsupported, because for data exchange purposes, they're
|
||||
over-engineered:
|
||||
0x06 (Undefined)
|
||||
0x0b (Regex - Exactly which flavor do you want? Better let higher level
|
||||
programmers make that decision.)
|
||||
0x0c (DBPointer)
|
||||
0x0d (JavaScript code)
|
||||
0x0e (Symbol)
|
||||
0x0f (JS w/ scope)
|
||||
0x11 (MongoDB-specific timestamp)
|
||||
|
||||
For binaries, only the default 0x0 type is supported.
|
||||
"""
|
||||
|
||||
from .codec import *
|
||||
from .objectid import ObjectId
|
||||
|
||||
__all__ = ["loads", "dumps"]
|
||||
|
||||
|
||||
def dumps(obj, generator=None, on_unknown=None):
|
||||
"""
|
||||
Given a dict, outputs a BSON string.
|
||||
|
||||
generator is an optional function which accepts the dictionary/array being
|
||||
encoded, the current DFS traversal stack, and outputs an iterator indicating
|
||||
the correct encoding order for keys.
|
||||
"""
|
||||
if isinstance(obj, BSONCoding):
|
||||
return encode_object(obj, [],
|
||||
generator_func=generator, on_unknown=on_unknown)
|
||||
return encode_document(obj, [],
|
||||
generator_func=generator, on_unknown=on_unknown)
|
||||
|
||||
|
||||
def loads(data):
|
||||
"""
|
||||
Given a BSON string, outputs a dict.
|
||||
"""
|
||||
return decode_document(data, 0)[1]
|
||||
|
||||
|
||||
def patch_socket():
|
||||
"""
|
||||
Patches the Python socket class such that sockets can send and receive BSON
|
||||
objects atomically.
|
||||
|
||||
This adds the following functions to socket:
|
||||
|
||||
recvbytes(bytes_needed, sock_buf = None) - reads bytes_needed bytes
|
||||
atomically. Returns None if socket closed.
|
||||
|
||||
recvobj() - reads a BSON document from the socket atomically and returns
|
||||
the deserialized dictionary. Returns None if socket closed.
|
||||
|
||||
sendobj(obj) - sends a BSON document to the socket atomically.
|
||||
"""
|
||||
from socket import socket
|
||||
from .network import recvbytes, recvobj, sendobj
|
||||
socket.recvbytes = recvbytes
|
||||
socket.recvobj = recvobj
|
||||
socket.sendobj = sendobj
|
396
modules/bson/codec.py
Normal file
396
modules/bson/codec.py
Normal file
@@ -0,0 +1,396 @@
|
||||
#!/usr/bin/python -OOOO
|
||||
# vim: set fileencoding=utf8 shiftwidth=4 tabstop=4 textwidth=80 foldmethod=marker :
|
||||
# Copyright (c) 2010, Kou Man Tong. All rights reserved.
|
||||
# Copyright (c) 2015, Ayun Park. All rights reserved.
|
||||
# For licensing, see LICENSE file included in the package.
|
||||
"""
|
||||
Base codec functions for bson.
|
||||
"""
|
||||
import struct
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from uuid import UUID
|
||||
from decimal import Decimal
|
||||
try:
|
||||
from io import BytesIO as StringIO
|
||||
except ImportError:
|
||||
from cStringIO import StringIO
|
||||
|
||||
import calendar
|
||||
from dateutil.tz import tzutc
|
||||
from binascii import b2a_hex
|
||||
|
||||
from six import integer_types, iterkeys, text_type, PY3
|
||||
from six.moves import xrange
|
||||
|
||||
|
||||
utc = tzutc()
|
||||
|
||||
class MissingClassDefinition(ValueError):
|
||||
def __init__(self, class_name):
|
||||
super(MissingClassDefinition,
|
||||
self).__init__("No class definition for class %s" % (class_name,))
|
||||
|
||||
|
||||
class UnknownSerializerError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class MissingTimezoneWarning(RuntimeWarning):
|
||||
def __init__(self, *args):
|
||||
args = list(args)
|
||||
if len(args) < 1:
|
||||
args.append("Input datetime object has no tzinfo, assuming UTC.")
|
||||
super(MissingTimezoneWarning, self).__init__(*args)
|
||||
|
||||
|
||||
class TraversalStep(object):
|
||||
def __init__(self, parent, key):
|
||||
self.parent = parent
|
||||
self.key = key
|
||||
|
||||
|
||||
class BSONCoding(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def bson_encode(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def bson_init(self, raw_values):
|
||||
pass
|
||||
|
||||
|
||||
classes = {}
|
||||
|
||||
|
||||
def import_class(cls):
|
||||
if not issubclass(cls, BSONCoding):
|
||||
return
|
||||
|
||||
global classes
|
||||
classes[cls.__name__] = cls
|
||||
|
||||
|
||||
def import_classes(*args):
|
||||
for cls in args:
|
||||
import_class(cls)
|
||||
|
||||
|
||||
def import_classes_from_modules(*args):
|
||||
for module in args:
|
||||
for item in module.__dict__:
|
||||
if hasattr(item, "__new__") and hasattr(item, "__name__"):
|
||||
import_class(item)
|
||||
|
||||
|
||||
def encode_object(obj, traversal_stack, generator_func, on_unknown=None):
|
||||
values = obj.bson_encode()
|
||||
class_name = obj.__class__.__name__
|
||||
values["$$__CLASS_NAME__$$"] = class_name
|
||||
return encode_document(values, traversal_stack, obj,
|
||||
generator_func, on_unknown)
|
||||
|
||||
|
||||
def encode_object_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown):
|
||||
return b"\x03" + encode_cstring(name) + \
|
||||
encode_object(value, traversal_stack,
|
||||
generator_func=generator_func, on_unknown=on_unknown)
|
||||
|
||||
|
||||
class _EmptyClass(object):
|
||||
pass
|
||||
|
||||
|
||||
def decode_object(raw_values):
|
||||
global classes
|
||||
class_name = raw_values["$$__CLASS_NAME__$$"]
|
||||
try:
|
||||
cls = classes[class_name]
|
||||
except KeyError:
|
||||
raise MissingClassDefinition(class_name)
|
||||
|
||||
retval = _EmptyClass()
|
||||
retval.__class__ = cls
|
||||
alt_retval = retval.bson_init(raw_values)
|
||||
return alt_retval or retval
|
||||
|
||||
|
||||
def encode_string(value):
|
||||
value = value.encode("utf-8")
|
||||
length = len(value)
|
||||
return struct.pack("<i%dsb" % (length,), length + 1, value, 0)
|
||||
|
||||
|
||||
def encode_cstring(value):
|
||||
if not isinstance(value, bytes):
|
||||
value = str(value).encode("utf-8")
|
||||
if b"\x00" in value:
|
||||
raise ValueError("Element names may not include NUL bytes.")
|
||||
# A NUL byte is used to delimit our string, accepting one would cause
|
||||
# our string to terminate early.
|
||||
return value + b"\x00"
|
||||
|
||||
|
||||
def encode_binary(value, binary_subtype=0):
|
||||
length = len(value)
|
||||
return struct.pack("<ib", length, binary_subtype) + value
|
||||
|
||||
|
||||
def encode_double(value):
|
||||
return struct.pack("<d", value)
|
||||
|
||||
|
||||
ELEMENT_TYPES = {
|
||||
0x01: "double",
|
||||
0x02: "string",
|
||||
0x03: "document",
|
||||
0x04: "array",
|
||||
0x05: "binary",
|
||||
0x07: "object_id",
|
||||
0x08: "boolean",
|
||||
0x09: "UTCdatetime",
|
||||
0x0A: "none",
|
||||
0x10: "int32",
|
||||
0x11: "uint64",
|
||||
0x12: "int64"
|
||||
}
|
||||
|
||||
|
||||
def encode_double_element(name, value):
|
||||
return b"\x01" + encode_cstring(name) + encode_double(value)
|
||||
|
||||
|
||||
def encode_string_element(name, value):
|
||||
return b"\x02" + encode_cstring(name) + encode_string(value)
|
||||
|
||||
|
||||
def _is_string(value):
|
||||
if isinstance(value, text_type):
|
||||
return True
|
||||
elif isinstance(value, str) or isinstance(value, bytes):
|
||||
try:
|
||||
unicode(value, errors='strict')
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def encode_value(name, value, buf, traversal_stack,
|
||||
generator_func, on_unknown=None):
|
||||
if isinstance(value, bool):
|
||||
buf.write(encode_boolean_element(name, value))
|
||||
elif isinstance(value, integer_types):
|
||||
if value < -0x80000000 or 0x7FFFFFFFFFFFFFFF >= value > 0x7fffffff:
|
||||
buf.write(encode_int64_element(name, value))
|
||||
elif value > 0x7FFFFFFFFFFFFFFF:
|
||||
if value > 0xFFFFFFFFFFFFFFFF:
|
||||
raise Exception("BSON format supports only int value < %s" % 0xFFFFFFFFFFFFFFFF)
|
||||
buf.write(encode_uint64_element(name, value))
|
||||
else:
|
||||
buf.write(encode_int32_element(name, value))
|
||||
elif isinstance(value, float):
|
||||
buf.write(encode_double_element(name, value))
|
||||
elif _is_string(value):
|
||||
buf.write(encode_string_element(name, value))
|
||||
elif isinstance(value, str) or isinstance(value, bytes):
|
||||
buf.write(encode_binary_element(name, value))
|
||||
elif isinstance(value, UUID):
|
||||
buf.write(encode_binary_element(name, value.bytes, binary_subtype=4))
|
||||
elif isinstance(value, datetime):
|
||||
buf.write(encode_utc_datetime_element(name, value))
|
||||
elif value is None:
|
||||
buf.write(encode_none_element(name, value))
|
||||
elif isinstance(value, dict):
|
||||
buf.write(encode_document_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown))
|
||||
elif isinstance(value, list) or isinstance(value, tuple):
|
||||
buf.write(encode_array_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown))
|
||||
elif isinstance(value, BSONCoding):
|
||||
buf.write(encode_object_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown))
|
||||
elif isinstance(value, Decimal):
|
||||
buf.write(encode_double_element(name, float(value)))
|
||||
else:
|
||||
if on_unknown is not None:
|
||||
encode_value(name, on_unknown(value), buf, traversal_stack,
|
||||
generator_func, on_unknown)
|
||||
else:
|
||||
raise UnknownSerializerError()
|
||||
|
||||
|
||||
def encode_document(obj, traversal_stack, traversal_parent=None,
|
||||
generator_func=None, on_unknown=None):
|
||||
buf = StringIO()
|
||||
key_iter = iterkeys(obj)
|
||||
if generator_func is not None:
|
||||
key_iter = generator_func(obj, traversal_stack)
|
||||
for name in key_iter:
|
||||
value = obj[name]
|
||||
traversal_stack.append(TraversalStep(traversal_parent or obj, name))
|
||||
encode_value(name, value, buf, traversal_stack,
|
||||
generator_func, on_unknown)
|
||||
traversal_stack.pop()
|
||||
e_list = buf.getvalue()
|
||||
e_list_length = len(e_list)
|
||||
return struct.pack("<i%dsb" % (e_list_length,),
|
||||
e_list_length + 4 + 1, e_list, 0)
|
||||
|
||||
|
||||
def encode_array(array, traversal_stack, traversal_parent=None,
|
||||
generator_func=None, on_unknown=None):
|
||||
buf = StringIO()
|
||||
for i in xrange(0, len(array)):
|
||||
value = array[i]
|
||||
traversal_stack.append(TraversalStep(traversal_parent or array, i))
|
||||
encode_value(str(i), value, buf, traversal_stack,
|
||||
generator_func, on_unknown)
|
||||
traversal_stack.pop()
|
||||
e_list = buf.getvalue()
|
||||
e_list_length = len(e_list)
|
||||
return struct.pack("<i%dsb" % (e_list_length,),
|
||||
e_list_length + 4 + 1, e_list, 0)
|
||||
|
||||
|
||||
def decode_binary_subtype(value, binary_subtype):
|
||||
if binary_subtype in [0x03, 0x04]: # legacy UUID, UUID
|
||||
return UUID(bytes=value)
|
||||
return value
|
||||
|
||||
|
||||
def decode_document(data, base, as_array=False):
|
||||
# Create all the struct formats we might use.
|
||||
double_struct = struct.Struct("<d")
|
||||
int_struct = struct.Struct("<i")
|
||||
char_struct = struct.Struct("<b")
|
||||
long_struct = struct.Struct("<q")
|
||||
uint64_struct = struct.Struct("<Q")
|
||||
int_char_struct = struct.Struct("<ib")
|
||||
|
||||
length = struct.unpack("<i", data[base:base + 4])[0]
|
||||
end_point = base + length
|
||||
if data[end_point - 1] not in ('\0', 0):
|
||||
raise ValueError('missing null-terminator in document')
|
||||
base += 4
|
||||
retval = [] if as_array else {}
|
||||
decode_name = not as_array
|
||||
|
||||
while base < end_point - 1:
|
||||
|
||||
element_type = char_struct.unpack(data[base:base + 1])[0]
|
||||
|
||||
if PY3:
|
||||
ll = data.index(0, base + 1) + 1
|
||||
base, name = ll, data[base + 1:ll - 1].decode("utf-8") \
|
||||
if decode_name else None
|
||||
else:
|
||||
ll = data.index("\x00", base + 1) + 1
|
||||
base, name = ll, unicode(data[base + 1:ll - 1])\
|
||||
if decode_name else None
|
||||
|
||||
if element_type == 0x01: # double
|
||||
value = double_struct.unpack(data[base: base + 8])[0]
|
||||
base += 8
|
||||
elif element_type == 0x02: # string
|
||||
length = int_struct.unpack(data[base:base + 4])[0]
|
||||
value = data[base + 4: base + 4 + length - 1]
|
||||
if PY3:
|
||||
value = value.decode("utf-8")
|
||||
else:
|
||||
value = unicode(value)
|
||||
base += 4 + length
|
||||
elif element_type == 0x03: # document
|
||||
base, value = decode_document(data, base)
|
||||
elif element_type == 0x04: # array
|
||||
base, value = decode_document(data, base, as_array=True)
|
||||
elif element_type == 0x05: # binary
|
||||
length, binary_subtype = int_char_struct.unpack(
|
||||
data[base:base + 5])
|
||||
value = data[base + 5:base + 5 + length]
|
||||
value = decode_binary_subtype(value, binary_subtype)
|
||||
base += 5 + length
|
||||
elif element_type == 0x07: # object_id
|
||||
value = b2a_hex(data[base:base + 12])
|
||||
base += 12
|
||||
elif element_type == 0x08: # boolean
|
||||
value = bool(char_struct.unpack(data[base:base + 1])[0])
|
||||
base += 1
|
||||
elif element_type == 0x09: # UTCdatetime
|
||||
value = datetime.fromtimestamp(
|
||||
long_struct.unpack(data[base:base + 8])[0] / 1000.0, utc)
|
||||
base += 8
|
||||
elif element_type == 0x0A: # none
|
||||
value = None
|
||||
elif element_type == 0x10: # int32
|
||||
value = int_struct.unpack(data[base:base + 4])[0]
|
||||
base += 4
|
||||
elif element_type == 0x11: # uint64
|
||||
value = uint64_struct.unpack(data[base:base + 8])[0]
|
||||
base += 8
|
||||
elif element_type == 0x12: # int64
|
||||
value = long_struct.unpack(data[base:base + 8])[0]
|
||||
base += 8
|
||||
|
||||
if as_array:
|
||||
retval.append(value)
|
||||
else:
|
||||
retval[name] = value
|
||||
if "$$__CLASS_NAME__$$" in retval:
|
||||
retval = decode_object(retval)
|
||||
return end_point, retval
|
||||
|
||||
|
||||
def encode_document_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown):
|
||||
return b"\x03" + encode_cstring(name) + \
|
||||
encode_document(value, traversal_stack,
|
||||
generator_func=generator_func, on_unknown=on_unknown)
|
||||
|
||||
|
||||
def encode_array_element(name, value, traversal_stack,
|
||||
generator_func, on_unknown):
|
||||
return b"\x04" + encode_cstring(name) + \
|
||||
encode_array(value, traversal_stack,
|
||||
generator_func=generator_func, on_unknown=on_unknown)
|
||||
|
||||
|
||||
def encode_binary_element(name, value, binary_subtype=0):
|
||||
return b"\x05" + encode_cstring(name) + encode_binary(value, binary_subtype=binary_subtype)
|
||||
|
||||
|
||||
def encode_boolean_element(name, value):
|
||||
return b"\x08" + encode_cstring(name) + struct.pack("<b", value)
|
||||
|
||||
|
||||
def encode_utc_datetime_element(name, value):
|
||||
if value.tzinfo is None:
|
||||
warnings.warn(MissingTimezoneWarning(), None, 4)
|
||||
value = int(round(calendar.timegm(value.utctimetuple()) * 1000 +
|
||||
(value.microsecond / 1000.0)))
|
||||
return b"\x09" + encode_cstring(name) + struct.pack("<q", value)
|
||||
|
||||
|
||||
def encode_none_element(name, value):
|
||||
return b"\x0a" + encode_cstring(name)
|
||||
|
||||
|
||||
def encode_int32_element(name, value):
|
||||
value = struct.pack("<i", value)
|
||||
return b"\x10" + encode_cstring(name) + value
|
||||
|
||||
|
||||
def encode_uint64_element(name, value):
|
||||
return b"\x11" + encode_cstring(name) + struct.pack("<Q", value)
|
||||
|
||||
|
||||
def encode_int64_element(name, value):
|
||||
return b"\x12" + encode_cstring(name) + struct.pack("<q", value)
|
||||
|
||||
|
||||
def encode_object_id_element(name, value):
|
||||
return b"\x07" + encode_cstring(name) + value
|
67
modules/bson/network.py
Normal file
67
modules/bson/network.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
from struct import unpack
|
||||
|
||||
from six import BytesIO, b
|
||||
|
||||
from . import dumps, loads
|
||||
|
||||
|
||||
def _bintoint(data):
|
||||
return unpack("<i", data)[0]
|
||||
|
||||
|
||||
def sendobj(self, obj):
|
||||
"""
|
||||
Atomically send a BSON message.
|
||||
"""
|
||||
data = dumps(obj)
|
||||
self.sendall(data)
|
||||
|
||||
|
||||
def recvobj(self):
|
||||
"""
|
||||
Atomic read of a BSON message.
|
||||
|
||||
This function either returns a dict, None, or raises a socket error.
|
||||
|
||||
If the return value is None, it means the socket is closed by the other side.
|
||||
"""
|
||||
sock_buf = self.recvbytes(4)
|
||||
if sock_buf is None:
|
||||
return None
|
||||
|
||||
message_length = _bintoint(sock_buf.getvalue())
|
||||
sock_buf = self.recvbytes(message_length - 4, sock_buf)
|
||||
if sock_buf is None:
|
||||
return None
|
||||
|
||||
retval = loads(sock_buf.getvalue())
|
||||
return retval
|
||||
|
||||
|
||||
def recvbytes(self, bytes_needed, sock_buf = None):
|
||||
"""
|
||||
Atomic read of bytes_needed bytes.
|
||||
|
||||
This function either returns exactly the nmber of bytes requested in a
|
||||
StringIO buffer, None, or raises a socket error.
|
||||
|
||||
If the return value is None, it means the socket is closed by the other side.
|
||||
"""
|
||||
if sock_buf is None:
|
||||
sock_buf = BytesIO()
|
||||
bytes_count = 0
|
||||
while bytes_count < bytes_needed:
|
||||
chunk = self.recv(min(bytes_needed - bytes_count, 32768))
|
||||
part_count = len(chunk)
|
||||
|
||||
if type(chunk) == str:
|
||||
chunk = b(chunk)
|
||||
|
||||
if part_count < 1:
|
||||
return None
|
||||
|
||||
bytes_count += part_count
|
||||
sock_buf.write(chunk)
|
||||
|
||||
return sock_buf
|
295
modules/bson/objectid.py
Normal file
295
modules/bson/objectid.py
Normal file
@@ -0,0 +1,295 @@
|
||||
# Copyright 2009-2015 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.
|
||||
|
||||
"""Tools for working with MongoDB `ObjectIds
|
||||
<http://dochub.mongodb.org/core/objectids>`_.
|
||||
"""
|
||||
|
||||
import binascii
|
||||
import calendar
|
||||
import datetime
|
||||
import hashlib
|
||||
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
|
||||
|
||||
|
||||
def _machine_bytes():
|
||||
"""Get the machine portion of an ObjectId.
|
||||
"""
|
||||
machine_hash = hashlib.md5()
|
||||
if PY3:
|
||||
# gethostname() returns a unicode string in python 3.x
|
||||
# while update() requires a byte string.
|
||||
machine_hash.update(socket.gethostname().encode())
|
||||
else:
|
||||
# Calling encode() here will fail with non-ascii hostnames
|
||||
machine_hash.update(socket.gethostname())
|
||||
return machine_hash.digest()[0: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)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
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)))
|
||||
|
||||
@property
|
||||
def binary(self):
|
||||
"""12-byte binary representation of this ObjectId.
|
||||
"""
|
||||
return self.__id
|
||||
|
||||
@property
|
||||
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)
|
88
modules/bson/py3compat.py
Normal file
88
modules/bson/py3compat.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# Copyright 2009-2015 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.
|
||||
|
||||
"""Utility functions and definitions for python3 compatibility."""
|
||||
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
import codecs
|
||||
import _thread as thread
|
||||
from io import BytesIO as StringIO
|
||||
MAXSIZE = sys.maxsize
|
||||
|
||||
imap = map
|
||||
|
||||
def b(s):
|
||||
# BSON and socket operations deal in binary data. In
|
||||
# python 3 that means instances of `bytes`. In python
|
||||
# 2.6 and 2.7 you can create an alias for `bytes` using
|
||||
# the b prefix (e.g. b'foo').
|
||||
# See http://python3porting.com/problems.html#nicer-solutions
|
||||
return codecs.latin_1_encode(s)[0]
|
||||
|
||||
def bytes_from_hex(h):
|
||||
return bytes.fromhex(h)
|
||||
|
||||
def iteritems(d):
|
||||
return iter(d.items())
|
||||
|
||||
def itervalues(d):
|
||||
return iter(d.values())
|
||||
|
||||
def reraise(exctype, value, trace=None):
|
||||
raise exctype(str(value)).with_traceback(trace)
|
||||
|
||||
def _unicode(s):
|
||||
return s
|
||||
|
||||
text_type = str
|
||||
string_type = str
|
||||
integer_types = int
|
||||
else:
|
||||
import thread
|
||||
|
||||
from itertools import imap
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
MAXSIZE = sys.maxint
|
||||
|
||||
def b(s):
|
||||
# See comments above. In python 2.x b('foo') is just 'foo'.
|
||||
return s
|
||||
|
||||
def bytes_from_hex(h):
|
||||
return h.decode('hex')
|
||||
|
||||
def iteritems(d):
|
||||
return d.iteritems()
|
||||
|
||||
def itervalues(d):
|
||||
return d.itervalues()
|
||||
|
||||
# "raise x, y, z" raises SyntaxError in Python 3
|
||||
exec("""def reraise(exctype, value, trace=None):
|
||||
raise exctype, str(value), trace
|
||||
""")
|
||||
|
||||
_unicode = unicode
|
||||
|
||||
string_type = basestring
|
||||
text_type = unicode
|
||||
integer_types = (int, long)
|
52
modules/bson/tz_util.py
Normal file
52
modules/bson/tz_util.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright 2010-2015 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.
|
||||
|
||||
"""Timezone related utilities for BSON."""
|
||||
|
||||
from datetime import (timedelta,
|
||||
tzinfo)
|
||||
|
||||
ZERO = timedelta(0)
|
||||
|
||||
|
||||
class FixedOffset(tzinfo):
|
||||
"""Fixed offset timezone, in minutes east from UTC.
|
||||
|
||||
Implementation based from the Python `standard library documentation
|
||||
<http://docs.python.org/library/datetime.html#tzinfo-objects>`_.
|
||||
Defining __getinitargs__ enables pickling / copying.
|
||||
"""
|
||||
|
||||
def __init__(self, offset, name):
|
||||
if isinstance(offset, timedelta):
|
||||
self.__offset = offset
|
||||
else:
|
||||
self.__offset = timedelta(minutes=offset)
|
||||
self.__name = name
|
||||
|
||||
def __getinitargs__(self):
|
||||
return self.__offset, self.__name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.__offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.__name
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
utc = FixedOffset(0, "UTC")
|
||||
"""Fixed offset timezone representing UTC."""
|
Reference in New Issue
Block a user