Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added pydantic validation support to ObjectId #120

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 35 additions & 21 deletions bson/objectid.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,44 +41,44 @@ 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
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)
return (fnv_1a_hash >> 24) ^ (fnv_1a_hash & 0xFFFFFF)


def _machine_bytes():
"""Get the machine portion of an ObjectId.
"""
"""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.
"""
"""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)
" or a 24-character hex string" % oid
)


class ObjectId(object):
"""A MongoDB ObjectId.
"""
"""A MongoDB ObjectId."""

_inc = random.randint(0, 0xFFFFFF)
_inc_lock = threading.Lock()

_machine_bytes = _machine_bytes()

__slots__ = ('__id')
__slots__ = "__id"

_type_marker = 7

Expand Down Expand Up @@ -157,8 +157,7 @@ def from_datetime(cls, generation_time):
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"
oid = struct.pack(">i", int(timestamp)) + b"\x00\x00\x00\x00\x00\x00\x00\x00"
return cls(oid)

@classmethod
Expand All @@ -180,8 +179,7 @@ def is_valid(cls, oid):
return False

def __generate(self):
"""Generate a new value for this ObjectId.
"""
"""Generate a new value for this ObjectId."""

# 4 bytes current time
oid = struct.pack(">i", int(time.time()))
Expand Down Expand Up @@ -222,13 +220,30 @@ def __validate(self, 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)))
raise TypeError(
"id must be an instance of (bytes, %s, ObjectId), "
"not %s" % (text_type.__name__, type(oid))
)

@classmethod
def __get_validators__(cls):
"""Yields all validators for this type.
Used to create a pydantic model.
"""
yield cls.validate

@classmethod
def validate(cls, oid):
"""Validate the given value as an ObjectId.
Pydantic model validator.
"""
if not isinstance(oid, ObjectId):
raise ValueError("Not a valid ObjectId")
return oid

@property
def binary(self):
"""12-byte binary representation of this ObjectId.
"""
"""12-byte binary representation of this ObjectId."""
return self.__id

@property
Expand All @@ -250,8 +265,7 @@ def __getstate__(self):
return self.__id

def __setstate__(self, value):
"""explicit state set from pickling
"""
"""explicit state set from pickling"""
# Provide backwards compatability with OIDs
# pickled with pymongo-1.9 or older.
if isinstance(value, dict):
Expand All @@ -262,7 +276,7 @@ def __setstate__(self, value):
# 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')
self.__id = oid.encode("latin-1")
else:
self.__id = oid

Expand Down