objectbox-api 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- objectbox/__init__.py +145 -0
- objectbox/box.py +294 -0
- objectbox/builder.py +42 -0
- objectbox/c.py +1572 -0
- objectbox/condition.py +300 -0
- objectbox/exceptions.py +238 -0
- objectbox/experimental.py +78 -0
- objectbox/logger.py +18 -0
- objectbox/model/__init__.py +63 -0
- objectbox/model/codegen.py +399 -0
- objectbox/model/entity.py +490 -0
- objectbox/model/fast_builder.py +324 -0
- objectbox/model/idsync.py +401 -0
- objectbox/model/iduid.py +32 -0
- objectbox/model/model.py +158 -0
- objectbox/model/properties.py +865 -0
- objectbox/objectbox.py +24 -0
- objectbox/query.py +468 -0
- objectbox/query_builder.py +450 -0
- objectbox/store.py +400 -0
- objectbox/store_options.py +136 -0
- objectbox/sync.py +1101 -0
- objectbox/transaction.py +60 -0
- objectbox/utils.py +60 -0
- objectbox/version.py +39 -0
- objectbox_api-5.0.0.dist-info/METADATA +63 -0
- objectbox_api-5.0.0.dist-info/RECORD +30 -0
- objectbox_api-5.0.0.dist-info/WHEEL +5 -0
- objectbox_api-5.0.0.dist-info/licenses/LICENSE +202 -0
- objectbox_api-5.0.0.dist-info/top_level.txt +1 -0
objectbox/condition.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import *
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from objectbox.c import obx_qb_cond
|
|
9
|
+
from objectbox.query_builder import QueryBuilder
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QueryCondition:
|
|
14
|
+
def and_(self, other: QueryCondition) -> QueryCondition:
|
|
15
|
+
"""*and* logic condition or using ``&`` operator"""
|
|
16
|
+
return LogicQueryCondition(self, other, LogicQueryConditionOp.AND)
|
|
17
|
+
__and__ = and_
|
|
18
|
+
|
|
19
|
+
def or_(self, other: QueryCondition) -> QueryCondition:
|
|
20
|
+
"""*or* logic condition or using ``|`` operator"""
|
|
21
|
+
return LogicQueryCondition(self, other, LogicQueryConditionOp.OR)
|
|
22
|
+
__or__ = or_
|
|
23
|
+
|
|
24
|
+
def _apply_is_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
25
|
+
return qb.is_null(self._property_id)
|
|
26
|
+
|
|
27
|
+
def _apply_not_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
28
|
+
return qb.not_null(self._property_id)
|
|
29
|
+
|
|
30
|
+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
31
|
+
""" Applies the QueryCondition to the supplied QueryBuilder.
|
|
32
|
+
|
|
33
|
+
:return:
|
|
34
|
+
The C handle for the applied condition.
|
|
35
|
+
"""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LogicQueryConditionOp(Enum):
|
|
40
|
+
AND = 1
|
|
41
|
+
OR = 2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LogicQueryCondition(QueryCondition):
|
|
45
|
+
""" A QueryCondition describing a logical operation between two inner QueryCondition's (e.g. AND/OR). """
|
|
46
|
+
|
|
47
|
+
def __init__(self, cond1: QueryCondition, cond2: QueryCondition, op: LogicQueryConditionOp):
|
|
48
|
+
self._cond1 = cond1
|
|
49
|
+
self._cond2 = cond2
|
|
50
|
+
self._op = op
|
|
51
|
+
|
|
52
|
+
def _apply_conditions(self, qb: QueryBuilder) -> List[obx_qb_cond]:
|
|
53
|
+
return [self._cond1.apply(qb), self._cond2.apply(qb)]
|
|
54
|
+
|
|
55
|
+
def _apply_and(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
56
|
+
return qb.all(self._apply_conditions(qb))
|
|
57
|
+
|
|
58
|
+
def _apply_or(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
59
|
+
return qb.any(self._apply_conditions(qb))
|
|
60
|
+
|
|
61
|
+
def _apply_is_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
62
|
+
return qb.is_null(self._property_id)
|
|
63
|
+
|
|
64
|
+
def _apply_not_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
65
|
+
return qb.not_null(self._property_id)
|
|
66
|
+
|
|
67
|
+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
68
|
+
if self._op == LogicQueryConditionOp.AND:
|
|
69
|
+
return self._apply_and(qb)
|
|
70
|
+
elif self._op == LogicQueryConditionOp.OR:
|
|
71
|
+
return self._apply_or(qb)
|
|
72
|
+
else:
|
|
73
|
+
raise Exception(f"Unknown LogicQueryCondition op: {self._op.name}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PropertyQueryConditionOp(Enum):
|
|
77
|
+
EQ = 1
|
|
78
|
+
NOT_EQ = 2
|
|
79
|
+
CONTAINS = 3
|
|
80
|
+
STARTS_WITH = 4
|
|
81
|
+
ENDS_WITH = 5
|
|
82
|
+
GT = 6
|
|
83
|
+
GTE = 7
|
|
84
|
+
LT = 8
|
|
85
|
+
LTE = 9
|
|
86
|
+
BETWEEN = 10
|
|
87
|
+
NEAREST_NEIGHBOR = 11
|
|
88
|
+
CONTAINS_KEY_VALUE = 12
|
|
89
|
+
IS_NULL = 13
|
|
90
|
+
NOT_NULL = 14
|
|
91
|
+
ONE_OF = 15
|
|
92
|
+
NOT_ONE_OF = 16
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class PropertyQueryCondition(QueryCondition):
|
|
96
|
+
"""
|
|
97
|
+
Query condition
|
|
98
|
+
|
|
99
|
+
Query conditions describe operations to be applied on a property (e.g. name == "John", age == 24)
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
_OP_MAP: Dict[PropertyQueryConditionOp, str] = {
|
|
103
|
+
PropertyQueryConditionOp.EQ: "_apply_eq",
|
|
104
|
+
PropertyQueryConditionOp.NOT_EQ: "_apply_not_eq",
|
|
105
|
+
PropertyQueryConditionOp.CONTAINS: "_apply_contains",
|
|
106
|
+
PropertyQueryConditionOp.STARTS_WITH: "_apply_starts_with",
|
|
107
|
+
PropertyQueryConditionOp.ENDS_WITH: "_apply_ends_with",
|
|
108
|
+
PropertyQueryConditionOp.GT: "_apply_gt",
|
|
109
|
+
PropertyQueryConditionOp.GTE: "_apply_gte",
|
|
110
|
+
PropertyQueryConditionOp.LT: "_apply_lt",
|
|
111
|
+
PropertyQueryConditionOp.LTE: "_apply_lte",
|
|
112
|
+
PropertyQueryConditionOp.BETWEEN: "_apply_between",
|
|
113
|
+
PropertyQueryConditionOp.NEAREST_NEIGHBOR: "_apply_nearest_neighbor",
|
|
114
|
+
PropertyQueryConditionOp.CONTAINS_KEY_VALUE: "_contains_key_value",
|
|
115
|
+
PropertyQueryConditionOp.IS_NULL: "_apply_is_null",
|
|
116
|
+
PropertyQueryConditionOp.NOT_NULL: "_apply_not_null",
|
|
117
|
+
PropertyQueryConditionOp.ONE_OF: "_apply_one_of",
|
|
118
|
+
PropertyQueryConditionOp.NOT_ONE_OF: "_apply_not_one_of",
|
|
119
|
+
# ... new property query conditions here ... :)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def __init__(self, property_id: int, op: PropertyQueryConditionOp, args: Dict[str, Any]):
|
|
123
|
+
if op not in self._OP_MAP:
|
|
124
|
+
raise Exception(f"Invalid PropertyQueryConditionOp: {op}")
|
|
125
|
+
op_func_name = self._OP_MAP[op]
|
|
126
|
+
if not hasattr(self, op_func_name):
|
|
127
|
+
raise Exception(f"Missing PropertyQueryCondition op function: {op_func_name} (op: {op})")
|
|
128
|
+
op_func = getattr(self, op_func_name)
|
|
129
|
+
|
|
130
|
+
self._property_id = property_id
|
|
131
|
+
self._op = op
|
|
132
|
+
self._op_func = op_func
|
|
133
|
+
self._args = args
|
|
134
|
+
self._alias = None
|
|
135
|
+
|
|
136
|
+
def alias(self, value: str):
|
|
137
|
+
""" Sets an alias for this condition to change its value(s) on the built query via
|
|
138
|
+
``Query.set_parameter_alias()`` or ``Query.set_parameters_alias()``. """
|
|
139
|
+
self._alias = value
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def _apply_eq(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
143
|
+
value = self._args['value']
|
|
144
|
+
if isinstance(value, str):
|
|
145
|
+
case_sensitive = self._args['case_sensitive']
|
|
146
|
+
return qb.equals_string(self._property_id, value, case_sensitive)
|
|
147
|
+
elif isinstance(value, int):
|
|
148
|
+
return qb.equals_int(self._property_id, value)
|
|
149
|
+
elif isinstance(value, bytes):
|
|
150
|
+
return qb.equals_bytes(self._property_id, value)
|
|
151
|
+
else:
|
|
152
|
+
raise TypeError(f"Unsupported type for 'EQ': {type(value)}")
|
|
153
|
+
|
|
154
|
+
def _apply_not_eq(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
155
|
+
value = self._args['value']
|
|
156
|
+
if isinstance(value, str):
|
|
157
|
+
case_sensitive = self._args['case_sensitive']
|
|
158
|
+
return qb.not_equals_string(self._property_id, value, case_sensitive)
|
|
159
|
+
elif isinstance(value, int):
|
|
160
|
+
return qb.not_equals_int(self._property_id, value)
|
|
161
|
+
else:
|
|
162
|
+
raise TypeError(f"Unsupported type for 'NOT_EQ': {type(value)}")
|
|
163
|
+
|
|
164
|
+
def _apply_contains(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
165
|
+
value = self._args['value']
|
|
166
|
+
case_sensitive = self._args['case_sensitive']
|
|
167
|
+
if isinstance(value, str):
|
|
168
|
+
return qb.contains_string(self._property_id, value, case_sensitive)
|
|
169
|
+
else:
|
|
170
|
+
raise TypeError(f"Unsupported type for 'CONTAINS': {type(value)}")
|
|
171
|
+
|
|
172
|
+
def _apply_starts_with(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
173
|
+
value = self._args['value']
|
|
174
|
+
case_sensitive = self._args['case_sensitive']
|
|
175
|
+
if isinstance(value, str):
|
|
176
|
+
return qb.starts_with_string(self._property_id, value, case_sensitive)
|
|
177
|
+
else:
|
|
178
|
+
raise TypeError(f"Unsupported type for 'STARTS_WITH': {type(value)}")
|
|
179
|
+
|
|
180
|
+
def _apply_ends_with(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
181
|
+
value = self._args['value']
|
|
182
|
+
if isinstance(value, str):
|
|
183
|
+
case_sensitive = self._args['case_sensitive']
|
|
184
|
+
return qb.ends_with_string(self._property_id, value, case_sensitive)
|
|
185
|
+
else:
|
|
186
|
+
raise TypeError(f"Unsupported type for 'ENDS_WITH': {type(value)}")
|
|
187
|
+
|
|
188
|
+
def _apply_gt(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
189
|
+
value = self._args['value']
|
|
190
|
+
if isinstance(value, str):
|
|
191
|
+
case_sensitive = self._args['case_sensitive']
|
|
192
|
+
return qb.greater_than_string(self._property_id, value, case_sensitive)
|
|
193
|
+
elif isinstance(value, int):
|
|
194
|
+
return qb.greater_than_int(self._property_id, value)
|
|
195
|
+
elif isinstance(value, float):
|
|
196
|
+
return qb.greater_than_double(self._property_id, value)
|
|
197
|
+
elif isinstance(value, bytes):
|
|
198
|
+
return qb.greater_than_bytes(self._property_id, value)
|
|
199
|
+
else:
|
|
200
|
+
raise TypeError(f"Unsupported type for 'GT': {type(value)}")
|
|
201
|
+
|
|
202
|
+
def _apply_gte(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
203
|
+
value = self._args['value']
|
|
204
|
+
if isinstance(value, str):
|
|
205
|
+
case_sensitive = self._args['case_sensitive']
|
|
206
|
+
return qb.greater_or_equal_string(self._property_id, value, case_sensitive)
|
|
207
|
+
elif isinstance(value, int):
|
|
208
|
+
return qb.greater_or_equal_int(self._property_id, value)
|
|
209
|
+
elif isinstance(value, float):
|
|
210
|
+
return qb.greater_or_equal_double(self._property_id, value)
|
|
211
|
+
elif isinstance(value, bytes):
|
|
212
|
+
return qb.greater_or_equal_bytes(self._property_id, value)
|
|
213
|
+
else:
|
|
214
|
+
raise TypeError(f"Unsupported type for 'GTE': {type(value)}")
|
|
215
|
+
|
|
216
|
+
def _apply_lt(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
217
|
+
value = self._args['value']
|
|
218
|
+
if isinstance(value, str):
|
|
219
|
+
case_sensitive = self._args['case_sensitive']
|
|
220
|
+
return qb.less_than_string(self._property_id, value, case_sensitive)
|
|
221
|
+
elif isinstance(value, int):
|
|
222
|
+
return qb.less_than_int(self._property_id, value)
|
|
223
|
+
elif isinstance(value, float):
|
|
224
|
+
return qb.less_than_double(self._property_id, value)
|
|
225
|
+
elif isinstance(value, bytes):
|
|
226
|
+
return qb.less_than_bytes(self._property_id, value)
|
|
227
|
+
else:
|
|
228
|
+
raise TypeError("Unsupported type for 'LT': " + str(type(value)))
|
|
229
|
+
|
|
230
|
+
def _apply_lte(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
231
|
+
value = self._args['value']
|
|
232
|
+
if isinstance(value, str):
|
|
233
|
+
case_sensitive = self._args['case_sensitive']
|
|
234
|
+
return qb.less_or_equal_string(self._property_id, value, case_sensitive)
|
|
235
|
+
elif isinstance(value, int):
|
|
236
|
+
return qb.less_or_equal_int(self._property_id, value)
|
|
237
|
+
elif isinstance(value, float):
|
|
238
|
+
return qb.less_or_equal_double(self._property_id, value)
|
|
239
|
+
elif isinstance(value, bytes):
|
|
240
|
+
return qb.less_or_equal_bytes(self._property_id, value)
|
|
241
|
+
else:
|
|
242
|
+
raise TypeError(f"Unsupported type for 'LTE': {type(value)}")
|
|
243
|
+
|
|
244
|
+
def _apply_between(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
245
|
+
a = self._args['a']
|
|
246
|
+
b = self._args['b']
|
|
247
|
+
if isinstance(a, int) and isinstance(b, int):
|
|
248
|
+
return qb.between_2ints(self._property_id, a, b)
|
|
249
|
+
elif isinstance(a, float) or isinstance(b, float):
|
|
250
|
+
return qb.between_2doubles(self._property_id, a, b)
|
|
251
|
+
else:
|
|
252
|
+
raise TypeError(f"Unsupported type for 'BETWEEN': {type(a)}")
|
|
253
|
+
|
|
254
|
+
def _apply_nearest_neighbor(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
255
|
+
query_vector = self._args['query_vector']
|
|
256
|
+
max_result_count = self._args['max_result_count']
|
|
257
|
+
|
|
258
|
+
if len(query_vector) == 0:
|
|
259
|
+
from objectbox.exceptions import IllegalArgumentError
|
|
260
|
+
raise IllegalArgumentError("query_vector can't be empty")
|
|
261
|
+
|
|
262
|
+
is_float_vector = False
|
|
263
|
+
np = sys.modules.get("numpy") # not imported: query_vector is no numpy array
|
|
264
|
+
is_float_vector |= np is not None and isinstance(query_vector, np.ndarray) and query_vector.dtype == np.float32
|
|
265
|
+
is_float_vector |= isinstance(query_vector, list) and type(query_vector[0]) == float
|
|
266
|
+
if is_float_vector:
|
|
267
|
+
return qb.nearest_neighbors_f32(self._property_id, query_vector, max_result_count)
|
|
268
|
+
else:
|
|
269
|
+
raise TypeError(f"Unsupported type for 'NEAREST_NEIGHBOR': {type(query_vector)}")
|
|
270
|
+
|
|
271
|
+
def _contains_key_value(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
272
|
+
key = self._args['key']
|
|
273
|
+
value = self._args['value']
|
|
274
|
+
case_sensitive = self._args['case_sensitive']
|
|
275
|
+
return qb.contains_key_value(self._property_id, key, value, case_sensitive)
|
|
276
|
+
|
|
277
|
+
def _apply_is_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
278
|
+
return qb.is_null(self._property_id)
|
|
279
|
+
|
|
280
|
+
def _apply_not_null(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
281
|
+
return qb.not_null(self._property_id)
|
|
282
|
+
|
|
283
|
+
def _apply_one_of(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
284
|
+
values = self._args['values']
|
|
285
|
+
# Cannot tell strings from ints by the values (the list may be empty), so use the property kind
|
|
286
|
+
if self._args.get('string', False):
|
|
287
|
+
case_sensitive = self._args['case_sensitive']
|
|
288
|
+
return qb.in_strings(self._property_id, values, case_sensitive)
|
|
289
|
+
else:
|
|
290
|
+
return qb.in_ints(self._property_id, values)
|
|
291
|
+
|
|
292
|
+
def _apply_not_one_of(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
293
|
+
values = self._args['values']
|
|
294
|
+
return qb.not_in_ints(self._property_id, values)
|
|
295
|
+
|
|
296
|
+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
|
|
297
|
+
c_cond = self._op_func(qb)
|
|
298
|
+
if self._alias is not None:
|
|
299
|
+
qb._alias_for_property(self._alias, self._property_id)
|
|
300
|
+
return c_cond
|
objectbox/exceptions.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
from typing import Dict, Optional, Type
|
|
3
|
+
|
|
4
|
+
from objectbox.c import py_str, C, DbErrorCode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DbError(Exception):
|
|
8
|
+
"""The base class for all exceptions thrown by ObjectBox.
|
|
9
|
+
Every exception has a code attribute for identification.
|
|
10
|
+
|
|
11
|
+
Some also derive from the matching built-in exception, e.g. IllegalArgumentError is a ValueError."""
|
|
12
|
+
|
|
13
|
+
code = DbErrorCode.OBX_NO_SUCCESS # Re-defined by derived classes
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: Optional[str] = None):
|
|
16
|
+
"""Creates the exception with the given message, or by default the last error message of the C library."""
|
|
17
|
+
self.message = message if message is not None else py_str(C.obx_last_error_message())
|
|
18
|
+
code = self.code
|
|
19
|
+
label = f"{code.value} ({code.name})" if isinstance(code, DbErrorCode) else str(code)
|
|
20
|
+
super().__init__(f"{label} - {self.message}")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def from_code(code: int):
|
|
24
|
+
"""Creates a DbError of the given error code."""
|
|
25
|
+
return create_db_error(code)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def last():
|
|
29
|
+
"""Creates a DbError of the last error that was generated in core."""
|
|
30
|
+
return DbError.from_code(C.obx_last_error())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NotFoundError(DbError):
|
|
34
|
+
"""Raised when an object is not found."""
|
|
35
|
+
code = DbErrorCode.OBX_NOT_FOUND
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NoSuccessError(DbError):
|
|
39
|
+
code = DbErrorCode.OBX_NO_SUCCESS
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TimeoutError(DbError, builtins.TimeoutError):
|
|
43
|
+
"""A timeout (also the built-in TimeoutError, which this class shadows in this module)."""
|
|
44
|
+
code = DbErrorCode.OBX_TIMEOUT
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IllegalStateError(DbError, RuntimeError):
|
|
48
|
+
"""An operation is not allowed in the current state, e.g. on a closed object (also a RuntimeError)."""
|
|
49
|
+
code = DbErrorCode.OBX_ERROR_ILLEGAL_STATE
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class IllegalArgumentError(DbError, ValueError):
|
|
53
|
+
"""An argument has an invalid value (also a ValueError)."""
|
|
54
|
+
code = DbErrorCode.OBX_ERROR_ILLEGAL_ARGUMENT
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AllocationError(DbError):
|
|
58
|
+
code = DbErrorCode.OBX_ERROR_ALLOCATION
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class NumericOverflowError(DbError):
|
|
62
|
+
code = DbErrorCode.OBX_ERROR_NUMERIC_OVERFLOW
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class FeatureNotAvailableError(DbError, RuntimeError):
|
|
66
|
+
"""The feature is not available in the loaded ObjectBox library (also a RuntimeError)."""
|
|
67
|
+
code = DbErrorCode.OBX_ERROR_FEATURE_NOT_AVAILABLE
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
FeatureNotAvailable = FeatureNotAvailableError
|
|
71
|
+
"""Deprecated name of :class:`FeatureNotAvailableError` (the same class)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ShuttingDownError(DbError):
|
|
75
|
+
code = DbErrorCode.OBX_ERROR_SHUTTING_DOWN
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class IoError(DbError):
|
|
79
|
+
code = DbErrorCode.OBX_ERROR_IO
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BackupFileInvalidError(DbError):
|
|
83
|
+
code = DbErrorCode.OBX_ERROR_BACKUP_FILE_INVALID
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class NoErrorInfoError(DbError):
|
|
87
|
+
code = DbErrorCode.OBX_ERROR_NO_ERROR_INFO
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class GeneralError(DbError):
|
|
91
|
+
code = DbErrorCode.OBX_ERROR_GENERAL
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class UnknownError(DbError):
|
|
95
|
+
code = DbErrorCode.OBX_ERROR_UNKNOWN
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DbFullError(DbError):
|
|
99
|
+
code = DbErrorCode.OBX_ERROR_DB_FULL
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MaxReadersExceededError(DbError):
|
|
103
|
+
code = DbErrorCode.OBX_ERROR_MAX_READERS_EXCEEDED
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class StoreMustShutdownError(DbError):
|
|
107
|
+
code = DbErrorCode.OBX_ERROR_STORE_MUST_SHUTDOWN
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class MaxDataSizeExceededError(DbError):
|
|
111
|
+
code = DbErrorCode.OBX_ERROR_MAX_DATA_SIZE_EXCEEDED
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class DbGeneralError(DbError):
|
|
115
|
+
code = DbErrorCode.OBX_ERROR_DB_GENERAL
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class GeneralStorageError(DbError):
|
|
119
|
+
code = DbErrorCode.OBX_ERROR_STORAGE_GENERAL
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ConstraintViolatedError(DbError):
|
|
123
|
+
"""A constraint was violated; see the subclass UniqueViolatedError."""
|
|
124
|
+
code = DbErrorCode.OBX_ERROR_CONSTRAINT_VIOLATED
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class UniqueViolatedError(ConstraintViolatedError):
|
|
128
|
+
"""A unique constraint was violated (also a ConstraintViolatedError, like in Java)."""
|
|
129
|
+
code = DbErrorCode.OBX_ERROR_UNIQUE_VIOLATED
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class NonUniqueResultError(DbError):
|
|
133
|
+
code = DbErrorCode.OBX_ERROR_NON_UNIQUE_RESULT
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class PropertyTypeMismatchError(DbError):
|
|
137
|
+
code = DbErrorCode.OBX_ERROR_PROPERTY_TYPE_MISMATCH
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class IdAlreadyExistsError(DbError):
|
|
141
|
+
code = DbErrorCode.OBX_ERROR_ID_ALREADY_EXISTS
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class IdNotFoundError(DbError):
|
|
145
|
+
code = DbErrorCode.OBX_ERROR_ID_NOT_FOUND
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TimeSeriesError(DbError):
|
|
149
|
+
code = DbErrorCode.OBX_ERROR_TIME_SERIES
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class StdIllegalArgumentError(DbError):
|
|
153
|
+
code = DbErrorCode.OBX_ERROR_STD_ILLEGAL_ARGUMENT
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class StdOutOfRangeError(DbError):
|
|
157
|
+
code = DbErrorCode.OBX_ERROR_STD_OUT_OF_RANGE
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class StdLengthError(DbError):
|
|
161
|
+
code = DbErrorCode.OBX_ERROR_STD_LENGTH
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class StdBadAllocError(DbError):
|
|
165
|
+
code = DbErrorCode.OBX_ERROR_STD_BAD_ALLOC
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class StdRangeError(DbError):
|
|
169
|
+
code = DbErrorCode.OBX_ERROR_STD_RANGE
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class StdOverflowError(DbError):
|
|
173
|
+
code = DbErrorCode.OBX_ERROR_STD_OVERFLOW
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class StdOtherError(DbError):
|
|
177
|
+
code = DbErrorCode.OBX_ERROR_STD_OTHER
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class SchemaError(DbError):
|
|
181
|
+
code = DbErrorCode.OBX_ERROR_SCHEMA
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class FileCorruptError(DbError):
|
|
185
|
+
code = DbErrorCode.OBX_ERROR_FILE_CORRUPT
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class FilePagesCorruptError(FileCorruptError):
|
|
189
|
+
"""Corrupt pages of the database file (also a FileCorruptError, like in Java and Dart)."""
|
|
190
|
+
code = DbErrorCode.OBX_ERROR_FILE_PAGES_CORRUPT
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class SchemaObjectNotFoundError(DbError):
|
|
194
|
+
code = DbErrorCode.OBX_ERROR_SCHEMA_OBJECT_NOT_FOUND
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TreeModelInvalidError(DbError):
|
|
198
|
+
code = DbErrorCode.OBX_ERROR_TREE_MODEL_INVALID
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TreeValueTypeMismatchError(DbError):
|
|
202
|
+
code = DbErrorCode.OBX_ERROR_TREE_VALUE_TYPE_MISMATCH
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class TreePathNonUniqueError(DbError):
|
|
206
|
+
code = DbErrorCode.OBX_ERROR_TREE_PATH_NON_UNIQUE
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TreePathIllegalError(DbError):
|
|
210
|
+
code = DbErrorCode.OBX_ERROR_TREE_PATH_ILLEGAL
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TreeOtherError(DbError):
|
|
214
|
+
code = DbErrorCode.OBX_ERROR_TREE_OTHER
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
obx_db_error_map: Dict[int, Type] = {}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _init_db_errror_map(base=DbError):
|
|
221
|
+
for subclass in base.__subclasses__(): # Recursively: some errors derive from others, e.g. UniqueViolatedError
|
|
222
|
+
obx_db_error_map[subclass.code] = subclass
|
|
223
|
+
_init_db_errror_map(subclass)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
_init_db_errror_map()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def create_db_error(code: int) -> DbError:
|
|
230
|
+
if code == DbErrorCode.OBX_SUCCESS:
|
|
231
|
+
raise ValueError("Can't create a DbError for code: OBX_SUCCESS")
|
|
232
|
+
error_class = obx_db_error_map.get(code)
|
|
233
|
+
if error_class is None: # E.g. a code of a newer C library: a DbError with that code
|
|
234
|
+
error = DbError.__new__(DbError)
|
|
235
|
+
error.code = DbErrorCode(code) if code in DbErrorCode._value2member_map_ else code
|
|
236
|
+
DbError.__init__(error)
|
|
237
|
+
return error
|
|
238
|
+
return error_class()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright 2026 ObjectBox Ltd. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Experimental features: off by default; they may change or be removed in any release."""
|
|
16
|
+
|
|
17
|
+
# Marshalling objects (put): None (the generic flatbuffers.Builder), FAST_BUILDER or CODEGEN; at most one is active
|
|
18
|
+
FAST_BUILDER = "fast builder"
|
|
19
|
+
CODEGEN = "codegen"
|
|
20
|
+
_flatbuffers_mode = None
|
|
21
|
+
_flatbuffers_verify = False
|
|
22
|
+
_codegen_cache_dir = None # directory for compiled codegen (see enable_flatbuffers_codegen) or None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _set_flatbuffers_mode(mode: str, enabled: bool, verify: bool):
|
|
26
|
+
global _flatbuffers_mode, _flatbuffers_verify
|
|
27
|
+
if enabled:
|
|
28
|
+
_flatbuffers_mode = mode
|
|
29
|
+
_flatbuffers_verify = verify
|
|
30
|
+
elif _flatbuffers_mode == mode:
|
|
31
|
+
_flatbuffers_mode = None
|
|
32
|
+
_flatbuffers_verify = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def enable_fast_flatbuffers_builder(enabled: bool = True, verify: bool = False):
|
|
36
|
+
"""Experimental: marshals objects (put) with a FlatBuffers builder that is precomputed per entity instead of the
|
|
37
|
+
generic flatbuffers.Builder; this is much faster, especially for many objects. It produces the same bytes.
|
|
38
|
+
Applies to all entities in this process, from the next put on.
|
|
39
|
+
Only one experimental builder is active: enabling this one replaces enable_flatbuffers_codegen();
|
|
40
|
+
disabling it (enabled=False) has no effect if the other one is active.
|
|
41
|
+
|
|
42
|
+
:param verify: additionally marshals each object with the generic builder and raises an AssertionError if the
|
|
43
|
+
bytes differ (slower than the generic builder alone; for testing).
|
|
44
|
+
"""
|
|
45
|
+
_set_flatbuffers_mode(FAST_BUILDER, enabled, verify)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_fast_flatbuffers_builder_enabled() -> bool:
|
|
49
|
+
return _flatbuffers_mode == FAST_BUILDER
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def enable_flatbuffers_codegen(enabled: bool = True, verify: bool = False, cache_dir: str = None):
|
|
53
|
+
"""Experimental: like enable_fast_flatbuffers_builder(), but additionally generates Python code per entity
|
|
54
|
+
(compiled once per process: at the entity's first put, and for reading at its first read) that marshals its
|
|
55
|
+
objects; this is faster than the fast builder.
|
|
56
|
+
It produces the same bytes as the generic flatbuffers.Builder.
|
|
57
|
+
Reading objects (get, queries) also uses generated code, which returns the same objects as the default
|
|
58
|
+
(flatbuffers.Table), but is much faster, especially for entities with many properties.
|
|
59
|
+
Only one experimental builder is active: enabling this one replaces enable_fast_flatbuffers_builder();
|
|
60
|
+
disabling it (enabled=False) has no effect if the other one is active.
|
|
61
|
+
|
|
62
|
+
:param verify: see enable_fast_flatbuffers_builder(); for reading, each object is additionally read with
|
|
63
|
+
flatbuffers.Table and an AssertionError is raised if any property value differs.
|
|
64
|
+
:param cache_dir: optional directory to cache the compiled code in, so other processes skip compiling it
|
|
65
|
+
(~1 ms per function for an entity with 38 properties), e.g. for many short-lived processes. The generated
|
|
66
|
+
sources are written there as .py files; Python caches their bytecode as for any module (in __pycache__,
|
|
67
|
+
or under sys.pycache_prefix if set). Off by default.
|
|
68
|
+
Applies to code compiled after this call; each call sets it (None: no cache).
|
|
69
|
+
Security: only use a directory that is writable by trusted users only, as code from it is executed.
|
|
70
|
+
"""
|
|
71
|
+
global _codegen_cache_dir
|
|
72
|
+
_set_flatbuffers_mode(CODEGEN, enabled, verify)
|
|
73
|
+
if enabled:
|
|
74
|
+
_codegen_cache_dir = cache_dir
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_flatbuffers_codegen_enabled() -> bool:
|
|
78
|
+
return _flatbuffers_mode == CODEGEN
|
objectbox/logger.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger("objectbox")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_stdout_logger():
|
|
8
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
9
|
+
handler.setLevel(logging.DEBUG)
|
|
10
|
+
# Output format example:
|
|
11
|
+
# 2024-04-04 10:16:46,272 [objectbox-py] [DEBUG] Creating property "id" (ID=1, UID=1001)
|
|
12
|
+
formatter = logging.Formatter('%(asctime)s [objectbox-py] [%(levelname)-5s] %(message)s')
|
|
13
|
+
handler.setFormatter(formatter)
|
|
14
|
+
logger.addHandler(handler)
|
|
15
|
+
return logger
|
|
16
|
+
|
|
17
|
+
# Not need to hook stdout as pytest will do the job. Use --log-cli-level= to set log level
|
|
18
|
+
# setup_stdout_logger()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright 2019-2024 ObjectBox Ltd. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from objectbox.model.entity import *
|
|
17
|
+
from objectbox.model.model import *
|
|
18
|
+
from objectbox.model.properties import *
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'Model',
|
|
22
|
+
'Entity',
|
|
23
|
+
'IdUid',
|
|
24
|
+
'Property',
|
|
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
|
+
'Relation',
|
|
40
|
+
'BoolVector',
|
|
41
|
+
'Int8Vector',
|
|
42
|
+
'Int16Vector',
|
|
43
|
+
'CharVector',
|
|
44
|
+
'Int32Vector',
|
|
45
|
+
'Int64Vector',
|
|
46
|
+
'Float32Vector',
|
|
47
|
+
'Float64Vector',
|
|
48
|
+
'Index',
|
|
49
|
+
'IndexType',
|
|
50
|
+
'Unique',
|
|
51
|
+
'ConflictStrategy',
|
|
52
|
+
'HnswIndex',
|
|
53
|
+
'VectorDistanceType',
|
|
54
|
+
'BoolList',
|
|
55
|
+
'Int8List',
|
|
56
|
+
'Int16List',
|
|
57
|
+
'CharList',
|
|
58
|
+
'Int32List',
|
|
59
|
+
'Int64List',
|
|
60
|
+
'Float32List',
|
|
61
|
+
'Float64List',
|
|
62
|
+
'Bytes',
|
|
63
|
+
]
|