Skip to content

Commit e41eff1

Browse files
committed
Merge branch '29-simplified-property-types' into dev
2 parents 72725a8 + 5bad0d5 commit e41eff1

File tree

15 files changed

+802
-157
lines changed

15 files changed

+802
-157
lines changed

example/ollama/llamas.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@
1515

1616

1717
from objectbox.model import *
18-
from objectbox.model.properties import *
19-
import numpy as np
2018

2119
# Have fresh data for each start
2220
objectbox.Store.remove_db_files("objectbox")
2321

2422
@Entity(id=1, uid=1)
2523
class DocumentEmbedding:
2624
id = Id(id=1, uid=1001)
27-
document = Property(str, id=2, uid=1002)
28-
embedding = Property(np.ndarray, type=PropertyType.floatVector, id=3, uid=1003, index=HnswIndex(
25+
document = String(id=2, uid=1002)
26+
embedding = Float32Vector(id=3, uid=1003, index=HnswIndex(
2927
id=3, uid=10001,
3028
dimensions=1024,
3129
distance_type=VectorDistanceType.COSINE

example/tasks/model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
@Entity(id=1, uid=1)
55
class Task:
66
id = Id(id=1, uid=1001)
7-
text = Property(str, id=2, uid=1002)
7+
text = String(id=2, uid=1002)
88

9-
date_created = Property(int, type=PropertyType.date, id=3, uid=1003)
10-
date_finished = Property(int, type=PropertyType.date, id=4, uid=1004)
9+
date_created = Date(py_type=int, id=3, uid=1003)
10+
date_finished = Date(py_type=int, id=4, uid=1004)
1111

1212

1313
def get_objectbox_model():

example/vectorsearch-cities/model.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
from objectbox.model import *
2-
from objectbox.model.properties import *
3-
import objectbox
4-
import numpy as np
5-
62

73
@Entity(id=1, uid=1)
84
class City:
95
id = Id(id=1, uid=1001)
10-
name = Property(str, id=2, uid=1002)
11-
location = Property(np.ndarray, type=PropertyType.floatVector, id=3, uid=1003, index=HnswIndex(
6+
name = String(id=2, uid=1002)
7+
location = Float32Vector(id=3, uid=1003, index=HnswIndex(
128
id=3, uid=10001,
139
dimensions=2,
1410
distance_type=VectorDistanceType.EUCLIDEAN
1511
))
1612

17-
1813
def get_objectbox_model():
1914
m = Model()
2015
m.entity(City, last_property_id=IdUid(3, 1003))

objectbox/box.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def get_all(self) -> list:
141141
finally:
142142
obx_bytes_array_free(c_bytes_array_p)
143143

144-
def remove(self, id_or_object):
144+
def remove(self, id_or_object) -> bool:
145145
if isinstance(id_or_object, self._entity.cls):
146146
id = self._entity.get_object_id(id_or_object)
147147
else:

objectbox/condition.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,20 @@ def alias(self, value: str):
113113

114114
def _apply_eq(self, qb: QueryBuilder) -> obx_qb_cond:
115115
value = self._args['value']
116-
case_sensitive = self._args['case_sensitive']
117116
if isinstance(value, str):
117+
case_sensitive = self._args['case_sensitive']
118118
return qb.equals_string(self._property_id, value, case_sensitive)
119119
elif isinstance(value, int):
120120
return qb.equals_int(self._property_id, value)
121+
elif isinstance(value, bytes):
122+
return qb.equals_bytes(self._property_id, value)
121123
else:
122124
raise Exception(f"Unsupported type for 'EQ': {type(value)}")
123125

124126
def _apply_not_eq(self, qb: QueryBuilder) -> obx_qb_cond:
125127
value = self._args['value']
126-
case_sensitive = self._args['case_sensitive']
127128
if isinstance(value, str):
129+
case_sensitive = self._args['case_sensitive']
128130
return qb.not_equals_string(self._property_id, value, case_sensitive)
129131
elif isinstance(value, int):
130132
return qb.not_equals_int(self._property_id, value)
@@ -149,57 +151,75 @@ def _apply_starts_with(self, qb: QueryBuilder) -> obx_qb_cond:
149151

150152
def _apply_ends_with(self, qb: QueryBuilder) -> obx_qb_cond:
151153
value = self._args['value']
152-
case_sensitive = self._args['case_sensitive']
153154
if isinstance(value, str):
155+
case_sensitive = self._args['case_sensitive']
154156
return qb.ends_with_string(self._property_id, value, case_sensitive)
155157
else:
156158
raise Exception(f"Unsupported type for 'ENDS_WITH': {type(value)}")
157159

158160
def _apply_gt(self, qb: QueryBuilder) -> obx_qb_cond:
159161
value = self._args['value']
160-
case_sensitive = self._args['case_sensitive']
161162
if isinstance(value, str):
163+
case_sensitive = self._args['case_sensitive']
162164
return qb.greater_than_string(self._property_id, value, case_sensitive)
163165
elif isinstance(value, int):
164166
return qb.greater_than_int(self._property_id, value)
167+
elif isinstance(value, float):
168+
return qb.greater_than_double(self._property_id, value)
169+
elif isinstance(value, bytes):
170+
return qb.greater_than_bytes(self._property_id, value)
165171
else:
166172
raise Exception(f"Unsupported type for 'GT': {type(value)}")
167173

168174
def _apply_gte(self, qb: QueryBuilder) -> obx_qb_cond:
169175
value = self._args['value']
170-
case_sensitive = self._args['case_sensitive']
171176
if isinstance(value, str):
177+
case_sensitive = self._args['case_sensitive']
172178
return qb.greater_or_equal_string(self._property_id, value, case_sensitive)
173179
elif isinstance(value, int):
174180
return qb.greater_or_equal_int(self._property_id, value)
181+
elif isinstance(value, float):
182+
return qb.greater_or_equal_double(self._property_id, value)
183+
elif isinstance(value, bytes):
184+
return qb.greater_or_equal_bytes(self._property_id, value)
175185
else:
176186
raise Exception(f"Unsupported type for 'GTE': {type(value)}")
177187

178188
def _apply_lt(self, qb: QueryBuilder) -> obx_qb_cond:
179189
value = self._args['value']
180-
case_sensitive = self._args['case_sensitive']
181190
if isinstance(value, str):
191+
case_sensitive = self._args['case_sensitive']
182192
return qb.less_than_string(self._property_id, value, case_sensitive)
183193
elif isinstance(value, int):
184194
return qb.less_than_int(self._property_id, value)
195+
elif isinstance(value, float):
196+
return qb.less_than_double(self._property_id, value)
197+
elif isinstance(value, bytes):
198+
return qb.less_than_bytes(self._property_id, value)
185199
else:
186200
raise Exception("Unsupported type for 'LT': " + str(type(value)))
187201

188202
def _apply_lte(self, qb: QueryBuilder) -> obx_qb_cond:
189203
value = self._args['value']
190-
case_sensitive = self._args['case_sensitive']
191204
if isinstance(value, str):
205+
case_sensitive = self._args['case_sensitive']
192206
return qb.less_or_equal_string(self._property_id, value, case_sensitive)
193207
elif isinstance(value, int):
194208
return qb.less_or_equal_int(self._property_id, value)
209+
elif isinstance(value, float):
210+
return qb.less_or_equal_double(self._property_id, value)
211+
elif isinstance(value, bytes):
212+
return qb.less_or_equal_bytes(self._property_id, value)
195213
else:
196214
raise Exception(f"Unsupported type for 'LTE': {type(value)}")
197215

198216
def _apply_between(self, qb: QueryBuilder) -> obx_qb_cond:
199217
a = self._args['a']
200218
b = self._args['b']
201-
if isinstance(a, int):
219+
if isinstance(a, int) and isinstance(b, int):
202220
return qb.between_2ints(self._property_id, a, b)
221+
elif isinstance(a, float) or isinstance(b, float):
222+
return qb.between_2doubles(self._property_id, a, b)
203223
else:
204224
raise Exception(f"Unsupported type for 'BETWEEN': {type(a)}")
205225

objectbox/model/__init__.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,40 @@
2020
__all__ = [
2121
'Model',
2222
'Entity',
23-
'Id',
2423
'IdUid',
2524
'Property',
26-
'PropertyType'
25+
'PropertyType',
26+
'Id',
27+
'IdUid',
28+
'Bool',
29+
'String',
30+
'Int8',
31+
'Int16',
32+
'Int32',
33+
'Int64',
34+
'Float32',
35+
'Float64',
36+
'Date',
37+
'DateNano',
38+
'Flex',
39+
'BoolVector',
40+
'Int8Vector',
41+
'Int16Vector',
42+
'CharVector',
43+
'Int32Vector',
44+
'Int64Vector',
45+
'Float32Vector',
46+
'Float64Vector',
47+
'Index',
48+
'HnswIndex',
49+
'VectorDistanceType',
50+
'BoolList',
51+
'Int8List',
52+
'Int16List',
53+
'CharList',
54+
'Int32List',
55+
'Int64List',
56+
'Float32List',
57+
'Float64List',
58+
'Bytes',
2759
]

objectbox/model/entity.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
import flatbuffers.flexbuffers
1818
from typing import Generic
1919
import numpy as np
20-
from math import floor
21-
from datetime import datetime
20+
from datetime import datetime, timezone
2221
from objectbox.c import *
2322
from objectbox.model.properties import Property
23+
from objectbox.utils import date_value_to_int
2424
import threading
2525

26+
2627
# _Entity class holds model information as well as conversions between python objects and FlatBuffers (ObjectBox data)
2728
class _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
@@ -186,13 +187,9 @@ def marshal(self, object, id: int) -> bytearray:
186187
else:
187188
val = id if prop == self.id_property else self.get_value(object, prop)
188189
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
190+
val = date_value_to_int(val, 1000) # convert to milliseconds
192191
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
192+
val = date_value_to_int(val, 1000000000) # convert to nanoseconds
196193
builder.Prepend(prop._fb_type, val)
197194

198195
builder.Slot(prop._fb_slot)
@@ -211,42 +208,49 @@ def unmarshal(self, data: bytes):
211208
for prop in self.properties:
212209
o = table.Offset(prop._fb_v_offset)
213210
val = None
211+
ob_type = prop._ob_type
214212
if not o:
215213
val = prop._py_type() # use default (empty) value if not present in the object
216-
elif prop._ob_type == OBXPropertyType_String:
214+
elif ob_type == OBXPropertyType_String:
217215
val = table.String(o + table.Pos).decode('utf-8')
218-
elif prop._ob_type == OBXPropertyType_BoolVector:
216+
elif ob_type == OBXPropertyType_BoolVector:
219217
val = table.GetVectorAsNumpy(flatbuffers.number_types.BoolFlags, o)
220-
elif prop._ob_type == OBXPropertyType_ByteVector:
218+
elif ob_type == OBXPropertyType_ByteVector:
221219
# access the FB byte vector information
222220
start = table.Vector(o)
223221
size = table.VectorLen(o)
224222
# slice the vector as a requested type
225-
val = prop._py_type(table.Bytes[start:start+size])
226-
elif prop._ob_type == OBXPropertyType_ShortVector:
223+
val = prop._py_type(table.Bytes[start:start + size])
224+
elif ob_type == OBXPropertyType_ShortVector:
227225
val = table.GetVectorAsNumpy(flatbuffers.number_types.Int16Flags, o)
228-
elif prop._ob_type == OBXPropertyType_CharVector:
226+
elif ob_type == OBXPropertyType_CharVector:
229227
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:
228+
elif ob_type == OBXPropertyType_Date:
229+
val = table.Get(prop._fb_type, o + table.Pos) # int
230+
if prop._py_type == datetime:
231+
val = datetime.fromtimestamp(val / 1000.0, tz=timezone.utc)
232+
elif prop._py_type == float:
233+
val = val / 1000.0
234+
elif ob_type == OBXPropertyType_DateNano and prop._py_type == datetime:
235+
val = table.Get(prop._fb_type, o + table.Pos) # int
236+
if prop._py_type == datetime:
237+
val = datetime.fromtimestamp(val / 1000000000.0, tz=timezone.utc)
238+
elif prop._py_type == float:
239+
val = val / 1000000000.0
240+
elif ob_type == OBXPropertyType_IntVector:
237241
val = table.GetVectorAsNumpy(flatbuffers.number_types.Int32Flags, o)
238-
elif prop._ob_type == OBXPropertyType_LongVector:
242+
elif ob_type == OBXPropertyType_LongVector:
239243
val = table.GetVectorAsNumpy(flatbuffers.number_types.Int64Flags, o)
240-
elif prop._ob_type == OBXPropertyType_FloatVector:
244+
elif ob_type == OBXPropertyType_FloatVector:
241245
val = table.GetVectorAsNumpy(flatbuffers.number_types.Float32Flags, o)
242-
elif prop._ob_type == OBXPropertyType_DoubleVector:
246+
elif ob_type == OBXPropertyType_DoubleVector:
243247
val = table.GetVectorAsNumpy(flatbuffers.number_types.Float64Flags, o)
244-
elif prop._ob_type == OBXPropertyType_Flex:
248+
elif ob_type == OBXPropertyType_Flex:
245249
# access the FB byte vector information
246250
start = table.Vector(o)
247251
size = table.VectorLen(o)
248252
# slice the vector as bytes
249-
buf = table.Bytes[start:start+size]
253+
buf = table.Bytes[start:start + size]
250254
val = flatbuffers.flexbuffers.Loads(buf)
251255
else:
252256
val = table.Get(prop._fb_type, o + table.Pos)
@@ -258,6 +262,8 @@ def unmarshal(self, data: bytes):
258262

259263
def Entity(id: int = 0, uid: int = 0) -> Callable[[Type], _Entity]:
260264
""" Entity decorator that wraps _Entity to allow @Entity(id=, uid=); i.e. no class arguments. """
265+
261266
def wrapper(class_):
262267
return _Entity(class_, id, uid)
268+
263269
return wrapper

0 commit comments

Comments
 (0)