Skip to content

Commit 1bbe959

Browse files
author
Ivan Dlugos
committed
finish model building and start ObjectBox
1 parent b468606 commit 1bbe959

10 files changed

Lines changed: 191 additions & 57 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44
# Environment
55
.venv/
66

7-
.pytest_cache/
7+
.pytest_cache/
8+
9+
# Test temporary files
10+
testdata/

objectbox/builder.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
from objectbox.c import C
1+
from objectbox.c import *
22
from objectbox.model import Model
3+
from objectbox.objectbox import ObjectBox
34

45

56
class Builder:
67
def __init__(self):
7-
self.__model = Model()
8+
self._model = Model()
9+
self._directory: str = ''
10+
11+
def directory(self, path: str) -> 'Builder':
12+
self._directory = path
13+
return self
814

915
def model(self, model: Model) -> 'Builder':
10-
self.__model = model
16+
self._model = model
17+
self._model._finish()
1118
return self
1219

1320
def build(self) -> 'ObjectBox':
14-
return self
21+
c_options = OBX_store_options()
22+
if len(self._directory) > 0:
23+
c_options.directory = c_str(self._directory)
24+
25+
c_store = obx_store_open(self._model._c_model, c_options.p())
26+
return ObjectBox(c_store)

objectbox/c.py

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import ctypes.util
22

3+
# This file contains C-API bindings based on the objectbox.h, linking to the 'objectbox' shared library
4+
35
# initialize the C library
46
C = ctypes.CDLL(ctypes.util.find_library("objectbox"))
57

@@ -40,6 +42,9 @@ class OBX_store_options(ctypes.Structure):
4042
('maxReaders', ctypes.c_uint)
4143
]
4244

45+
def p(self) -> 'ctypes.POINTER(OBX_store_options)':
46+
return ctypes.pointer(self)
47+
4348

4449
OBX_store_options_p = ctypes.POINTER(OBX_store_options)
4550

@@ -108,33 +113,77 @@ class OBX_query(ctypes.Structure):
108113

109114
OBX_query_p = ctypes.POINTER(OBX_query)
110115

116+
# manually configure error methods, we can't use `fn()` defined bellow yet due to circular dependencies
117+
C.obx_last_error_message.restype = ctypes.c_char_p
118+
C.obx_last_error_code.restype = obx_err
119+
111120

112121
class CError(Exception):
122+
codes = {
123+
0: "SUCCESS",
124+
404: "NOT_FOUND",
125+
10001: "ILLEGAL_STATE",
126+
10002: "ILLEGAL_ARGUMENT",
127+
10003: "ALLOCATION",
128+
10097: "NO_ERROR_INFO",
129+
10098: "GENERAL",
130+
10099: "UNKNOWN",
131+
10101: "DB_FULL",
132+
10102: "MAX_READERS_EXCEEDED",
133+
10103: "STORE_MUST_SHUTDOWN",
134+
10199: "STORAGE_GENERAL",
135+
10201: "UNIQUE_VIOLATED",
136+
10202: "NON_UNIQUE_RESULT",
137+
10203: "PROPERTY_TYPE_MISMATCH",
138+
10299: "CONSTRAINT_VIOLATED",
139+
10301: "STD_ILLEGAL_ARGUMENT",
140+
10302: "STD_OUT_OF_RANGE",
141+
10303: "STD_LENGTH",
142+
10304: "STD_BAD_ALLOC",
143+
10305: "STD_RANGE",
144+
10306: "STD_OVERFLOW",
145+
10399: "STD_OTHER",
146+
10501: "SCHEMA",
147+
10502: "FILE_CORRUPT"
148+
}
149+
113150
def __init__(self, code):
114151
self.code = code
115-
self.message = ctypes.c_char_p(C.obx_last_error_message()).value.decode("utf8")
116-
super(CError, self).__init__(self.message)
152+
self.message = py_str(C.obx_last_error_message())
153+
super(CError, self).__init__("%d (%s) - %s" % (code, self.codes[code], self.message))
117154

118155

119156
# check obx_err and raise an error
120-
def errcheck(code: obx_err, func, args):
157+
def check_obx_err(code: obx_err, func, args):
121158
if code != 0:
122159
raise CError(code)
123160

124161

162+
# check if the returned pointer is null and raise an error
163+
def check_ptr_result(result, func, args):
164+
if not result:
165+
raise CError(C.obx_last_error_code())
166+
return result
167+
168+
125169
# creates a global function "name" with the given restype & argtypes, calling C function with the same name
126170
def fn(name: str, restype: type, argtypes):
127171
func = C.__getattr__(name)
128172

129-
if restype == obx_err:
130-
func.errcheck = errcheck
131-
else:
173+
if restype is obx_err:
174+
func.errcheck = check_obx_err
175+
elif restype is not None:
176+
func.errcheck = check_ptr_result
132177
func.restype = restype
133178

134179
func.argtypes = argtypes
135180
return func
136181

137182

