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/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
@@ -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
+ ]