Skip to content

Commit 03ef52a

Browse files
committed
Merge branch '64-add-box-methods' into dev
2 parents fcf5fe3 + eaa905c commit 03ef52a

3 files changed

Lines changed: 156 additions & 15 deletions

File tree

‎objectbox/box.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ def __init__(self, store: Store, entity: _Entity):
3131
self._entity = entity
3232
self._c_box = obx_box(store._c_store, entity._id)
3333

34+
def contains(self, object_id: int) -> bool:
35+
"""Checks whether the box contains the given object ID."""
36+
contained = ctypes.c_bool()
37+
obx_box_contains(self._c_box, object_id, ctypes.byref(contained))
38+
return bool(contained.value)
39+
3440
def is_empty(self) -> bool:
3541
"""Returns true if box is empty (i.e. no objects of entity type are available)."""
3642
is_empty = ctypes.c_bool()
@@ -112,6 +118,16 @@ def _put_many(self, objects) -> None:
112118
for k in new.keys():
113119
self._entity._set_object_id(objects[k], ids[k])
114120

121+
def update(self, object_):
122+
"""Updates the existing object having the same ID as the given object.
123+
124+
Raises if the object has no ID (i.e. it was never put) or the ID doesn't match any stored object."""
125+
object_id = self._entity._get_object_id(object_)
126+
if not object_id:
127+
raise ValueError("Cannot update an object without an ID; use put() for new objects")
128+
data = self._entity._marshal(object_, object_id)
129+
obx_box_update(self._c_box, object_id, bytes(data), len(data))
130+
115131
def get(self, id: int):
116132
"""Get object by given Id or None if not found."""
117133
with self._store.read_tx():
@@ -126,6 +142,36 @@ def get(self, id: int):
126142
data = c_voidp_as_bytes(c_data, c_size.value)
127143
return self._entity._unmarshal(data)
128144

145+
def get_many(self, id_array: List[int]) -> list:
146+
"""Gets objects matching the given ID list.
147+
148+
:return: objects matching the ID list, preserving the order.
149+
If an invalid ID is supplied, the corresponding object is None.
150+
"""
151+
152+
if not id_array: # Empty
153+
return []
154+
155+
c_ids = (obx_id * len(id_array))(*id_array)
156+
c_id_array = OBX_id_array(ctypes.cast(c_ids, ctypes.POINTER(obx_id)), len(id_array))
157+
158+
with self._store.read_tx():
159+
c_bytes_array_p = obx_box_get_many(self._c_box, ctypes.byref(c_id_array))
160+
161+
result = []
162+
try:
163+
c_bytes_array = c_bytes_array_p.contents
164+
for i in range(c_bytes_array.count):
165+
c_bytes = c_bytes_array.data[i]
166+
if c_bytes.data is None or c_bytes.size == 0: # User supplied an invalid ID
167+
result.append(None)
168+
else:
169+
data = c_voidp_as_bytes(c_bytes.data, c_bytes.size)
170+
result.append(self._entity._unmarshal(data))
171+
finally:
172+
obx_bytes_array_free(c_bytes_array_p)
173+
return result
174+
129175
def get_all(self) -> list:
130176
"""Get all objects."""
131177
with self._store.read_tx():

‎objectbox/c.py‎

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def shlib_name(library: str) -> str:
8787
OBXLogLevel = ctypes.c_int
8888

8989
from enum import IntEnum
90+
91+
9092
class LogLevel(IntEnum):
9193
Verbose = 10
9294
Debug = 20
@@ -581,7 +583,7 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
581583
obx_opt_async_object_bytes_max_size_to_cache = c_fn('obx_opt_async_object_bytes_max_size_to_cache', None,
582584
[OBX_store_options_p, ctypes.c_uint64])
583585

584-
#typedef void obx_log_callback(OBXLogLevel log_level, const char* message, size_t message_size, void* user_data);
586+
# typedef void obx_log_callback(OBXLogLevel log_level, const char* message, size_t message_size, void* user_data);
585587
obx_log_callback_fn = ctypes.CFUNCTYPE(None, OBXLogLevel, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_voidp)
586588

