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
|
@@ -0,0 +1,865 @@
|
|
|
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
|
+
from enum import IntEnum
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
import sys
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
from objectbox.c import *
|
|
21
|
+
from objectbox.condition import PropertyQueryCondition, PropertyQueryConditionOp
|
|
22
|
+
from objectbox.model.iduid import IdUid
|
|
23
|
+
|
|
24
|
+
class PropertyType(IntEnum):
|
|
25
|
+
bool = OBXPropertyType_Bool
|
|
26
|
+
byte = OBXPropertyType_Byte
|
|
27
|
+
short = OBXPropertyType_Short
|
|
28
|
+
char = OBXPropertyType_Char
|
|
29
|
+
int = OBXPropertyType_Int
|
|
30
|
+
long = OBXPropertyType_Long
|
|
31
|
+
float = OBXPropertyType_Float
|
|
32
|
+
double = OBXPropertyType_Double
|
|
33
|
+
string = OBXPropertyType_String
|
|
34
|
+
date = OBXPropertyType_Date
|
|
35
|
+
dateNano = OBXPropertyType_DateNano
|
|
36
|
+
flex = OBXPropertyType_Flex
|
|
37
|
+
relation = OBXPropertyType_Relation
|
|
38
|
+
boolVector = OBXPropertyType_BoolVector
|
|
39
|
+
byteVector = OBXPropertyType_ByteVector
|
|
40
|
+
shortVector = OBXPropertyType_ShortVector
|
|
41
|
+
charVector = OBXPropertyType_CharVector
|
|
42
|
+
intVector = OBXPropertyType_IntVector
|
|
43
|
+
longVector = OBXPropertyType_LongVector
|
|
44
|
+
floatVector = OBXPropertyType_FloatVector
|
|
45
|
+
doubleVector = OBXPropertyType_DoubleVector
|
|
46
|
+
# stringVector = OBXPropertyType_StringVector
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# FlatBuffers struct format per type (as the packer of the flatbuffers.number_types flags, see Property._fb_type);
|
|
50
|
+
# strings, vectors and flex are offsets ('I'). This avoids importing flatbuffers, which imports numpy, if available.
|
|
51
|
+
_fb_formats = {
|
|
52
|
+
PropertyType.bool: '?',
|
|
53
|
+
PropertyType.byte: 'b',
|
|
54
|
+
PropertyType.short: 'h',
|
|
55
|
+
PropertyType.char: 'b',
|
|
56
|
+
PropertyType.int: 'i',
|
|
57
|
+
PropertyType.long: 'q',
|
|
58
|
+
PropertyType.float: 'f',
|
|
59
|
+
PropertyType.double: 'd',
|
|
60
|
+
PropertyType.string: 'I',
|
|
61
|
+
PropertyType.date: 'q',
|
|
62
|
+
PropertyType.dateNano: 'q',
|
|
63
|
+
PropertyType.flex: 'I',
|
|
64
|
+
PropertyType.relation: 'q',
|
|
65
|
+
PropertyType.boolVector: 'I',
|
|
66
|
+
PropertyType.byteVector: 'I',
|
|
67
|
+
PropertyType.shortVector: 'I',
|
|
68
|
+
PropertyType.charVector: 'I',
|
|
69
|
+
PropertyType.intVector: 'I',
|
|
70
|
+
PropertyType.longVector: 'I',
|
|
71
|
+
PropertyType.floatVector: 'I',
|
|
72
|
+
PropertyType.doubleVector: 'I',
|
|
73
|
+
# PropertyType.stringVector: 'I',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# flatbuffers.number_types flags per struct format
|
|
77
|
+
_fb_flags_names = {'?': 'BoolFlags', 'b': 'Int8Flags', 'h': 'Int16Flags', 'i': 'Int32Flags', 'q': 'Int64Flags',
|
|
78
|
+
'f': 'Float32Flags', 'd': 'Float64Flags', 'I': 'UOffsetTFlags'}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _ndarray() -> type:
|
|
82
|
+
"""numpy.ndarray; imports numpy, so only call it where numpy is needed (e.g. vector properties)."""
|
|
83
|
+
import numpy
|
|
84
|
+
return numpy.ndarray
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class IndexType(IntEnum):
|
|
88
|
+
VALUE = OBXPropertyFlags_INDEXED
|
|
89
|
+
HASH = OBXPropertyFlags_INDEX_HASH
|
|
90
|
+
HASH64 = OBXPropertyFlags_INDEX_HASH64
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Index:
|
|
94
|
+
"""Property index, e.g. ``name = String(index=Index())``.
|
|
95
|
+
|
|
96
|
+
The default type is HASH for String properties (indexes a hash of the value, which takes less space) and VALUE for
|
|
97
|
+
other types, like in the other ObjectBox languages. Hash indexes are only supported for String properties.
|
|
98
|
+
Note: to use the index of a String property, query it with case-sensitive conditions (the default)."""
|
|
99
|
+
|
|
100
|
+
# TODO HNSW isn't a `type` but HASH and HASH64 are, remove type member and make HashIndex and Hash64Index classes?
|
|
101
|
+
|
|
102
|
+
def __init__(self, type: Optional[IndexType] = None, uid: int = 0):
|
|
103
|
+
self.type = type # None: the default for the property type (_type_for())
|
|
104
|
+
|
|
105
|
+
self.iduid = IdUid(0, uid)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def id(self):
|
|
109
|
+
""" Index Id """
|
|
110
|
+
return self.iduid.id
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def uid(self):
|
|
114
|
+
""" Index UId """
|
|
115
|
+
return self.iduid.uid
|
|
116
|
+
|
|
117
|
+
def has_uid(self):
|
|
118
|
+
return self.iduid.uid != 0
|
|
119
|
+
|
|
120
|
+
def _type_for(self, ob_type: int) -> IndexType:
|
|
121
|
+
if self.type is not None:
|
|
122
|
+
return self.type
|
|
123
|
+
return IndexType.HASH if ob_type == OBXPropertyType_String else IndexType.VALUE
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ConflictStrategy(IntEnum):
|
|
127
|
+
"""What happens when putting an object violates a :class:`Unique` constraint."""
|
|
128
|
+
|
|
129
|
+
FAIL = 0
|
|
130
|
+
"""Putting raises a ``UniqueViolatedError`` (the default)."""
|
|
131
|
+
|
|
132
|
+
REPLACE = 1
|
|
133
|
+
"""The existing object with the same value is removed, and the put object is stored (with its own ID).
|
|
134
|
+
Only one property of an entity can use it; required for all unique properties of a Sync entity."""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class Unique:
|
|
138
|
+
"""Unique constraint of a property, e.g. ``email = String(unique=Unique())``: putting an object with a value
|
|
139
|
+
that another object of the entity already has raises a ``UniqueViolatedError`` (by default; see ``on_conflict``).
|
|
140
|
+
|
|
141
|
+
A unique property is indexed: it gets an :class:`Index` (of the default type) unless one is given via ``index=``,
|
|
142
|
+
e.g. to set its type. Like in the other ObjectBox languages, not supported for the ID and for types that do not
|
|
143
|
+
support an index (e.g. floating point and vector properties)."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, on_conflict: ConflictStrategy = ConflictStrategy.FAIL):
|
|
146
|
+
self.on_conflict = on_conflict
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class HnswFlags(IntEnum):
|
|
150
|
+
"""
|
|
151
|
+
Vector-Search HNSW Index Flags
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
NONE = 0
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
DEBUG_LOGS = 1
|
|
160
|
+
"""
|
|
161
|
+
Enables debug logs.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
DEBUG_LOGS_DETAILED = 2
|
|
165
|
+
"""
|
|
166
|
+
Enables "high volume" debug logs, e.g. individual gets/puts.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
VECTOR_CACHE_SIMD_PADDING_OFF = 4
|
|
170
|
+
"""
|
|
171
|
+
Padding for SIMD is enabled by default, which uses more memory but may be faster. This flag turns it off.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
REPARATION_LIMIT_CANDIDATES = 8
|
|
175
|
+
"""
|
|
176
|
+
If the speed of removing nodes becomes a concern in your use case, you can speed it up by setting this flag.
|
|
177
|
+
By default, repairing the graph after node removals creates more connections to improve the graph's quality.
|
|
178
|
+
The extra costs for this are relatively low (e.g. vs. regular indexing), and thus the default is recommended.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
class VectorDistanceType(IntEnum):
|
|
182
|
+
"""
|
|
183
|
+
Vector-search distance computation strategy type
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
UNKNOWN = OBXVectorDistanceType_UNKNOWN
|
|
187
|
+
"""
|
|
188
|
+
Not a real type, just best practice (e.g. forward compatibility)
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
EUCLIDEAN = OBXVectorDistanceType_EUCLIDEAN
|
|
192
|
+
"""
|
|
193
|
+
The default; typically 'euclidean squared' internally."
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
COSINE = OBXVectorDistanceType_COSINE
|
|
197
|
+
"""
|
|
198
|
+
Cosine similarity compares two vectors irrespective of their magnitude (compares the angle of two vectors).
|
|
199
|
+
Often used for document or semantic similarity.
|
|
200
|
+
Value range: 0.0 - 2.0 (0.0: same direction, 1.0: orthogonal, 2.0: opposite direction)
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
DOT_PRODUCT = OBXVectorDistanceType_DOT_PRODUCT
|
|
204
|
+
"""
|
|
205
|
+
For normalized vectors (vector length == 1.0), the dot product is equivalent to the cosine similarity.
|
|
206
|
+
Because of this, the dot product is often preferred as it performs better.
|
|
207
|
+
Value range (normalized vectors): 0.0 - 2.0 (0.0: same direction, 1.0: orthogonal, 2.0: opposite direction)
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
DOT_PRODUCT_NON_NORMALIZED = OBXVectorDistanceType_DOT_PRODUCT_NON_NORMALIZED
|
|
211
|
+
"""
|
|
212
|
+
A custom dot product similarity measure that does not require the vectors to be normalized.
|
|
213
|
+
Note: this is no replacement for cosine similarity (like DotProduct for normalized vectors is).
|
|
214
|
+
The non-linear conversion provides a high precision over the entire float range (for the raw dot product).
|
|
215
|
+
The higher the dot product, the lower the distance is (the nearer the vectors are).
|
|
216
|
+
The more negative the dot product, the higher the distance is (the farther the vectors are).
|
|
217
|
+
Value range: 0.0 - 2.0 (nonlinear; 0.0: nearest, 1.0: orthogonal, 2.0: farthest)
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
class HnswIndex:
|
|
221
|
+
"""Vector-Search HNSW Property Index"""
|
|
222
|
+
def __init__(self,
|
|
223
|
+
dimensions: int,
|
|
224
|
+
neighbors_per_node: Optional[int] = None,
|
|
225
|
+
indexing_search_count: Optional[int] = None,
|
|
226
|
+
flags: HnswFlags = HnswFlags.NONE,
|
|
227
|
+
distance_type: VectorDistanceType = VectorDistanceType.EUCLIDEAN,
|
|
228
|
+
reparation_backlink_probability: Optional[float] = None,
|
|
229
|
+
vector_cache_hint_size_kb: Optional[int] = None,
|
|
230
|
+
uid: int = 0):
|
|
231
|
+
"""
|
|
232
|
+
:param dimensions:
|
|
233
|
+
Vector dimensionality.
|
|
234
|
+
:param neighbors_per_node:
|
|
235
|
+
Maximum number of neighbors per node (aka "M").
|
|
236
|
+
Higher number increases the graph connectivity which can lead to better results, but higher resources usage.
|
|
237
|
+
If no value is set, a default value taken (currently 30).
|
|
238
|
+
Try e.g. 16 for faster but less accurate results, or 64 for more accurate results.
|
|
239
|
+
:param indexing_search_count:
|
|
240
|
+
Maximum number of neighbors searched while indexing (aka "efConstruction").
|
|
241
|
+
If not set, internally default value is currently set to 100, which can change in future version.
|
|
242
|
+
The default value serves as a starting point that can likely be optimized for specific datasets and use cases.
|
|
243
|
+
The higher the value, the more accurate the search, but the longer the indexing will take.
|
|
244
|
+
If indexing time is not a major concern, a value of at least 200 is recommended to improve search quality. blah.
|
|
245
|
+
:param flags:
|
|
246
|
+
Set flags.
|
|
247
|
+
:param distance_type:
|
|
248
|
+
Set distance strategy type.
|
|
249
|
+
:param reparation_backlink_probability:
|
|
250
|
+
When repairing the graph after a node was removed, this gives the probability of adding backlinks to the repaired
|
|
251
|
+
neighbors. The default is 1.0 (aka "always") as this should be worth a bit of extra costs as it improves the graph's quality.
|
|
252
|
+
:param vector_cache_hint_size_kb:
|
|
253
|
+
Vector cache hint size: a non-binding hint of the maximum size of the vector cache in KB, an int
|
|
254
|
+
(default: 2097152 or 2 GB/GiB).
|
|
255
|
+
"""
|
|
256
|
+
self.dimensions = dimensions
|
|
257
|
+
self.neighbors_per_node = neighbors_per_node
|
|
258
|
+
self.indexing_search_count = indexing_search_count
|
|
259
|
+
self.flags = flags
|
|
260
|
+
self.distance_type = distance_type
|
|
261
|
+
self.reparation_backlink_probability = reparation_backlink_probability
|
|
262
|
+
if vector_cache_hint_size_kb is not None and (not isinstance(vector_cache_hint_size_kb, int) or
|
|
263
|
+
isinstance(vector_cache_hint_size_kb, bool)):
|
|
264
|
+
raise TypeError(f"vector_cache_hint_size_kb must be an int (KB), got {vector_cache_hint_size_kb!r}")
|
|
265
|
+
self.vector_cache_hint_size_kb = vector_cache_hint_size_kb
|
|
266
|
+
|
|
267
|
+
self.iduid = IdUid(0, uid)
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def id(self):
|
|
271
|
+
""" Index Id """
|
|
272
|
+
return self.iduid.id
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def uid(self):
|
|
276
|
+
""" Index Uid """
|
|
277
|
+
return self.iduid.uid
|
|
278
|
+
|
|
279
|
+
def has_uid(self):
|
|
280
|
+
""" Returns true if Uid is set. """
|
|
281
|
+
return self.uid != 0
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Property:
|
|
285
|
+
def __init__(self, pytype: Type, uid: int = 0, **kwargs):
|
|
286
|
+
Property._check_args(kwargs, ('index', 'type', 'unique'))
|
|
287
|
+
self.iduid = IdUid(0, uid)
|
|
288
|
+
self.name = "" # Python attribute name; set in Entity.fill_properties()
|
|
289
|
+
self._db_name = "" # Name in the database and model JSON (camelCase by default); set in Entity.fill_properties()
|
|
290
|
+
self._entity = None # The _Entity this property belongs to; set in Entity.fill_properties()
|
|
291
|
+
self.index = Property._index_arg(kwargs.get('index', None))
|
|
292
|
+
self.unique = Property._unique_arg(kwargs.get('unique', None))
|
|
293
|
+
if self.unique is not None:
|
|
294
|
+
if isinstance(self.index, HnswIndex):
|
|
295
|
+
raise TypeError("A unique property can not have a HnswIndex")
|
|
296
|
+
if self.index is None:
|
|
297
|
+
self.index = Index()
|
|
298
|
+
|
|
299
|
+
self._py_type = pytype
|
|
300
|
+
self._is_ndarray = pytype is getattr(sys.modules.get('numpy'), 'ndarray', None) # numpy imported if ndarray
|
|
301
|
+
self._ob_type = kwargs['type'] if 'type' in kwargs else self._determine_ob_type()
|
|
302
|
+
self._fb_format = _fb_formats[self._ob_type] # struct format of the field (offsets: 'I')
|
|
303
|
+
|
|
304
|
+
self._flags = 0
|
|
305
|
+
self._set_flags()
|
|
306
|
+
|
|
307
|
+
self._fb_slot = None
|
|
308
|
+
self._fb_v_offset = None
|
|
309
|
+
|
|
310
|
+
def _condition_value(self, value):
|
|
311
|
+
""" Converts a value for a query condition (overridden, e.g. for dates). """
|
|
312
|
+
return value
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def _check_args(kwargs: dict, allowed):
|
|
316
|
+
unknown = set(kwargs) - set(allowed)
|
|
317
|
+
if 'id' in unknown:
|
|
318
|
+
raise TypeError("Properties do not take an id: IDs are assigned via the model JSON file "
|
|
319
|
+
"(to identify a property, e.g. when renaming it, use uid=)")
|
|
320
|
+
if unknown:
|
|
321
|
+
raise TypeError(f"Unexpected property argument(s): {', '.join(sorted(unknown))}")
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def _unique_arg(unique):
|
|
325
|
+
if unique is Unique: # The class, e.g. String(unique=Unique), like property classes used without parentheses
|
|
326
|
+
return Unique()
|
|
327
|
+
if unique is not None and not isinstance(unique, Unique):
|
|
328
|
+
raise TypeError(f"unique must be a Unique (e.g. Unique() or Unique(ConflictStrategy.REPLACE)), "
|
|
329
|
+
f"got {unique!r}")
|
|
330
|
+
return unique
|
|
331
|
+
|
|
332
|
+
@staticmethod
|
|
333
|
+
def _index_arg(index):
|
|
334
|
+
if index is Index: # The class, e.g. String(index=Index), like property classes used without parentheses
|
|
335
|
+
return Index()
|
|
336
|
+
if index is not None and not isinstance(index, (Index, HnswIndex)):
|
|
337
|
+
raise TypeError(f"index must be an Index (e.g. Index() or Index(IndexType.VALUE)) or a HnswIndex, "
|
|
338
|
+
f"got {index!r}")
|
|
339
|
+
return index
|
|
340
|
+
|
|
341
|
+
def __getattr__(self, name):
|
|
342
|
+
# Only called if the attribute is not set: _fb_type is resolved on first use, then set on the instance.
|
|
343
|
+
if name == '_fb_type' and '_fb_format' in self.__dict__:
|
|
344
|
+
import flatbuffers.number_types # imports numpy (if available), so only when needed
|
|
345
|
+
self._fb_type = getattr(flatbuffers.number_types, _fb_flags_names[self.__dict__['_fb_format']])
|
|
346
|
+
return self._fb_type
|
|
347
|
+
raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name))
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def id(self):
|
|
351
|
+
""" Property Id """
|
|
352
|
+
return self.iduid.id
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def uid(self):
|
|
356
|
+
""" Property Uid """
|
|
357
|
+
return self.iduid.uid
|
|
358
|
+
|
|
359
|
+
def has_uid(self):
|
|
360
|
+
""" Returns true if property has a valid Uid """
|
|
361
|
+
return self.uid != 0
|
|
362
|
+
|
|
363
|
+
def is_id(self) -> bool:
|
|
364
|
+
""" Check if Property is an Id Property. """
|
|
365
|
+
return isinstance(self, Id)
|
|
366
|
+
|
|
367
|
+
def on_sync(self):
|
|
368
|
+
""" Method called once ID/UID are synced with the model file. """
|
|
369
|
+
assert self.iduid.is_assigned()
|
|
370
|
+
self._fb_slot = self.id - 1
|
|
371
|
+
self._fb_v_offset = 4 + 2 * self._fb_slot
|
|
372
|
+
|
|
373
|
+
def _determine_ob_type(self) -> OBXPropertyType:
|
|
374
|
+
""" Tries to infer the OBX property type from the Python type. """
|
|
375
|
+
ts = self._py_type
|
|
376
|
+
if ts == str:
|
|
377
|
+
return OBXPropertyType_String
|
|
378
|
+
elif ts == int:
|
|
379
|
+
return OBXPropertyType_Long
|
|
380
|
+
elif ts == bytes: # or ts == bytearray: might require further tests on read objects due to mutability
|
|
381
|
+
return OBXPropertyType_ByteVector
|
|
382
|
+
elif ts == list or self._is_ndarray:
|
|
383
|
+
return OBXPropertyType_DoubleVector
|
|
384
|
+
elif ts == float:
|
|
385
|
+
return OBXPropertyType_Double
|
|
386
|
+
elif ts == bool:
|
|
387
|
+
return OBXPropertyType_Bool
|
|
388
|
+
else:
|
|
389
|
+
raise TypeError("unknown property type %s" % ts)
|
|
390
|
+
|
|
391
|
+
def _set_flags(self):
|
|
392
|
+
if self.is_id():
|
|
393
|
+
self._flags |= OBXPropertyFlags_ID
|
|
394
|
+
|
|
395
|
+
if self.index is not None:
|
|
396
|
+
self._flags |= OBXPropertyFlags_INDEXED
|
|
397
|
+
if isinstance(self.index, Index): # Generic index
|
|
398
|
+
self._flags |= self.index._type_for(self._ob_type)
|
|
399
|
+
|
|
400
|
+
if self.unique is not None:
|
|
401
|
+
self._flags |= OBXPropertyFlags_UNIQUE
|
|
402
|
+
if self.unique.on_conflict == ConflictStrategy.REPLACE:
|
|
403
|
+
self._flags |= OBXPropertyFlags_UNIQUE_ON_CONFLICT_REPLACE
|
|
404
|
+
|
|
405
|
+
def is_null(self) -> PropertyQueryCondition:
|
|
406
|
+
""" *Is null* condition to be passed to :func:`Box.query`: matches objects without a value for this property,
|
|
407
|
+
i.e. it was put with None (unset properties are stored with their default value, not null) """
|
|
408
|
+
self._assert_ids_assigned()
|
|
409
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.IS_NULL, {})
|
|
410
|
+
|
|
411
|
+
def is_not_null(self) -> PropertyQueryCondition:
|
|
412
|
+
""" *Is not null* condition to be passed to :func:`Box.query`: matches objects with a value for this property """
|
|
413
|
+
self._assert_ids_assigned()
|
|
414
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.NOT_NULL, {})
|
|
415
|
+
|
|
416
|
+
def _assert_ids_assigned(self):
|
|
417
|
+
# Using assert(s) so they can be optionally disabled for performance
|
|
418
|
+
assert self.iduid.is_assigned(), f"Property \"{self.name}\" ID not assigned"
|
|
419
|
+
if self.index is not None:
|
|
420
|
+
assert self.index.iduid.is_assigned(), f"Property \"{self.name}\" index ID not assigned"
|
|
421
|
+
|
|
422
|
+
class _NumericProperty(Property):
|
|
423
|
+
"""Common class for numeric conditions.
|
|
424
|
+
Implicitly no support for equals/not_equals, see also _IntProperty below.
|
|
425
|
+
"""
|
|
426
|
+
def __init__(self, py_type : Type, **kwargs):
|
|
427
|
+
super(_NumericProperty, self).__init__(py_type, **kwargs)
|
|
428
|
+
|
|
429
|
+
def greater_than(self, value) -> PropertyQueryCondition:
|
|
430
|
+
""" *Greater-than* (``>``) condition to be passed to :func:`objectbox.Box.query` """
|
|
431
|
+
self._assert_ids_assigned()
|
|
432
|
+
args = {'value': self._condition_value(value)}
|
|
433
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GT, args)
|
|
434
|
+
|
|
435
|
+
def greater_or_equal(self, value) -> PropertyQueryCondition:
|
|
436
|
+
""" *Greater-or-equal* (``>=``) condition to be passed to :func:`Box.query` """
|
|
437
|
+
self._assert_ids_assigned()
|
|
438
|
+
args = {'value': self._condition_value(value)}
|
|
439
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GTE, args)
|
|
440
|
+
|
|
441
|
+
def less_than(self, value) -> PropertyQueryCondition:
|
|
442
|
+
""" *Less-than* (``<``) condition to be passed to :func:`Box.query` """
|
|
443
|
+
self._assert_ids_assigned()
|
|
444
|
+
args = {'value': self._condition_value(value)}
|
|
445
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LT, args)
|
|
446
|
+
|
|
447
|
+
def less_or_equal(self, value) -> PropertyQueryCondition:
|
|
448
|
+
""" *Less-or-equal* (``<=``) condition to be passed to :func:`Box.query` """
|
|
449
|
+
self._assert_ids_assigned()
|
|
450
|
+
args = {'value': self._condition_value(value)}
|
|
451
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LTE, args)
|
|
452
|
+
|
|
453
|
+
def between(self, a, b) -> PropertyQueryCondition:
|
|
454
|
+
""" *Between* a and b (``a <= x <= b``) condition to be passed to :func:`Box.query` """
|
|
455
|
+
self._assert_ids_assigned()
|
|
456
|
+
args = {'a': self._condition_value(a), 'b': self._condition_value(b)}
|
|
457
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.BETWEEN, args)
|
|
458
|
+
|
|
459
|
+
class _IntProperty(_NumericProperty):
|
|
460
|
+
"""Integer-based conditions.
|
|
461
|
+
Adds support for equals/not_equals.
|
|
462
|
+
"""
|
|
463
|
+
def __init__(self, py_type : Type, **kwargs):
|
|
464
|
+
super(_IntProperty, self).__init__(py_type, **kwargs)
|
|
465
|
+
|
|
466
|
+
def _condition_value(self, value):
|
|
467
|
+
if not isinstance(value, int): # Includes bool (an int)
|
|
468
|
+
raise TypeError(f"Property {self.name} takes int values in conditions, got {value!r}")
|
|
469
|
+
return value
|
|
470
|
+
|
|
471
|
+
def equals(self, value) -> PropertyQueryCondition:
|
|
472
|
+
""" *Equals* (``==``) condition to be passed to :func:`Box.query` """
|
|
473
|
+
self._assert_ids_assigned()
|
|
474
|
+
args = {'value': self._condition_value(value)}
|
|
475
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.EQ, args)
|
|
476
|
+
|
|
477
|
+
def not_equals(self, value) -> PropertyQueryCondition:
|
|
478
|
+
""" *Not equals* (``!=``) condition to be passed to :func:`Box.query` """
|
|
479
|
+
self._assert_ids_assigned()
|
|
480
|
+
args = {'value': self._condition_value(value)}
|
|
481
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.NOT_EQ, args)
|
|
482
|
+
|
|
483
|
+
def one_of(self, values: List[int]) -> PropertyQueryCondition:
|
|
484
|
+
""" *one of* (``in``) condition to be passed to :func:`Box.query`;
|
|
485
|
+
matches if the property value equals any of the given values.
|
|
486
|
+
Supported for 32-bit (Int32) and 64-bit (Int64, Id, Date, DateNano, Relation) integer properties;
|
|
487
|
+
raises a ``TypeError`` for other properties (e.g. Int8, Int16, Bool). """
|
|
488
|
+
self._assert_ids_assigned()
|
|
489
|
+
self._assert_one_of_supported()
|
|
490
|
+
args = {'values': [self._condition_value(value) for value in values]}
|
|
491
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.ONE_OF, args)
|
|
492
|
+
|
|
493
|
+
def not_one_of(self, values: List[int]) -> PropertyQueryCondition:
|
|
494
|
+
""" *not one of* (``not in``) condition to be passed to :func:`Box.query`;
|
|
495
|
+
matches if the property value equals none of the given values.
|
|
496
|
+
Supported for 32-bit (Int32) and 64-bit (Int64, Id, Date, DateNano, Relation) integer properties;
|
|
497
|
+
raises a ``TypeError`` for other properties (e.g. Int8, Int16, Bool). """
|
|
498
|
+
self._assert_ids_assigned()
|
|
499
|
+
self._assert_one_of_supported()
|
|
500
|
+
args = {'values': [self._condition_value(value) for value in values]}
|
|
501
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.NOT_ONE_OF, args)
|
|
502
|
+
|
|
503
|
+
def _assert_one_of_supported(self):
|
|
504
|
+
# The C API only supports 32-bit and 64-bit integers for "in" conditions
|
|
505
|
+
if self._ob_type not in (PropertyType.int, PropertyType.long, PropertyType.date, PropertyType.dateNano,
|
|
506
|
+
PropertyType.relation):
|
|
507
|
+
raise TypeError(f"Property \"{self.name}\" of type {PropertyType(self._ob_type).name} "
|
|
508
|
+
f"does not support one_of()/not_one_of(); supported are 32-bit and 64-bit integers")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# ID property (primary key)
|
|
512
|
+
class Id(_IntProperty):
|
|
513
|
+
"""ID property: the object ID (an int), 0 for a new object; put() assigns it.
|
|
514
|
+
|
|
515
|
+
By default, IDs are assigned by ObjectBox only: putting an object with an ID that was not assigned by ObjectBox
|
|
516
|
+
raises (an IllegalArgumentError). Set ``assignable=True`` to put objects with IDs of your own (e.g. from another
|
|
517
|
+
system); new objects with ID 0 still get IDs assigned (higher than any ID put before)."""
|
|
518
|
+
def __init__(self, *, uid: int = 0, py_type: type = int, assignable: bool = False, **kwargs):
|
|
519
|
+
Property._check_args(kwargs, ())
|
|
520
|
+
super(Id, self).__init__(py_type, uid=uid)
|
|
521
|
+
self.assignable = assignable
|
|
522
|
+
if assignable:
|
|
523
|
+
self._flags |= OBXPropertyFlags_ID_SELF_ASSIGNABLE
|
|
524
|
+
|
|
525
|
+
# Bool property
|
|
526
|
+
class Bool(_IntProperty):
|
|
527
|
+
"""Boolean Property"""
|
|
528
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
529
|
+
super(Bool, self).__init__(bool, type=PropertyType.bool, uid=uid, **kwargs)
|
|
530
|
+
|
|
531
|
+
# String property with starts/ends_with
|
|
532
|
+
class String(Property):
|
|
533
|
+
"""String Property"""
|
|
534
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
535
|
+
super(String, self).__init__(str, type=PropertyType.string, uid=uid, **kwargs)
|
|
536
|
+
|
|
537
|
+
def starts_with(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
538
|
+
""" *starts with* (string-prefix) condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
539
|
+
self._assert_ids_assigned()
|
|
540
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
541
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.STARTS_WITH, args)
|
|
542
|
+
|
|
543
|
+
def ends_with(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
544
|
+
""" *ends with* (string-suffix) condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
545
|
+
self._assert_ids_assigned()
|
|
546
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
547
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.ENDS_WITH, args)
|
|
548
|
+
|
|
549
|
+
def equals(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
550
|
+
""" *equals* (``==``) condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
551
|
+
self._assert_ids_assigned()
|
|
552
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
553
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.EQ, args)
|
|
554
|
+
|
|
555
|
+
def not_equals(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
556
|
+
""" *not-equals* (``~=``) condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
557
|
+
self._assert_ids_assigned()
|
|
558
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
559
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.NOT_EQ, args)
|
|
560
|
+
|
|
561
|
+
def contains(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
562
|
+
""" *contains string* condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
563
|
+
self._assert_ids_assigned()
|
|
564
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
565
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.CONTAINS, args)
|
|
566
|
+
|
|
567
|
+
def greater_than(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
568
|
+
""" *greater-than* condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
569
|
+
self._assert_ids_assigned()
|
|
570
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
571
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GT, args)
|
|
572
|
+
|
|
573
|
+
def greater_or_equal(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
574
|
+
""" *greater-or-equal* condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
575
|
+
self._assert_ids_assigned()
|
|
576
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
577
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GTE, args)
|
|
578
|
+
|
|
579
|
+
def less_than(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
580
|
+
""" *less-than* condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
581
|
+
self._assert_ids_assigned()
|
|
582
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
583
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LT, args)
|
|
584
|
+
|
|
585
|
+
def less_or_equal(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
586
|
+
""" *less-or-equal* condition (opt-in: case-sensitive) to be passed to :func:`Box.query` """
|
|
587
|
+
self._assert_ids_assigned()
|
|
588
|
+
args = {'value': value, 'case_sensitive': case_sensitive}
|
|
589
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LTE, args)
|
|
590
|
+
|
|
591
|
+
def one_of(self, values: List[str], case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
592
|
+
""" *one of* (``in``) condition (opt-in: case-sensitive) to be passed to :func:`Box.query`;
|
|
593
|
+
matches if the property value equals any of the given values. """
|
|
594
|
+
self._assert_ids_assigned()
|
|
595
|
+
args = {'values': values, 'case_sensitive': case_sensitive, 'string': True}
|
|
596
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.ONE_OF, args)
|
|
597
|
+
|
|
598
|
+
# Signed Integer Numeric Properties
|
|
599
|
+
class Int8(_IntProperty):
|
|
600
|
+
"""Integer 8-bit Property"""
|
|
601
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
602
|
+
super(Int8, self).__init__(int, type=PropertyType.byte, uid=uid, **kwargs)
|
|
603
|
+
class Int16(_IntProperty):
|
|
604
|
+
"""Integer 16-bit Property"""
|
|
605
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
606
|
+
super(Int16, self).__init__(int, type=PropertyType.short, uid=uid, **kwargs)
|
|
607
|
+
class Int32(_IntProperty):
|
|
608
|
+
"""Integer 32-bit Property"""
|
|
609
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
610
|
+
super(Int32, self).__init__(int, type=PropertyType.int, uid=uid, **kwargs)
|
|
611
|
+
class Int64(_IntProperty):
|
|
612
|
+
"""Integer 64-bit Property"""
|
|
613
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
614
|
+
super(Int64, self).__init__(int, type=PropertyType.long, uid=uid, **kwargs)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class Relation(_IntProperty):
|
|
618
|
+
"""To-one relation: the ID of an object of the target entity (an int; 0 for none), e.g.
|
|
619
|
+
``customer_id = Relation(Customer)``. Unlike an ``Int64``, ObjectBox knows the target: e.g. removing the target
|
|
620
|
+
object resets the relation to 0. The relation is indexed (no index for 0).
|
|
621
|
+
|
|
622
|
+
Name it like the relation with an ``_id`` suffix (e.g. ``customer_id`` for a customer): it is stored as
|
|
623
|
+
``customerId``, as a ``ToOne`` named ``customer`` in Java, Kotlin and Dart stores its target ID, so the model and
|
|
624
|
+
data match theirs (e.g. for Sync).
|
|
625
|
+
|
|
626
|
+
:param target: the target entity, i.e. the class decorated with ``@Entity()``, or its name, e.g. for a relation to
|
|
627
|
+
the entity itself (``parent_id = Relation("Node")``) or to an entity defined later.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
def __init__(self, target, *, uid: int = 0, **kwargs):
|
|
631
|
+
Property._check_args(kwargs, ())
|
|
632
|
+
from objectbox.model.entity import _Entity
|
|
633
|
+
if not isinstance(target, (_Entity, str)):
|
|
634
|
+
raise TypeError(f"Relation target must be an entity (a class decorated with @Entity()) or an entity name, "
|
|
635
|
+
f"got {target!r}")
|
|
636
|
+
super(Relation, self).__init__(int, type=PropertyType.relation, uid=uid, index=Index())
|
|
637
|
+
self._flags |= OBXPropertyFlags_INDEX_PARTIAL_SKIP_ZERO
|
|
638
|
+
self._target = target
|
|
639
|
+
|
|
640
|
+
@property
|
|
641
|
+
def _target_name(self) -> str:
|
|
642
|
+
return self._target if isinstance(self._target, str) else self._target._name
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
class SyncClock(Int64):
|
|
646
|
+
"""Sync clock property (64-bit integer) of a Sync entity (see ``@SyncEntity()``): a "hybrid logical clock" to
|
|
647
|
+
resolve Sync conflicts. These clock values allow "last write wins" conflict resolution.
|
|
648
|
+
|
|
649
|
+
There can be only one sync clock per Sync entity type; which is also recommended for basic conflict resolution.
|
|
650
|
+
For new objects, keep the value 0 (the default) to reserve "a slot" in the object data. ObjectBox Sync updates
|
|
651
|
+
this property automatically on put operations.
|
|
652
|
+
As a hybrid clock, it combines a wall clock with a logical counter to compensate for some clock skew effects.
|
|
653
|
+
To get a timestamp from a value, see ``Sync.sync_clock_timestamp()``.
|
|
654
|
+
"""
|
|
655
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
656
|
+
Property._check_args(kwargs, ())
|
|
657
|
+
super(SyncClock, self).__init__(uid=uid)
|
|
658
|
+
self._flags |= OBXPropertyFlags_SYNC_CLOCK
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class SyncPrecedence(Int64):
|
|
662
|
+
"""Sync precedence property (64-bit integer) of a Sync entity (see ``@SyncEntity()``) to customize Sync conflict
|
|
663
|
+
resolution: developer-assigned precedence values are then used to resolve conflicts via "higher precedence wins".
|
|
664
|
+
Defining and assigning precedence values are completely in the hands of the developer.
|
|
665
|
+
|
|
666
|
+
There can be only one sync precedence per Sync entity type.
|
|
667
|
+
Typically, it is combined with a ``SyncClock``, with the latter being the tie-breaker for equal precedence values.
|
|
668
|
+
"""
|
|
669
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
670
|
+
Property._check_args(kwargs, ())
|
|
671
|
+
super(SyncPrecedence, self).__init__(uid=uid)
|
|
672
|
+
self._flags |= OBXPropertyFlags_SYNC_PRECEDENCE
|
|
673
|
+
|
|
674
|
+
# Floating-Point Numeric Properties
|
|
675
|
+
class Float32(_NumericProperty):
|
|
676
|
+
"""Floating-point 32-bit Property"""
|
|
677
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
678
|
+
super(Float32, self).__init__(float, type=PropertyType.float, uid=uid, **kwargs)
|
|
679
|
+
|
|
680
|
+
class Float64(_NumericProperty):
|
|
681
|
+
"""Floating-point 64-bit Property"""
|
|
682
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
683
|
+
super(Float64, self).__init__(float, type=PropertyType.double, uid=uid, **kwargs)
|
|
684
|
+
|
|
685
|
+
# Date Properties
|
|
686
|
+
class Date(_IntProperty):
|
|
687
|
+
"""Date property: stored as milliseconds since the Unix epoch. Values (also in query conditions) can be a
|
|
688
|
+
``datetime``, a ``float`` (seconds) or an ``int`` (milliseconds); ``py_type`` is the type read back."""
|
|
689
|
+
_multiplier = 1000 # From seconds
|
|
690
|
+
|
|
691
|
+
def __init__(self, py_type=datetime, *, uid: int = 0, **kwargs):
|
|
692
|
+
super(Date, self).__init__(py_type, type=PropertyType.date, uid=uid, **kwargs)
|
|
693
|
+
|
|
694
|
+
def _condition_value(self, value):
|
|
695
|
+
from objectbox.utils import date_value_to_int
|
|
696
|
+
return date_value_to_int(value, self._multiplier)
|
|
697
|
+
|
|
698
|
+
class DateNano(_IntProperty):
|
|
699
|
+
"""Date property with nanosecond resolution: stored as nanoseconds since the Unix epoch. Values (also in query
|
|
700
|
+
conditions) can be a ``datetime``, a ``float`` (seconds) or an ``int`` (nanoseconds); ``py_type`` is the type
|
|
701
|
+
read back."""
|
|
702
|
+
_multiplier = 1000000000 # From seconds
|
|
703
|
+
|
|
704
|
+
def __init__(self, py_type=datetime, *, uid: int = 0, **kwargs):
|
|
705
|
+
super(DateNano, self).__init__(py_type, type=PropertyType.dateNano, uid=uid, **kwargs)
|
|
706
|
+
|
|
707
|
+
_condition_value = Date._condition_value
|
|
708
|
+
|
|
709
|
+
# Bytes Property
|
|
710
|
+
class Bytes(_NumericProperty):
|
|
711
|
+
"""Bytes blob Property"""
|
|
712
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
713
|
+
super(Bytes, self).__init__(bytes, type=PropertyType.byteVector, uid=uid, **kwargs)
|
|
714
|
+
|
|
715
|
+
def equals(self, value) -> PropertyQueryCondition:
|
|
716
|
+
""" *byte-string equals* (``==``) condition to be passed to :func:`Box.query` """
|
|
717
|
+
self._assert_ids_assigned()
|
|
718
|
+
args = {'value': value}
|
|
719
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.EQ, args)
|
|
720
|
+
|
|
721
|
+
def greater_than(self, value) -> PropertyQueryCondition:
|
|
722
|
+
""" *byte-string greater-than* (``==``) condition to be passed to :func:`Box.query` """
|
|
723
|
+
self._assert_ids_assigned()
|
|
724
|
+
args = {'value': value}
|
|
725
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GT, args)
|
|
726
|
+
|
|
727
|
+
def greater_or_equal(self, value) -> PropertyQueryCondition:
|
|
728
|
+
""" *byte-string greater-or-equal* (``>=``) condition to be passed to :func:`Box.query` """
|
|
729
|
+
self._assert_ids_assigned()
|
|
730
|
+
args = {'value': value}
|
|
731
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.GTE, args)
|
|
732
|
+
|
|
733
|
+
def less_than(self, value) -> PropertyQueryCondition:
|
|
734
|
+
""" *byte-string less-than* (``<``) condition to be passed to :func:`Box.query` """
|
|
735
|
+
self._assert_ids_assigned()
|
|
736
|
+
args = {'value': value}
|
|
737
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LT, args)
|
|
738
|
+
|
|
739
|
+
def less_or_equal(self, value) -> PropertyQueryCondition:
|
|
740
|
+
""" *byte-string less-or-equal* (``<``) condition to be passed to :func:`Box.query` """
|
|
741
|
+
self._assert_ids_assigned()
|
|
742
|
+
args = {'value': value}
|
|
743
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.LTE, args)
|
|
744
|
+
|
|
745
|
+
# Flex Property
|
|
746
|
+
class Flex(Property):
|
|
747
|
+
"""Flex dictionary-compatible Property"""
|
|
748
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
749
|
+
super(Flex, self).__init__(Generic, type=PropertyType.flex, uid=uid, **kwargs)
|
|
750
|
+
def contains_key_value(self, key: str, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
|
|
751
|
+
""" *contains key/valuel* condition to be passed to :func:`Box.query` """
|
|
752
|
+
self._assert_ids_assigned()
|
|
753
|
+
args = {'key': key, 'value': value, 'case_sensitive': case_sensitive}
|
|
754
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.CONTAINS_KEY_VALUE, args)
|
|
755
|
+
|
|
756
|
+
class _VectorProperty(Property):
|
|
757
|
+
def __init__(self, py_type : Type, **kwargs):
|
|
758
|
+
super(_VectorProperty, self).__init__(py_type, **kwargs)
|
|
759
|
+
|
|
760
|
+
class BoolVector(_VectorProperty):
|
|
761
|
+
"""Boolean Vector Property"""
|
|
762
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
763
|
+
super(BoolVector, self).__init__(_ndarray(), type=PropertyType.boolVector, uid=uid, **kwargs)
|
|
764
|
+
class Int8Vector(_VectorProperty):
|
|
765
|
+
"""Integer 8-bit Vector Property"""
|
|
766
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
767
|
+
super(Int8Vector, self).__init__(bytes, type=PropertyType.byteVector, uid=uid, **kwargs)
|
|
768
|
+
|
|
769
|
+
class Int16Vector(_VectorProperty):
|
|
770
|
+
"""Integer 16-bit Vector Property"""
|
|
771
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
772
|
+
super(Int16Vector, self).__init__(_ndarray(), type=PropertyType.shortVector, uid=uid, **kwargs)
|
|
773
|
+
|
|
774
|
+
class CharVector(_VectorProperty):
|
|
775
|
+
"""Char 16-bit Vector Property"""
|
|
776
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
777
|
+
super(CharVector, self).__init__(_ndarray(), type=PropertyType.charVector, uid=uid, **kwargs)
|
|
778
|
+
|
|
779
|
+
class Int32Vector(_VectorProperty):
|
|
780
|
+
"""Integer 32-bit Vector Property"""
|
|
781
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
782
|
+
super(Int32Vector, self).__init__(_ndarray(), type=PropertyType.intVector, uid=uid, **kwargs)
|
|
783
|
+
|
|
784
|
+
class Int64Vector(_VectorProperty):
|
|
785
|
+
"""Integer 64-bit Vector Property"""
|
|
786
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
787
|
+
super(Int64Vector, self).__init__(_ndarray(), type=PropertyType.longVector, uid=uid, **kwargs)
|
|
788
|
+
|
|
789
|
+
class Float32Vector(_VectorProperty):
|
|
790
|
+
"""Floating-point 32-bit Vector Property"""
|
|
791
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
792
|
+
super(Float32Vector, self).__init__(_ndarray(), type=PropertyType.floatVector, uid=uid, **kwargs)
|
|
793
|
+
def nearest_neighbors(self, query_vector, max_result_count: int) -> PropertyQueryCondition:
|
|
794
|
+
""" *Nearest neighbors* condition to be passed to :func:`objectbox.Box.query`: performs an approximate nearest
|
|
795
|
+
neighbor (ANN) search to find objects near to the given query vector. This requires the property to have an
|
|
796
|
+
:class:`HnswIndex`. Get the results ordered by distance via ``find_with_scores()`` or
|
|
797
|
+
``find_ids_with_scores()``.
|
|
798
|
+
|
|
799
|
+
:param query_vector:
|
|
800
|
+
The vector to find the nearest neighbors of (a list of floats or a numpy float32 array); its dimensions
|
|
801
|
+
should be at least the dimensions of the property.
|
|
802
|
+
:param max_result_count:
|
|
803
|
+
The maximum number of objects to return by this condition. Hint: it can also be used as the "ef" HNSW
|
|
804
|
+
parameter to increase the search quality in combination with a query limit, e.g. 100 with
|
|
805
|
+
``query.limit(10)`` may give 10 results of better quality than 10 (quality/performance tradeoff).
|
|
806
|
+
"""
|
|
807
|
+
self._assert_ids_assigned()
|
|
808
|
+
args = {'query_vector': query_vector, 'max_result_count': max_result_count}
|
|
809
|
+
return PropertyQueryCondition(self.id, PropertyQueryConditionOp.NEAREST_NEIGHBOR, args)
|
|
810
|
+
|
|
811
|
+
def nearest_neighbor(self, query_vector, element_count: int) -> PropertyQueryCondition:
|
|
812
|
+
""" Deprecated: use :meth:`nearest_neighbors` (same arguments; named like in the other ObjectBox
|
|
813
|
+
languages). """
|
|
814
|
+
import warnings
|
|
815
|
+
warnings.warn("nearest_neighbor() is deprecated, use nearest_neighbors()", DeprecationWarning, stacklevel=2)
|
|
816
|
+
return self.nearest_neighbors(query_vector, element_count)
|
|
817
|
+
|
|
818
|
+
class Float64Vector(_VectorProperty):
|
|
819
|
+
"""Floating-point 64-bit Vector Property"""
|
|
820
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
821
|
+
super(Float64Vector, self).__init__(_ndarray(), type=PropertyType.doubleVector, uid=uid, **kwargs)
|
|
822
|
+
|
|
823
|
+
class _ListProperty(Property):
|
|
824
|
+
def __init__(self, **kwargs):
|
|
825
|
+
super(_ListProperty, self).__init__(list, **kwargs)
|
|
826
|
+
|
|
827
|
+
class BoolList(_ListProperty):
|
|
828
|
+
"""Boolean List Property"""
|
|
829
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
830
|
+
super(BoolList, self).__init__(type=PropertyType.boolVector, uid=uid, **kwargs)
|
|
831
|
+
|
|
832
|
+
class Int8List(_ListProperty):
|
|
833
|
+
"""Integer 8-bit List Property"""
|
|
834
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
835
|
+
super(Int8List, self).__init__(type=PropertyType.byteVector, uid=uid, **kwargs)
|
|
836
|
+
|
|
837
|
+
class Int16List(_ListProperty):
|
|
838
|
+
"""Integer 16-bit List Property"""
|
|
839
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
840
|
+
super(Int16List, self).__init__(type=PropertyType.shortVector, uid=uid, **kwargs)
|
|
841
|
+
|
|
842
|
+
class Int32List(_ListProperty):
|
|
843
|
+
"""Integer 32-bit List Property"""
|
|
844
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
845
|
+
super(Int32List, self).__init__(type=PropertyType.intVector, uid=uid, **kwargs)
|
|
846
|
+
|
|
847
|
+
class Int64List(_ListProperty):
|
|
848
|
+
"""Integer 64-bit List Property"""
|
|
849
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
850
|
+
super(Int64List, self).__init__(type=PropertyType.longVector, uid=uid, **kwargs)
|
|
851
|
+
|
|
852
|
+
class Float32List(_ListProperty):
|
|
853
|
+
"""Floating-point 32-bit List Property"""
|
|
854
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
855
|
+
super(Float32List, self).__init__(type=PropertyType.floatVector, uid=uid, **kwargs)
|
|
856
|
+
|
|
857
|
+
class Float64List(_ListProperty):
|
|
858
|
+
"""Floating-point 64-bit List Property"""
|
|
859
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
860
|
+
super(Float64List, self).__init__(type=PropertyType.doubleVector, uid=uid, **kwargs)
|
|
861
|
+
|
|
862
|
+
class CharList(_ListProperty):
|
|
863
|
+
"""Char 16-bit List Property"""
|
|
864
|
+
def __init__(self, *, uid: int = 0, **kwargs):
|
|
865
|
+
super(CharList, self).__init__(type=PropertyType.charVector, uid=uid, **kwargs)
|