183+
def py_str(ptr: ctypes.c_char_p) -> str:
184+
return ctypes.c_char_p(ptr).value.decode("utf-8")
185+
186+
138187
def c_str(string: str) -> ctypes.c_char_p:
139188
return string.encode('utf-8')
140189

@@ -149,6 +198,28 @@ def c_str(string: str) -> ctypes.c_char_p:
149198
obx_model_property = fn('obx_model_property', obx_err,
150199
[OBX_model_p, ctypes.c_char_p, OBXPropertyType, obx_schema_id, obx_uid])
151200

201+
# obx_err (OBX_model* model, OBXPropertyFlags flags);
202+
obx_model_property_flags = fn('obx_model_property_flags', obx_err, [OBX_model_p, OBXPropertyFlags])
203+
204+
# obx_err (OBX_model*, obx_schema_id entity_id, obx_uid entity_uid);
205+
obx_model_last_entity_id = fn('obx_model_last_entity_id', obx_err, [OBX_model_p, obx_schema_id, obx_uid])
206+
207+
# obx_err (OBX_model* model, obx_schema_id index_id, obx_uid index_uid);
208+
obx_model_last_index_id = fn('obx_model_last_index_id', obx_err, [OBX_model_p, obx_schema_id, obx_uid])
209+
210+
# obx_err (OBX_model* model, obx_schema_id relation_id, obx_uid relation_uid);
211+
obx_model_last_relation_id = fn('obx_model_last_relation_id', obx_err, [OBX_model_p, obx_schema_id, obx_uid])
212+
213+
# obx_err (OBX_model* model, obx_schema_id property_id, obx_uid property_uid);
214+
obx_model_entity_last_property_id = fn('obx_model_entity_last_property_id', obx_err,
215+
[OBX_model_p, obx_schema_id, obx_uid])
216+
217+
# OBX_store* (OBX_model* model, const OBX_store_options* options);
218+
obx_store_open = fn('obx_store_open', OBX_store_p, [OBX_model_p, OBX_store_options_p])
219+
220+
# obx_err (OBX_store* store);
221+
obx_store_close = fn('obx_store_close', obx_err, [OBX_store_p])
222+
152223
OBXPropertyType_Bool = 1
153224
OBXPropertyType_Byte = 2
154225
OBXPropertyType_Short = 3
@@ -184,7 +255,6 @@ def c_str(string: str) -> ctypes.c_char_p:
184255
OBXDebugFlags_LOG_QUERY_PARAMETERS = 8,
185256
OBXDebugFlags_LOG_ASYNC_QUEUE = 16,
186257

187-
188258
# Standard put ("insert or update")
189259
OBXPutMode_PUT = 1,
190260

@@ -198,7 +268,6 @@ def c_str(string: str) -> ctypes.c_char_p:
198268
# This is primarily used internally. Wrong usage leads to inconsistent data (e.g. index data not updated)!
199269
OBXPutMode_PUT_ID_GUARANTEED_TO_BE_NEW = 4
200270

201-
202271
# Reverts the order from ascending (default) to descending.
203272
OBXOrderFlags_DESCENDING = 1,
204273

objectbox/model/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
__all__ = [
66
'Model',
77
'Entity',
8-
'Id'
8+
'Id',
9+
'IdUid'
910
]

objectbox/model/entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def fillProperties(self):
3232
variables = {k: v for k, v in variables.items() if issubclass(type(v), Property)}
3333

3434
for k, v in variables.items():
35-
v.__name = k
35+
v._name = k
3636
self.properties.append(v)
3737

3838

objectbox/model/model.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,40 @@ def __init__(self, id: int, uid: int):
1111
self.id = id
1212
self.uid = uid
1313

14+
def __bool__(self):
15+
return self.id != 0 or self.uid != 0
1416

15-
class Model:
16-
last_entity: IdUid
17-
last_index: IdUid
18-
last_relation: IdUid
19-
20-
retired_entity_uids: List[int]
21-
retired_property_uids: List[int]
22-
retired_index_uids: List[int]
23-
retired_relation_uids: List[int]
2417

18+
class Model:
2519
def __init__(self):
26-
self.__entities: List[type] = list()
27-
self.__model = obx_model_create()
20+
self._entities: List[type] = list()
21+
self._c_model = obx_model_create()
22+
self.last_entity_id = IdUid(0, 0)
23+
self.last_index_id = IdUid(0, 0)
24+
self.last_relation_id = IdUid(0, 0)
2825

29-
def entity(self, entity: _Entity):
26+
def entity(self, entity: _Entity, last_property_id: IdUid):
3027
if not isinstance(entity, _Entity):
3128
raise ValueError("Given type is not an Entity. Are you passing an instance instead of a type or did you "
3229
"forget the '@Entity' annotation?")
3330

34-
obx_model_entity(self.__model, c_str(entity.name), entity.id, entity.uid)
31+
obx_model_entity(self._c_model, c_str(entity.name), entity.id, entity.uid)
3532