587589
# OBX_C_API void obx_opt_log_callback(OBX_store_options* opt, obx_log_callback* callback, void* user_data);
@@ -633,10 +635,16 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
633635
# OBX_box* (OBX_store* store, obx_schema_id entity_id);
634636
obx_box = c_fn('obx_box', OBX_box_p, [OBX_store_p, obx_schema_id])
635637

638+
# OBX_C_API obx_err obx_box_contains(OBX_box* box, obx_id id, bool* out_contains);
639+
obx_box_contains = c_fn_rc('obx_box_contains', [OBX_box_p, obx_id, ctypes.POINTER(ctypes.c_bool)])
640+
636641
# obx_err (OBX_box* box, obx_id id, const void** data, size_t* size);
637642
obx_box_get = c_fn_nocheck('obx_box_get', obx_err, [
638643
OBX_box_p, obx_id, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_size_t)])
639644

645+
# OBX_C_API OBX_bytes_array* obx_box_get_many(OBX_box* box, const OBX_id_array* ids);
646+
obx_box_get_many = c_fn('obx_box_get_many', OBX_bytes_array_p, [OBX_box_p, OBX_id_array_p])
647+
640648
# OBX_bytes_array* (OBX_box* box);
641649
obx_box_get_all = c_fn('obx_box_get_all', OBX_bytes_array_p, [OBX_box_p])
642650

