1818from typing import Generic
1919import numpy as np
2020from math import floor
21- from datetime import datetime
21+ from datetime import datetime , timezone
2222from objectbox .c import *
2323from objectbox .model .properties import Property
2424import threading
2525
26+
2627# _Entity class holds model information as well as conversions between python objects and FlatBuffers (ObjectBox data)
2728class _Entity (object ):
2829 def __init__ (self , cls , id : int , uid : int ):
@@ -47,7 +48,7 @@ def __init__(self, cls, id: int, uid: int):
4748 self .id_property = None
4849 self .fill_properties ()
4950 self ._tl = threading .local ()
50-
51+
5152 def __call__ (self , ** properties ):
5253 """ The constructor of the user Entity class. """
5354 object_ = self .cls ()
@@ -122,9 +123,9 @@ def get_value(self, object, prop: Property):
122123 if (val == np .array (prop )).all ():
123124 return np .array ([])
124125 elif val == prop :
125- if prop ._py_type == datetime :
126- return datetime . fromtimestamp ( 0 )
127- if prop ._ob_type == OBXPropertyType_Flex :
126+ if prop ._ob_type == OBXPropertyType_Date or prop . _ob_type == OBXPropertyType_DateNano :
127+ return 0.0 # For marshalling, prefer float over datetime
128+ elif prop ._ob_type == OBXPropertyType_Flex :
128129 return None
129130 else :
130131 return prop ._py_type () # default (empty) value for the given type
@@ -136,6 +137,19 @@ def get_object_id(self, object) -> int:
136137 def set_object_id (self , object , id : int ):
137138 setattr (object , self .id_property ._name , id )
138139
140+ @staticmethod
141+ def date_value_to_int (value , multiplier : int ) -> int :
142+ if isinstance (value , datetime ):
143+ return round (value .timestamp () * multiplier ) # timestamp returns seconds
144+ elif isinstance (value , float ):
145+ return round (value * multiplier ) # floats typically represent seconds
146+ elif isinstance (value , int ): # Interpret ints as-is (without the multiplier); e.g. milliseconds or nanoseconds
147+ return value
148+ else :
149+ raise TypeError (
150+ f"Unsupported Python datetime type: { type (value )} . Please use datetime, float (seconds based) or "
151+ f"int (milliseconds for Date, nanoseconds for DateNano)." )
152+
139153 def marshal (self , object , id : int ) -> bytearray :
140154 if not hasattr (self ._tl , "builder" ):
141155 self ._tl .builder = flatbuffers .Builder (256 )
@@ -186,13 +200,9 @@ def marshal(self, object, id: int) -> bytearray:
186200 else :
187201 val = id if prop == self .id_property else self .get_value (object , prop )
188202 if prop ._ob_type == OBXPropertyType_Date :
189- if prop ._py_type == datetime :
190- val = val .timestamp () * 1000 # timestamp returns seconds, convert to milliseconds
191- val = floor (val ) # use floor to allow for float types
203+ val = self .date_value_to_int (val , 1000 ) # convert to milliseconds
192204 elif prop ._ob_type == OBXPropertyType_DateNano :
193- if prop ._py_type == datetime :
194- val = val .timestamp () * 1000000000 # convert to nanoseconds
195- val = floor (val ) # use floor to allow for float types
205+ val = self .date_value_to_int (val , 1000000000 ) # convert to nanoseconds
196206 builder .Prepend (prop ._fb_type , val )
197207
198208 builder .Slot (prop ._fb_slot )
@@ -211,42 +221,49 @@ def unmarshal(self, data: bytes):
211221 for prop in self .properties :
212222 o = table .Offset (prop ._fb_v_offset )
213223 val = None
224+ ob_type = prop ._ob_type
214225 if not o :
215226 val = prop ._py_type () # use default (empty) value if not present in the object
216- elif prop . _ob_type == OBXPropertyType_String :
227+ elif ob_type == OBXPropertyType_String :
217228 val = table .String (o + table .Pos ).decode ('utf-8' )
218- elif prop . _ob_type == OBXPropertyType_BoolVector :
229+ elif ob_type == OBXPropertyType_BoolVector :
219230 val = table .GetVectorAsNumpy (flatbuffers .number_types .BoolFlags , o )
220- elif prop . _ob_type == OBXPropertyType_ByteVector :
231+ elif ob_type == OBXPropertyType_ByteVector :
221232 # access the FB byte vector information
222233 start = table .Vector (o )
223234 size = table .VectorLen (o )
224235 # slice the vector as a requested type
225- val = prop ._py_type (table .Bytes [start :start + size ])
226- elif prop . _ob_type == OBXPropertyType_ShortVector :
236+ val = prop ._py_type (table .Bytes [start :start + size ])
237+ elif ob_type == OBXPropertyType_ShortVector :
227238 val = table .GetVectorAsNumpy (flatbuffers .number_types .Int16Flags , o )
228- elif prop . _ob_type == OBXPropertyType_CharVector :
239+ elif ob_type == OBXPropertyType_CharVector :
229240 val = table .GetVectorAsNumpy (flatbuffers .number_types .Int16Flags , o )
230- elif prop ._ob_type == OBXPropertyType_Date and prop ._py_type == datetime :
231- table_val = table .Get (prop ._fb_type , o + table .Pos )
232- val = datetime .fromtimestamp (table_val / 1000 ) if table_val != 0 else datetime .fromtimestamp (0 ) # default timestamp
233- elif prop ._ob_type == OBXPropertyType_DateNano and prop ._py_type == datetime :
234- table_val = table .Get (prop ._fb_type , o + table .Pos )
235- val = datetime .fromtimestamp (table_val / 1000000000 ) if table_val != 0 else datetime .fromtimestamp (0 ) # default timestamp
236- elif prop ._ob_type == OBXPropertyType_IntVector :
241+ elif ob_type == OBXPropertyType_Date :
242+ val = table .Get (prop ._fb_type , o + table .Pos ) # int
243+ if prop ._py_type == datetime :
244+ val = datetime .fromtimestamp (val / 1000.0 , tz = timezone .utc )
245+ elif prop ._py_type == float :
246+ val = val / 1000.0
247+ elif ob_type == OBXPropertyType_DateNano and prop ._py_type == datetime :
248+ val = table .Get (prop ._fb_type , o + table .Pos ) # int
249+ if prop ._py_type == datetime :
250+ val = datetime .fromtimestamp (val / 1000000000.0 , tz = timezone .utc )
251+ elif prop ._py_type == float :
252+ val = val / 1000000000.0
253+ elif ob_type == OBXPropertyType_IntVector :
237254 val = table .GetVectorAsNumpy (flatbuffers .number_types .Int32Flags , o )
238- elif prop . _ob_type == OBXPropertyType_LongVector :
255+ elif ob_type == OBXPropertyType_LongVector :
239256 val = table .GetVectorAsNumpy (flatbuffers .number_types .Int64Flags , o )
240- elif prop . _ob_type == OBXPropertyType_FloatVector :
257+ elif ob_type == OBXPropertyType_FloatVector :
241258 val = table .GetVectorAsNumpy (flatbuffers .number_types .Float32Flags , o )
242- elif prop . _ob_type == OBXPropertyType_DoubleVector :
259+ elif ob_type == OBXPropertyType_DoubleVector :
243260 val = table .GetVectorAsNumpy (flatbuffers .number_types .Float64Flags , o )
244- elif prop . _ob_type == OBXPropertyType_Flex :
261+ elif ob_type == OBXPropertyType_Flex :
245262 # access the FB byte vector information
246263 start = table .Vector (o )
247264 size = table .VectorLen (o )
248265 # slice the vector as bytes
249- buf = table .Bytes [start :start + size ]
266+ buf = table .Bytes [start :start + size ]
250267 val = flatbuffers .flexbuffers .Loads (buf )
251268 else :
252269 val = table .Get (prop ._fb_type , o + table .Pos )
@@ -258,6 +275,8 @@ def unmarshal(self, data: bytes):
258275
259276def Entity (id : int = 0 , uid : int = 0 ) -> Callable [[Type ], _Entity ]:
260277 """ Entity decorator that wraps _Entity to allow @Entity(id=, uid=); i.e. no class arguments. """
278+
261279 def wrapper (class_ ):
262280 return _Entity (class_ , id , uid )
281+
263282 return wrapper
0 commit comments