3633
for v in entity.properties:
37-
obx_model_property(self.__model, c_str(v._Property__name), v._Property__ob_type, v._Property__id, v._Property__uid)
34+
obx_model_property(self._c_model, c_str(v._name), v._ob_type, v._id, v._uid)
35+
if v._flags != 0:
36+
obx_model_property_flags(self._c_model, v._flags)
37+
38+
39+
obx_model_entity_last_property_id(self._c_model, last_property_id.id, last_property_id.uid)
40+
41+
# called by Builder
42+
def _finish(self):
43+
if self.last_relation_id:
44+
obx_model_last_relation_id(self._c_model, self.last_relation_id.id, self.last_relation_id.uid)
45+
46+
if self.last_index_id:
47+
obx_model_last_index_id(self._c_model, self.last_index_id.id, self.last_index_id.uid)
48+
49+
if self.last_entity_id:
50+
obx_model_last_entity_id(self._c_model, self.last_entity_id.id, self.last_entity_id.uid)

objectbox/model/properties.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,43 @@
55
# base property
66
class Property:
77
def __init__(self, py_type: type, id: int, uid: int):
8-
self.__is_id = isinstance(self, Id)
9-
self.__py_type = py_type
10-
self.__id = id
11-
self.__uid = uid
12-
self.__name: str = "" # set when in Entity.fillProperties()
8+
self._is_id = isinstance(self, Id)
9+
self._id = id
10+
self._uid = uid
11+
self._name: str = "" # set when in Entity.fillProperties()
1312

14-
self.__ob_type: OBXPropertyType
15-
self.__fb_type: object # flatbuffers.number_types
16-
self._set_basic_type()
13+
self._py_type: type = py_type
14+
self._ob_type: OBXPropertyType
15+
self._fb_type: object # flatbuffers.number_types
16+
self.__set_basic_type()
1717

18-
def _set_basic_type(self) -> OBXPropertyType:
19-
ts = self.__py_type
18+
self._flags: OBXPropertyFlags = 0
19+
self.__set_flags()
20+
21+
def __set_basic_type(self) -> OBXPropertyType:
22+
ts = self._py_type
2023
if ts == str:
21-
self.__ob_type = OBXPropertyType_String
22-
self.__fb_type = flatbuffers.number_types.UOffsetTFlags
24+
self._ob_type = OBXPropertyType_String
25+
self._fb_type = flatbuffers.number_types.UOffsetTFlags
2326
elif ts == int:
24-
self.__ob_type = OBXPropertyType_Long
25-
self.__fb_type = flatbuffers.number_types.Int64Flags
27+
self._ob_type = OBXPropertyType_Long
28+
self._fb_type = flatbuffers.number_types.Int64Flags
2629
# TODO support
2730
# elif ts == bytes or ts == bytearray:
2831
# self.__ob_type = OBXPropertyType_ByteVector
2932
# self.__fb_type = flatbuffers.number_types.UOffsetTFlags
3033
elif ts == float:
31-
self.__ob_type = OBXPropertyType_Double
32-
self.__fb_type = flatbuffers.number_types.Float64Flags
34+
self._ob_type = OBXPropertyType_Double
35+
self._fb_type = flatbuffers.number_types.Float64Flags
3336
elif ts == bool:
34-
self.__ob_type = OBXPropertyType_Bool
35-
self.__fb_type = flatbuffers.number_types.BoolFlags
37+
self._ob_type = OBXPropertyType_Bool
38+
self._fb_type = flatbuffers.number_types.BoolFlags
3639
else:
3740
raise TypeError("unknown property type %s" % ts)
3841

42+
def __set_flags(self):
43+
if self._is_id:
44+
self._flags = OBXPropertyFlags_ID
3945

4046
# ID property (primary key)
4147
class Id(Property):

objectbox/objectbox.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from objectbox.c import *
2+
3+
4+
class ObjectBox:
5+
def __init__(self, c_store: OBX_store_p):
6+
self._c_store = c_store
7+
8+
def __del__(self):
9+
obx_store_close(self._c_store)

tests/test_basics.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
1+
import os
2+
import shutil
3+
4+
import objectbox
15
import objectbox.version
6+
from tests.model import TestEntity
7+
import pytest
8+
9+
db_name = 'testdata'
10+
11+
12+
def remove_test_db():
13+
if os.path.exists(db_name):
14+
shutil.rmtree(db_name)
15+
16+
17+
# cleanup before and after each testcase
18+
@pytest.fixture(autouse=True)
19+
def run_around_tests():
20+
remove_test_db()
21+
yield # run the test function
22+
remove_test_db()
223

324

425
def test_version():
526
info = objectbox.version.version_info()
627
assert len(info) > 10
28+
29+
30+
def test_open():
31+
from objectbox.model import IdUid
32+
33+
model = objectbox.Model()
34+
model.entity(TestEntity, last_property_id=IdUid(1, 1001))
35+
model.last_entity_id = IdUid(1, 1)
36+
37+
objectbox.Builder().model(model).directory(db_name).build()

tests/test_model.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)