@@ -650,24 +658,29 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
650658
# obx_err (OBX_box* box, obx_id id, const void* data, size_t size);
651659
obx_box_put = c_fn_rc('obx_box_put', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
652660

661+
# OBX_C_API obx_err obx_box_insert(OBX_box* box, obx_id id, const void* data, size_t size);
662+
obx_box_insert = c_fn_rc('obx_box_insert', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
663+
664+
# OBX_C_API obx_err obx_box_update(OBX_box* box, obx_id id, const void* data, size_t size);
665+
obx_box_update = c_fn_rc('obx_box_update', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
666+
653667
# obx_err (OBX_box* box, const OBX_bytes_array* objects, const obx_id* ids, OBXPutMode mode);
654-
obx_box_put_many = c_fn_rc('obx_box_put_many', [
655-
OBX_box_p, OBX_bytes_array_p, ctypes.POINTER(obx_id), OBXPutMode])
668+
obx_box_put_many = c_fn_rc('obx_box_put_many', [OBX_box_p, OBX_bytes_array_p, ctypes.POINTER(obx_id), OBXPutMode])
656669

657670
# obx_err (OBX_box* box, obx_id id);
658671
obx_box_remove = c_fn_nocheck('obx_box_remove', obx_err, [OBX_box_p, obx_id])
659672

660-
# obx_err (OBX_box* box, uint64_t* out_count);
661-
obx_box_remove_all = c_fn_rc('obx_box_remove_all', [
662-
OBX_box_p, ctypes.POINTER(ctypes.c_uint64)])
673+
# OBX_C_API obx_err obx_box_remove_many(OBX_box* box, const OBX_id_array* ids, uint64_t* out_count);
674+
obx_box_remove_many = c_fn_rc('obx_box_remove_many', [OBX_box_p, OBX_id_array_p, ctypes.POINTER(ctypes.c_uint64)])
675+
676+
# OBX_C_API obx_err obx_box_remove_all(OBX_box* box, uint64_t* out_count);
677+
obx_box_remove_all = c_fn_rc('obx_box_remove_all', [OBX_box_p, ctypes.POINTER(ctypes.c_uint64)])
663678

664-
# obx_err (OBX_box* box, bool* out_is_empty);
665-
obx_box_is_empty = c_fn_rc('obx_box_is_empty', [
666-
OBX_box_p, ctypes.POINTER(ctypes.c_bool)])
679+
# OBX_C_API obx_err obx_box_is_empty(OBX_box* box, bool* out_is_empty);
680+
obx_box_is_empty = c_fn_rc('obx_box_is_empty', [OBX_box_p, ctypes.POINTER(ctypes.c_bool)])
667681

668-
# obx_err obx_box_count(OBX_box* box, uint64_t limit, uint64_t* out_count);
669-
obx_box_count = c_fn_rc('obx_box_count', [
670-
OBX_box_p, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64)])
682+
# OBX_C_API obx_err obx_box_count(OBX_box* box, uint64_t limit, uint64_t* out_count);
683+
obx_box_count = c_fn_rc('obx_box_count', [OBX_box_p, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64)])
671684

672685
# OBX_query_builder* obx_query_builder(OBX_store* store, obx_schema_id entity_id);
673686
obx_query_builder = c_fn('obx_query_builder', OBX_query_builder_p, [OBX_store_p, obx_schema_id])

‎tests/test_box.py‎

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import pytest
2-
import objectbox
3-
from tests.model import TestEntity, TestEntityDatetime, TestEntityFlex
4-
from tests.common import *
52
import numpy as np
63
from datetime import datetime, timezone
74
import time
85
from math import floor
96

7+
import objectbox
8+
from objectbox.exceptions import *
9+
from tests.common import *
10+
from tests.model import TestEntity, TestEntityDatetime, TestEntityFlex
11+
1012

1113
def test_box_basics(test_store):
1214
box = test_store.box(TestEntity)
@@ -280,3 +282,83 @@ def test_flex_values(test_store):
280282
obj_id = box.put(TestEntityFlex(flex=dict_))
281283
read_obj = box.get(obj_id)
282284
assert read_obj.flex == dict_
285+
286+
287+
def test_contains(test_store):
288+
box = test_store.box(TestEntity)
289+
290+
box.put([TestEntity(str=f"Object {i}") for i in range(1, 4)])
291+
assert box.count() == 3
292+
293+
assert box.contains(1)
294+
assert box.contains(2)
295+
assert box.contains(3)
296+
assert not box.contains(4)
297+
assert not box.contains(3248)
298+
299+
with pytest.raises(IllegalArgumentError, match="Illegal ID value: 0"):
300+
box.contains(0)
301+
302+
303+
def test_update(test_store):
304+
box = test_store.box(TestEntity)
305+
306+
box.put([TestEntity(str=f"Object {i}") for i in range(1, 4)])
307+
assert box.count() == 3
308+
309+
with test_store.write_tx():
310+
for obj in box.get_all():
311+
obj.str += " (updated)"
312+
box.update(obj)
313+
314+
assert box.get(1).str == "Object 1 (updated)"
315+
assert box.get(2).str == "Object 2 (updated)"
316+
assert box.get(3).str == "Object 3 (updated)"
317+
assert box.count() == 3
318+
319+
with pytest.raises(ValueError, match="without an ID"):
320+
box.update(TestEntity(str="New object"))
321+
322+
obj = box.get(1)
323+
box.remove(1)
324+
with pytest.raises(IdNotFoundError, match="Update failed, given ID doesn't exist: 1"):
325+
box.update(obj)
326+
assert not box.contains(1) # Failed update must not re-create the object
327+
328+
329+
def test_get_many(test_store):
330+
box = test_store.box(TestEntity)
331+
332+
box.put([TestEntity(str=f"Object {i}") for i in range(1, 6)])
333+
assert box.count() == 5
334+
335+
objects = box.get_many([1, 3, 4])
336+
assert len(objects) == 3
337+
assert objects[0].str == "Object 1"
338+
assert objects[1].str == "Object 3"
339+
assert objects[2].str == "Object 4"
340+
341+
objects = box.get_many([5, 2])
342+
assert len(objects) == 2
343+
assert objects[0].str == "Object 5"
344+
assert objects[1].str == "Object 2"
345+
346+
objects = box.get_many([5, 5, 2])
347+
assert len(objects) == 3
348+
assert objects[0].str == "Object 5"
349+
assert objects[1].str == "Object 5"
350+
assert objects[2].str == "Object 2"
351+
352+
objects = box.get_many([2])
353+
assert len(objects) == 1
354+
assert objects[0].str == "Object 2"
355+
356+
objects = box.get_many([])
357+
assert len(objects) == 0
358+
359+
objects = box.get_many([1, 789, 2, 10])
360+
assert len(objects) == 4
361+
assert objects[0].str == "Object 1"
362+
assert objects[1] is None
363+
assert objects[2].str == "Object 2"
364+
assert objects[3] is None

0 commit comments

Comments
 (0)