milvus-haystack 0.0.1__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.
- milvus_haystack/__about__.py +4 -0
- milvus_haystack/__init__.py +4 -0
- milvus_haystack/filter_utils.py +478 -0
- milvus_haystack/milvus.py +841 -0
- milvus_haystack-0.0.1.dist-info/METADATA +48 -0
- milvus_haystack-0.0.1.dist-info/RECORD +8 -0
- milvus_haystack-0.0.1.dist-info/WHEEL +4 -0
- milvus_haystack-0.0.1.dist-info/licenses/LICENSE.txt +73 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
from typing import Union, List, Dict
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from haystack.errors import FilterError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def nested_defaultdict() -> defaultdict:
|
|
10
|
+
"""
|
|
11
|
+
Data structure that recursively adds a dictionary as value if a key does not exist. Advantage: In nested dictionary
|
|
12
|
+
structures, we don't need to check if a key already exists (which can become hard to maintain in nested dictionaries
|
|
13
|
+
with many levels) but access the existing value if a key exists and create an empty dictionary if a key does not
|
|
14
|
+
exist.
|
|
15
|
+
"""
|
|
16
|
+
return defaultdict(nested_defaultdict)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LogicalFilterClause(ABC):
|
|
20
|
+
"""
|
|
21
|
+
Class that is able to parse a filter and convert it to the format that the underlying databases of our
|
|
22
|
+
DocumentStores require.
|
|
23
|
+
|
|
24
|
+
Filters are defined as nested dictionaries. The keys of the dictionaries can be a logical
|
|
25
|
+
operator (`"$and"`, `"$or"`, `"$not"`), a comparison operator (`"$eq"`, `"$in"`, `"$gt"`, `"$gte"`, `"$lt"`,
|
|
26
|
+
`"$lte"`) or a metadata field name.
|
|
27
|
+
Logical operator keys take a dictionary of metadata field names and/or logical operators as
|
|
28
|
+
value. Metadata field names take a dictionary of comparison operators as value. Comparison
|
|
29
|
+
operator keys take a single value or (in case of `"$in"`) a list of values as value.
|
|
30
|
+
If no logical operator is provided, `"$and"` is used as default operation. If no comparison
|
|
31
|
+
operator is provided, `"$eq"` (or `"$in"` if the comparison value is a list) is used as default
|
|
32
|
+
operation.
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
filters = {
|
|
36
|
+
"$and": {
|
|
37
|
+
"type": {"$eq": "article"},
|
|
38
|
+
"date": {"$gte": "2015-01-01", "$lt": "2021-01-01"},
|
|
39
|
+
"rating": {"$gte": 3},
|
|
40
|
+
"$or": {
|
|
41
|
+
"genre": {"$in": ["economy", "politics"]},
|
|
42
|
+
"publisher": {"$eq": "nytimes"}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
# or simpler using default operators
|
|
47
|
+
filters = {
|
|
48
|
+
"type": "article",
|
|
49
|
+
"date": {"$gte": "2015-01-01", "$lt": "2021-01-01"},
|
|
50
|
+
"rating": {"$gte": 3},
|
|
51
|
+
"$or": {
|
|
52
|
+
"genre": ["economy", "politics"],
|
|
53
|
+
"publisher": "nytimes"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To use the same logical operator multiple times on the same level, logical operators take optionally a list of
|
|
59
|
+
dictionaries as value.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
```python
|
|
63
|
+
filters = {
|
|
64
|
+
"$or": [
|
|
65
|
+
{
|
|
66
|
+
"$and": {
|
|
67
|
+
"Type": "News Paper",
|
|
68
|
+
"Date": {
|
|
69
|
+
"$lt": "2019-01-01"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"$and": {
|
|
75
|
+
"Type": "Blog Post",
|
|
76
|
+
"Date": {
|
|
77
|
+
"$gte": "2019-01-01"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self, conditions: List[Union["LogicalFilterClause", "ComparisonOperation"]]
|
|
89
|
+
):
|
|
90
|
+
self.conditions = conditions
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def evaluate(self, fields) -> bool:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def parse(
|
|
98
|
+
cls, filter_term: Union[dict, List[dict]]
|
|
99
|
+
) -> Union["LogicalFilterClause", "ComparisonOperation"]:
|
|
100
|
+
"""
|
|
101
|
+
Parses a filter dictionary/list and returns a LogicalFilterClause instance.
|
|
102
|
+
|
|
103
|
+
:param filter_term: Dictionary or list that contains the filter definition.
|
|
104
|
+
"""
|
|
105
|
+
conditions: List[Union[LogicalFilterClause, ComparisonOperation]] = []
|
|
106
|
+
|
|
107
|
+
if isinstance(filter_term, dict):
|
|
108
|
+
filter_term = [filter_term]
|
|
109
|
+
for item in filter_term:
|
|
110
|
+
for key, value in item.items():
|
|
111
|
+
if key == "$not":
|
|
112
|
+
conditions.append(NotOperation.parse(value))
|
|
113
|
+
elif key == "$and":
|
|
114
|
+
conditions.append(AndOperation.parse(value))
|
|
115
|
+
elif key == "$or":
|
|
116
|
+
conditions.append(OrOperation.parse(value))
|
|
117
|
+
# Key needs to be a metadata field
|
|
118
|
+
else:
|
|
119
|
+
conditions.extend(ComparisonOperation.parse(key, value))
|
|
120
|
+
|
|
121
|
+
if cls == LogicalFilterClause:
|
|
122
|
+
if len(conditions) == 1:
|
|
123
|
+
return conditions[0]
|
|
124
|
+
else:
|
|
125
|
+
return AndOperation(conditions)
|
|
126
|
+
else:
|
|
127
|
+
return cls(conditions)
|
|
128
|
+
|
|
129
|
+
def convert_to_milvus(self):
|
|
130
|
+
"""
|
|
131
|
+
Converts the LogicalFilterClause instance to a Milvus filter.
|
|
132
|
+
"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def invert(self) -> Union["LogicalFilterClause", "ComparisonOperation"]:
|
|
137
|
+
"""
|
|
138
|
+
Inverts the LogicalOperation instance.
|
|
139
|
+
Necessary for Weaviate as Weaviate doesn't seem to support the 'Not' operator anymore.
|
|
140
|
+
(https://github.com/semi-technologies/weaviate/issues/1717)
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ComparisonOperation(ABC):
|
|
146
|
+
def __init__(
|
|
147
|
+
self, field_name: str, comparison_value: Union[str, int, float, bool, List]
|
|
148
|
+
):
|
|
149
|
+
self.field_name = field_name
|
|
150
|
+
self.comparison_value = comparison_value
|
|
151
|
+
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def evaluate(self, fields) -> bool:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def parse(
|
|
158
|
+
cls, field_name, comparison_clause: Union[Dict, List, str, float]
|
|
159
|
+
) -> List["ComparisonOperation"]:
|
|
160
|
+
comparison_operations: List[ComparisonOperation] = []
|
|
161
|
+
|
|
162
|
+
if isinstance(comparison_clause, dict):
|
|
163
|
+
for comparison_operation, comparison_value in comparison_clause.items():
|
|
164
|
+
if comparison_operation == "$eq":
|
|
165
|
+
comparison_operations.append(
|
|
166
|
+
EqOperation(field_name, comparison_value)
|
|
167
|
+
)
|
|
168
|
+
elif comparison_operation == "$in":
|
|
169
|
+
comparison_operations.append(
|
|
170
|
+
InOperation(field_name, comparison_value)
|
|
171
|
+
)
|
|
172
|
+
elif comparison_operation == "$ne":
|
|
173
|
+
comparison_operations.append(
|
|
174
|
+
NeOperation(field_name, comparison_value)
|
|
175
|
+
)
|
|
176
|
+
elif comparison_operation == "$nin":
|
|
177
|
+
comparison_operations.append(
|
|
178
|
+
NinOperation(field_name, comparison_value)
|
|
179
|
+
)
|
|
180
|
+
elif comparison_operation == "$gt":
|
|
181
|
+
comparison_operations.append(
|
|
182
|
+
GtOperation(field_name, comparison_value)
|
|
183
|
+
)
|
|
184
|
+
elif comparison_operation == "$gte":
|
|
185
|
+
comparison_operations.append(
|
|
186
|
+
GteOperation(field_name, comparison_value)
|
|
187
|
+
)
|
|
188
|
+
elif comparison_operation == "$lt":
|
|
189
|
+
comparison_operations.append(
|
|
190
|
+
LtOperation(field_name, comparison_value)
|
|
191
|
+
)
|
|
192
|
+
elif comparison_operation == "$lte":
|
|
193
|
+
comparison_operations.append(
|
|
194
|
+
LteOperation(field_name, comparison_value)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# No comparison operator is given, so we use the default operators "$in" if the comparison value is a list and
|
|
198
|
+
# "$eq" in every other case
|
|
199
|
+
elif isinstance(comparison_clause, list):
|
|
200
|
+
comparison_operations.append(InOperation(field_name, comparison_clause))
|
|
201
|
+
else:
|
|
202
|
+
comparison_operations.append((EqOperation(field_name, comparison_clause)))
|
|
203
|
+
|
|
204
|
+
return comparison_operations
|
|
205
|
+
|
|
206
|
+
def convert_to_milvus(self):
|
|
207
|
+
"""
|
|
208
|
+
Converts the ComparisonOperation instance to a Milvus comparison operator.
|
|
209
|
+
"""
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def invert(self) -> "ComparisonOperation":
|
|
214
|
+
"""
|
|
215
|
+
Inverts the ComparisonOperation.
|
|
216
|
+
Necessary for Weaviate as Weaviate doesn't seem to support the 'Not' operator anymore.
|
|
217
|
+
(https://github.com/semi-technologies/weaviate/issues/1717)
|
|
218
|
+
"""
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class NotOperation(LogicalFilterClause):
|
|
223
|
+
"""
|
|
224
|
+
Handles conversion of logical 'NOT' operations.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def evaluate(self, fields) -> bool:
|
|
228
|
+
return not any(condition.evaluate(fields) for condition in self.conditions)
|
|
229
|
+
|
|
230
|
+
def convert_to_milvus(self) -> str:
|
|
231
|
+
conditions = [
|
|
232
|
+
condition.invert().convert_to_milvus() for condition in self.conditions
|
|
233
|
+
]
|
|
234
|
+
if len(conditions) > 1:
|
|
235
|
+
# Conditions in self.conditions are by default combined with AND which becomes OR according to DeMorgan
|
|
236
|
+
return f"({' or '.join(conditions)})"
|
|
237
|
+
else:
|
|
238
|
+
return conditions[0]
|
|
239
|
+
|
|
240
|
+
def invert(self) -> Union[LogicalFilterClause, ComparisonOperation]:
|
|
241
|
+
# This method is called when a "$not" operation is embedded in another "$not" operation. Therefore, we don't
|
|
242
|
+
# invert the operations here, as two "$not" operation annihilate each other.
|
|
243
|
+
# (If we have more than one condition, we return an AndOperation, the default logical operation for combining
|
|
244
|
+
# multiple conditions.)
|
|
245
|
+
if len(self.conditions) > 1:
|
|
246
|
+
return AndOperation(self.conditions)
|
|
247
|
+
else:
|
|
248
|
+
return self.conditions[0]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class AndOperation(LogicalFilterClause):
|
|
252
|
+
"""
|
|
253
|
+
Handles conversion of logical 'AND' operations.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def evaluate(self, fields) -> bool:
|
|
257
|
+
return all(condition.evaluate(fields) for condition in self.conditions)
|
|
258
|
+
|
|
259
|
+
def convert_to_milvus(self) -> Dict[str, Union[str, List[Dict]]]:
|
|
260
|
+
conditions = [condition.convert_to_milvus() for condition in self.conditions]
|
|
261
|
+
return f"({' and '.join(conditions)})"
|
|
262
|
+
|
|
263
|
+
def invert(self) -> "OrOperation":
|
|
264
|
+
return OrOperation([condition.invert() for condition in self.conditions])
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class OrOperation(LogicalFilterClause):
|
|
268
|
+
"""
|
|
269
|
+
Handles conversion of logical 'OR' operations.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def evaluate(self, fields) -> bool:
|
|
273
|
+
return any(condition.evaluate(fields) for condition in self.conditions)
|
|
274
|
+
|
|
275
|
+
def convert_to_milvus(self) -> str:
|
|
276
|
+
conditions = [condition.convert_to_milvus() for condition in self.conditions]
|
|
277
|
+
return f"({' or '.join(conditions)})"
|
|
278
|
+
|
|
279
|
+
def invert(self) -> AndOperation:
|
|
280
|
+
return AndOperation([condition.invert() for condition in self.conditions])
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class EqOperation(ComparisonOperation):
|
|
284
|
+
"""
|
|
285
|
+
Handles conversion of the '$eq' comparison operation.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def evaluate(self, fields) -> bool:
|
|
289
|
+
if self.field_name not in fields:
|
|
290
|
+
return False
|
|
291
|
+
return fields[self.field_name] == self.comparison_value
|
|
292
|
+
|
|
293
|
+
def convert_to_milvus(self) -> str:
|
|
294
|
+
if isinstance(self.comparison_value, str):
|
|
295
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
296
|
+
else:
|
|
297
|
+
comp_val = self.comparison_value
|
|
298
|
+
return f"({self.field_name} == {comp_val})"
|
|
299
|
+
|
|
300
|
+
def invert(self) -> "NeOperation":
|
|
301
|
+
return NeOperation(self.field_name, self.comparison_value)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class InOperation(ComparisonOperation):
|
|
305
|
+
"""
|
|
306
|
+
Handles conversion of the '$in' comparison operation.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def evaluate(self, fields) -> bool:
|
|
310
|
+
if self.field_name not in fields:
|
|
311
|
+
return False
|
|
312
|
+
return fields[self.field_name] in self.comparison_value # type: ignore
|
|
313
|
+
# is only initialized with lists, but changing the type annotation would mean duplicating __init__
|
|
314
|
+
|
|
315
|
+
def convert_to_milvus(self) -> str:
|
|
316
|
+
if not isinstance(self.comparison_value, list):
|
|
317
|
+
raise FilterError("'$in' operation requires comparison value to be a list.")
|
|
318
|
+
comp_val = []
|
|
319
|
+
for x in self.comparison_value:
|
|
320
|
+
if isinstance(x, str):
|
|
321
|
+
comp_val.append('"' + str(x) + '"')
|
|
322
|
+
else:
|
|
323
|
+
comp_val.append(x)
|
|
324
|
+
|
|
325
|
+
return f"""({self.field_name} in [{','.join(comp_val)}])"""
|
|
326
|
+
|
|
327
|
+
def invert(self) -> "NinOperation":
|
|
328
|
+
return NinOperation(self.field_name, self.comparison_value)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class NeOperation(ComparisonOperation):
|
|
332
|
+
"""
|
|
333
|
+
Handles conversion of the '$ne' comparison operation.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
def evaluate(self, fields) -> bool:
|
|
337
|
+
if self.field_name not in fields:
|
|
338
|
+
return False
|
|
339
|
+
return fields[self.field_name] != self.comparison_value
|
|
340
|
+
|
|
341
|
+
def convert_to_milvus(self) -> str:
|
|
342
|
+
if isinstance(self.comparison_value, str):
|
|
343
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
344
|
+
else:
|
|
345
|
+
comp_val = self.comparison_value
|
|
346
|
+
return f"({self.field_name} != {comp_val})"
|
|
347
|
+
|
|
348
|
+
def invert(self) -> "EqOperation":
|
|
349
|
+
return EqOperation(self.field_name, self.comparison_value)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class NinOperation(ComparisonOperation):
|
|
353
|
+
"""
|
|
354
|
+
Handles conversion of the '$nin' comparison operation.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
def evaluate(self, fields) -> bool:
|
|
358
|
+
if self.field_name not in fields:
|
|
359
|
+
return False
|
|
360
|
+
return fields[self.field_name] not in self.comparison_value # type: ignore
|
|
361
|
+
# is only initialized with lists, but changing the type annotation would mean duplicating __init__
|
|
362
|
+
|
|
363
|
+
def convert_to_milvus(self) -> str:
|
|
364
|
+
if not isinstance(self.comparison_value, list):
|
|
365
|
+
raise FilterError(
|
|
366
|
+
"'$nin' operation requires comparison value to be a list."
|
|
367
|
+
)
|
|
368
|
+
comp_val = []
|
|
369
|
+
for x in self.comparison_value:
|
|
370
|
+
if isinstance(x, str):
|
|
371
|
+
comp_val.append('"' + str(x) + '"')
|
|
372
|
+
else:
|
|
373
|
+
comp_val.append(x)
|
|
374
|
+
|
|
375
|
+
return f"""({self.field_name} not in [{','.join(comp_val)}])"""
|
|
376
|
+
|
|
377
|
+
def invert(self) -> "InOperation":
|
|
378
|
+
return InOperation(self.field_name, self.comparison_value)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class GtOperation(ComparisonOperation):
|
|
382
|
+
"""
|
|
383
|
+
Handles conversion of the '$gt' comparison operation.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def evaluate(self, fields) -> bool:
|
|
387
|
+
if self.field_name not in fields:
|
|
388
|
+
return False
|
|
389
|
+
return fields[self.field_name] > self.comparison_value
|
|
390
|
+
|
|
391
|
+
def convert_to_milvus(self) -> str:
|
|
392
|
+
if not isinstance(self.comparison_value, (float, int, str)):
|
|
393
|
+
raise FilterError(
|
|
394
|
+
"Comparison value for '$gt' operation must be a float or int or str."
|
|
395
|
+
)
|
|
396
|
+
if isinstance(self.comparison_value, str):
|
|
397
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
398
|
+
else:
|
|
399
|
+
comp_val = self.comparison_value
|
|
400
|
+
return f"({self.field_name} > {comp_val})"
|
|
401
|
+
|
|
402
|
+
def invert(self) -> "LteOperation":
|
|
403
|
+
return LteOperation(self.field_name, self.comparison_value)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class GteOperation(ComparisonOperation):
|
|
407
|
+
"""
|
|
408
|
+
Handles conversion of the '$gte' comparison operation.
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
def evaluate(self, fields) -> bool:
|
|
412
|
+
if self.field_name not in fields:
|
|
413
|
+
return False
|
|
414
|
+
return fields[self.field_name] >= self.comparison_value
|
|
415
|
+
|
|
416
|
+
def convert_to_milvus(self) -> str:
|
|
417
|
+
if not isinstance(self.comparison_value, (float, int, str)):
|
|
418
|
+
raise FilterError(
|
|
419
|
+
"Comparison value for '$gte' operation must be a float or int or str."
|
|
420
|
+
)
|
|
421
|
+
if isinstance(self.comparison_value, str):
|
|
422
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
423
|
+
else:
|
|
424
|
+
comp_val = self.comparison_value
|
|
425
|
+
return f"({self.field_name} >= {comp_val})"
|
|
426
|
+
|
|
427
|
+
def invert(self) -> "LtOperation":
|
|
428
|
+
return LtOperation(self.field_name, self.comparison_value)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class LtOperation(ComparisonOperation):
|
|
432
|
+
"""
|
|
433
|
+
Handles conversion of the '$lt' comparison operation.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
def evaluate(self, fields) -> bool:
|
|
437
|
+
if self.field_name not in fields:
|
|
438
|
+
return False
|
|
439
|
+
return fields[self.field_name] < self.comparison_value
|
|
440
|
+
|
|
441
|
+
def convert_to_milvus(self) -> str:
|
|
442
|
+
if not isinstance(self.comparison_value, (float, int, str)):
|
|
443
|
+
raise FilterError(
|
|
444
|
+
"Comparison value for '$lt' operation must be a float or int or str."
|
|
445
|
+
)
|
|
446
|
+
if isinstance(self.comparison_value, str):
|
|
447
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
448
|
+
else:
|
|
449
|
+
comp_val = self.comparison_value
|
|
450
|
+
return f"({self.field_name} < {comp_val})"
|
|
451
|
+
|
|
452
|
+
def invert(self) -> "GteOperation":
|
|
453
|
+
return GteOperation(self.field_name, self.comparison_value)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class LteOperation(ComparisonOperation):
|
|
457
|
+
"""
|
|
458
|
+
Handles conversion of the '$lte' comparison operation.
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
def evaluate(self, fields) -> bool:
|
|
462
|
+
if self.field_name not in fields:
|
|
463
|
+
return False
|
|
464
|
+
return fields[self.field_name] <= self.comparison_value
|
|
465
|
+
|
|
466
|
+
def convert_to_milvus(self) -> str:
|
|
467
|
+
if not isinstance(self.comparison_value, (float, int, str)):
|
|
468
|
+
raise FilterError(
|
|
469
|
+
"Comparison value for '$lte' operation must be a float or int or str."
|
|
470
|
+
)
|
|
471
|
+
if isinstance(self.comparison_value, str):
|
|
472
|
+
comp_val = '"' + str(self.comparison_value) + '"'
|
|
473
|
+
else:
|
|
474
|
+
comp_val = self.comparison_value
|
|
475
|
+
return f"({self.field_name} <= {comp_val})"
|
|
476
|
+
|
|
477
|
+
def invert(self) -> "GtOperation":
|
|
478
|
+
return GtOperation(self.field_name, self.comparison_value)
|