aggify 0.2.8__tar.gz → 0.3.0__tar.gz
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.
- {aggify-0.2.8 → aggify-0.3.0}/PKG-INFO +2 -3
- {aggify-0.2.8 → aggify-0.3.0}/README.md +1 -2
- {aggify-0.2.8 → aggify-0.3.0}/aggify/aggify.py +92 -87
- {aggify-0.2.8 → aggify-0.3.0}/aggify/compiler.py +19 -16
- {aggify-0.2.8 → aggify-0.3.0}/aggify/exceptions.py +3 -1
- {aggify-0.2.8 → aggify-0.3.0}/aggify/types.py +1 -2
- {aggify-0.2.8 → aggify-0.3.0}/aggify/utilty.py +1 -13
- {aggify-0.2.8 → aggify-0.3.0}/pyproject.toml +1 -1
- {aggify-0.2.8 → aggify-0.3.0}/LICENSE +0 -0
- {aggify-0.2.8 → aggify-0.3.0}/aggify/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aggify
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A MongoDB aggregation generator for Mongoengine
|
|
5
5
|
Home-page: https://github.com/Aggify/aggify
|
|
6
6
|
Author: SeYeD.Dev
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
[](https://pypi.org/project/aggify)
|
|
28
28
|
[](https://pypi.org/project/aggify)
|
|
29
29
|
[](https://pypi.org/project/aggify)
|
|
30
|
-
[](https://
|
|
30
|
+
[](https://app.codecov.io/gh/Aggify/aggify)
|
|
31
31
|
[](https://github.com/Aggify/aggify/blob/main/LICENSE)
|
|
32
32
|
[](https://github.com/Aggify/aggify/graphs/contributors)
|
|
33
33
|
[](https://t.me/Aggify)
|
|
@@ -181,4 +181,3 @@ In the sample usage above, you can see how Aggify simplifies the construction of
|
|
|
181
181
|
allowing you to chain filters, lookups, and other operations to build complex queries.
|
|
182
182
|
For more details and examples, please refer to the documentation and codebase.
|
|
183
183
|
|
|
184
|
-
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://pypi.org/project/aggify)
|
|
9
9
|
[](https://pypi.org/project/aggify)
|
|
10
10
|
[](https://pypi.org/project/aggify)
|
|
11
|
-
[](https://
|
|
11
|
+
[](https://app.codecov.io/gh/Aggify/aggify)
|
|
12
12
|
[](https://github.com/Aggify/aggify/blob/main/LICENSE)
|
|
13
13
|
[](https://github.com/Aggify/aggify/graphs/contributors)
|
|
14
14
|
[](https://t.me/Aggify)
|
|
@@ -161,4 +161,3 @@ Mongoengine equivalent query:
|
|
|
161
161
|
In the sample usage above, you can see how Aggify simplifies the construction of MongoDB aggregation pipelines by
|
|
162
162
|
allowing you to chain filters, lookups, and other operations to build complex queries.
|
|
163
163
|
For more details and examples, please refer to the documentation and codebase.
|
|
164
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import functools
|
|
2
|
-
from typing import Any, Dict, Type, Union, List,
|
|
2
|
+
from typing import Any, Dict, Type, Union, List, TypeVar, Callable
|
|
3
3
|
|
|
4
|
-
from mongoengine import Document, EmbeddedDocument, fields
|
|
4
|
+
from mongoengine import Document, EmbeddedDocument, fields as mongoengine_fields
|
|
5
5
|
from mongoengine.base import TopLevelDocumentMetaclass
|
|
6
6
|
|
|
7
7
|
from aggify.compiler import F, Match, Q, Operators, Cond # noqa keep
|
|
@@ -85,25 +85,23 @@ class Aggify:
|
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
87
|
# Extract fields to keep and check if _id should be deleted
|
|
88
|
-
to_keep_values =
|
|
89
|
-
delete_id = kwargs.get("id")
|
|
88
|
+
to_keep_values = {"id"}
|
|
89
|
+
delete_id = kwargs.get("id") is not None
|
|
90
90
|
projection = {}
|
|
91
91
|
|
|
92
92
|
# Add missing fields to the base model
|
|
93
93
|
for key, value in kwargs.items():
|
|
94
94
|
if value == 1:
|
|
95
|
-
to_keep_values.
|
|
95
|
+
to_keep_values.add(key)
|
|
96
96
|
elif key not in self.base_model._fields and isinstance( # noqa
|
|
97
97
|
kwargs[key], (str, dict)
|
|
98
|
-
):
|
|
99
|
-
to_keep_values.
|
|
100
|
-
self.base_model._fields[key] =
|
|
98
|
+
):
|
|
99
|
+
to_keep_values.add(key)
|
|
100
|
+
self.base_model._fields[key] = mongoengine_fields.IntField() # noqa
|
|
101
101
|
projection[get_db_field(self.base_model, key)] = value # noqa
|
|
102
102
|
|
|
103
103
|
# Remove fields from the base model, except the ones in to_keep_values and possibly _id
|
|
104
|
-
keys_for_deletion =
|
|
105
|
-
to_keep_values
|
|
106
|
-
) # noqa
|
|
104
|
+
keys_for_deletion = self.base_model._fields.keys() - to_keep_values # noqa
|
|
107
105
|
if delete_id:
|
|
108
106
|
keys_for_deletion.add("id")
|
|
109
107
|
for key in keys_for_deletion:
|
|
@@ -128,12 +126,12 @@ class Aggify:
|
|
|
128
126
|
return self
|
|
129
127
|
|
|
130
128
|
@last_out_stage_check
|
|
131
|
-
def order_by(self, *
|
|
129
|
+
def order_by(self, *order_fields: Union[str, List[str]]) -> "Aggify":
|
|
132
130
|
sort_dict = {
|
|
133
131
|
get_db_field(self.base_model, field.replace("-", "")): -1
|
|
134
132
|
if field.startswith("-")
|
|
135
|
-
else 1
|
|
136
|
-
for field in
|
|
133
|
+
else 1
|
|
134
|
+
for field in order_fields
|
|
137
135
|
}
|
|
138
136
|
self.pipelines.append({"$sort": sort_dict})
|
|
139
137
|
return self
|
|
@@ -144,19 +142,18 @@ class Aggify:
|
|
|
144
142
|
return self
|
|
145
143
|
|
|
146
144
|
@last_out_stage_check
|
|
147
|
-
def add_fields(self, **
|
|
148
|
-
"""
|
|
149
|
-
Generates a MongoDB addFields pipeline stage.
|
|
145
|
+
def add_fields(self, **fields) -> "Aggify": # noqa
|
|
146
|
+
"""Generates a MongoDB addFields pipeline stage.
|
|
150
147
|
|
|
151
148
|
Args:
|
|
152
|
-
|
|
149
|
+
fields: A dictionary of field expressions and values.
|
|
153
150
|
|
|
154
151
|
Returns:
|
|
155
152
|
A MongoDB add_fields pipeline stage.
|
|
156
153
|
"""
|
|
157
154
|
add_fields_stage = {"$addFields": {}}
|
|
158
155
|
|
|
159
|
-
for field, expression in
|
|
156
|
+
for field, expression in fields.items():
|
|
160
157
|
field = field.replace("__", ".")
|
|
161
158
|
if isinstance(expression, str):
|
|
162
159
|
add_fields_stage["$addFields"][field] = {"$literal": expression}
|
|
@@ -169,7 +166,9 @@ class Aggify:
|
|
|
169
166
|
else:
|
|
170
167
|
raise AggifyValueError([str, F, list], type(expression))
|
|
171
168
|
# TODO: Should be checked if new field is embedded, create embedded field.
|
|
172
|
-
self.base_model._fields[
|
|
169
|
+
self.base_model._fields[ # noqa
|
|
170
|
+
field.replace("$", "")
|
|
171
|
+
] = mongoengine_fields.IntField()
|
|
173
172
|
|
|
174
173
|
self.pipelines.append(add_fields_stage)
|
|
175
174
|
return self
|
|
@@ -238,12 +237,8 @@ class Aggify:
|
|
|
238
237
|
"""
|
|
239
238
|
Builds the pipelines list based on the query parameters.
|
|
240
239
|
"""
|
|
241
|
-
skip_list = []
|
|
242
240
|
|
|
243
241
|
for key, value in query.items():
|
|
244
|
-
if key in skip_list:
|
|
245
|
-
continue
|
|
246
|
-
|
|
247
242
|
# Split the key to access the field information.
|
|
248
243
|
split_query = key.split("__")
|
|
249
244
|
|
|
@@ -257,8 +252,8 @@ class Aggify:
|
|
|
257
252
|
or "document_type_obj"
|
|
258
253
|
not in join_field.__dict__ # Check whether this field is a join field or not.
|
|
259
254
|
or issubclass(
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
join_field.document_type, EmbeddedDocument # noqa
|
|
256
|
+
) # Check whether this field is embedded field or not
|
|
262
257
|
or len(split_query) == 1
|
|
263
258
|
or (len(split_query) == 2 and split_query[1] in Operators.ALL_OPERATORS)
|
|
264
259
|
):
|
|
@@ -276,7 +271,6 @@ class Aggify:
|
|
|
276
271
|
matches = []
|
|
277
272
|
for k, v in query.items():
|
|
278
273
|
if k.split("__")[0] == split_query[0]:
|
|
279
|
-
skip_list.append(k)
|
|
280
274
|
_key = k.replace("__", ".", 1)
|
|
281
275
|
match = self.__match({_key: v}).get("$match")
|
|
282
276
|
if match != {}:
|
|
@@ -309,7 +303,7 @@ class Aggify:
|
|
|
309
303
|
def unwind(
|
|
310
304
|
self,
|
|
311
305
|
path: str,
|
|
312
|
-
|
|
306
|
+
include_array_index: Union[str, None] = None,
|
|
313
307
|
preserve: bool = False,
|
|
314
308
|
) -> "Aggify":
|
|
315
309
|
"""Generates a MongoDB unwind pipeline stage.
|
|
@@ -319,7 +313,7 @@ class Aggify:
|
|
|
319
313
|
To specify a field path, prefix the field name with a dollar sign $
|
|
320
314
|
and enclose in quotes.
|
|
321
315
|
|
|
322
|
-
|
|
316
|
+
include_array_index: The name of a new field to hold the array index of the element.
|
|
323
317
|
The name cannot start with a dollar sign $.
|
|
324
318
|
|
|
325
319
|
preserve: Whether to preserve null and empty arrays.
|
|
@@ -344,29 +338,19 @@ class Aggify:
|
|
|
344
338
|
docs: https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/
|
|
345
339
|
"""
|
|
346
340
|
path = self.get_field_name_recursively(path)
|
|
347
|
-
if
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
"$unwind"
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
341
|
+
if include_array_index is None and preserve is False:
|
|
342
|
+
unwind_stage = {"$unwind": f"${path}"}
|
|
343
|
+
else:
|
|
344
|
+
unwind_stage = {"$unwind": {"path": f"${path}"}}
|
|
345
|
+
if preserve:
|
|
346
|
+
unwind_stage["$unwind"]["preserveNullAndEmptyArrays"] = preserve
|
|
347
|
+
if include_array_index:
|
|
348
|
+
unwind_stage["$unwind"][
|
|
349
|
+
"includeArrayIndex"
|
|
350
|
+
] = include_array_index.replace("$", "")
|
|
351
|
+
self.pipelines.append(unwind_stage)
|
|
359
352
|
return self
|
|
360
353
|
|
|
361
|
-
def aggregate(self):
|
|
362
|
-
"""
|
|
363
|
-
Returns the aggregated results.
|
|
364
|
-
|
|
365
|
-
Returns:
|
|
366
|
-
The aggregated results.
|
|
367
|
-
"""
|
|
368
|
-
return self.base_model.objects.aggregate(*self.pipelines) # type: ignore
|
|
369
|
-
|
|
370
354
|
def annotate(
|
|
371
355
|
self, annotate_name: str, accumulator: str, f: Union[Union[str, Dict], F, int]
|
|
372
356
|
) -> "Aggify":
|
|
@@ -390,31 +374,49 @@ class Aggify:
|
|
|
390
374
|
"""
|
|
391
375
|
|
|
392
376
|
# Some of the accumulator fields might be false and should be checked.
|
|
377
|
+
# noinspection SpellCheckingInspection
|
|
393
378
|
aggregation_mapping: Dict[str, Type] = {
|
|
394
|
-
"sum": (
|
|
395
|
-
"avg": (
|
|
396
|
-
"stdDevPop": (
|
|
397
|
-
"stdDevSamp": (
|
|
398
|
-
"push": (
|
|
399
|
-
"addToSet": (
|
|
400
|
-
"count": (
|
|
401
|
-
"first": (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
"
|
|
408
|
-
|
|
379
|
+
"sum": (mongoengine_fields.FloatField(), "$sum"),
|
|
380
|
+
"avg": (mongoengine_fields.FloatField(), "$avg"),
|
|
381
|
+
"stdDevPop": (mongoengine_fields.FloatField(), "$stdDevPop"),
|
|
382
|
+
"stdDevSamp": (mongoengine_fields.FloatField(), "$stdDevSamp"),
|
|
383
|
+
"push": (mongoengine_fields.ListField(), "$push"),
|
|
384
|
+
"addToSet": (mongoengine_fields.ListField(), "$addToSet"),
|
|
385
|
+
"count": (mongoengine_fields.IntField(), "$count"),
|
|
386
|
+
"first": (
|
|
387
|
+
mongoengine_fields.EmbeddedDocumentField(
|
|
388
|
+
mongoengine_fields.EmbeddedDocument
|
|
389
|
+
),
|
|
390
|
+
"$first",
|
|
391
|
+
),
|
|
392
|
+
"last": (
|
|
393
|
+
mongoengine_fields.EmbeddedDocumentField(
|
|
394
|
+
mongoengine_fields.EmbeddedDocument
|
|
395
|
+
),
|
|
396
|
+
"$last",
|
|
397
|
+
),
|
|
398
|
+
"max": (mongoengine_fields.DynamicField(), "$max"),
|
|
399
|
+
"accumulator": (mongoengine_fields.DynamicField(), "$accumulator"),
|
|
400
|
+
"min": (mongoengine_fields.DynamicField(), "$min"),
|
|
401
|
+
"median": (mongoengine_fields.DynamicField(), "$median"),
|
|
402
|
+
"mergeObjects": (mongoengine_fields.DictField(), "$mergeObjects"),
|
|
403
|
+
"top": (
|
|
404
|
+
mongoengine_fields.EmbeddedDocumentField(
|
|
405
|
+
mongoengine_fields.EmbeddedDocument
|
|
406
|
+
),
|
|
407
|
+
"$top",
|
|
408
|
+
),
|
|
409
409
|
"bottom": (
|
|
410
|
-
|
|
410
|
+
mongoengine_fields.EmbeddedDocumentField(
|
|
411
|
+
mongoengine_fields.EmbeddedDocument
|
|
412
|
+
),
|
|
411
413
|
"$bottom",
|
|
412
414
|
),
|
|
413
|
-
"topN": (
|
|
414
|
-
"bottomN": (
|
|
415
|
-
"firstN": (
|
|
416
|
-
"lastN": (
|
|
417
|
-
"maxN": (
|
|
415
|
+
"topN": (mongoengine_fields.ListField(), "$topN"),
|
|
416
|
+
"bottomN": (mongoengine_fields.ListField(), "$bottomN"),
|
|
417
|
+
"firstN": (mongoengine_fields.ListField(), "$firstN"),
|
|
418
|
+
"lastN": (mongoengine_fields.ListField(), "$lastN"),
|
|
419
|
+
"maxN": (mongoengine_fields.ListField(), "$maxN"),
|
|
418
420
|
}
|
|
419
421
|
|
|
420
422
|
try:
|
|
@@ -553,18 +555,23 @@ class Aggify:
|
|
|
553
555
|
foreign_field: Union[str, None] = None,
|
|
554
556
|
) -> "Aggify":
|
|
555
557
|
"""
|
|
556
|
-
|
|
558
|
+
Generates a MongoDB lookup pipeline stage.
|
|
557
559
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
560
|
+
Args:
|
|
561
|
+
from_collection (Document): The document representing the collection to perform the lookup on.
|
|
562
|
+
as_name (str): The name of the new field to create.
|
|
563
|
+
query (list[Q] | Union[Q, None], optional): List of desired queries with Q function or a single query.
|
|
564
|
+
let (Union[List[str], None], optional): The local field(s) to join on. If provided,
|
|
565
|
+
localField and foreignField are not used.
|
|
566
|
+
local_field (Union[str, None], optional): The local field to join on when `let` is not provided.
|
|
567
|
+
foreign_field (Union[str, None], optional): The foreign field to join on when `let` is not provided.
|
|
568
|
+
let (Union[List[str], None], optional): The local field(s) to join on. If provided,
|
|
569
|
+
localField and foreignField are not used.
|
|
570
|
+
local_field (Union[str, None], optional): The local field to join on when let not provided.
|
|
571
|
+
foreign_field (Union[str, None], optional): The foreign field to join on when let not provided.
|
|
565
572
|
|
|
566
|
-
|
|
567
|
-
|
|
573
|
+
Returns:
|
|
574
|
+
Aggify: An instance of the Aggify class representing a MongoDB lookup pipeline stage.
|
|
568
575
|
"""
|
|
569
576
|
|
|
570
577
|
lookup_stages = []
|
|
@@ -576,8 +583,6 @@ class Aggify:
|
|
|
576
583
|
expected_list=[["local_field", "foreign_field"], "let"]
|
|
577
584
|
)
|
|
578
585
|
elif not let:
|
|
579
|
-
if not (local_field and foreign_field):
|
|
580
|
-
raise InvalidArgument(expected_list=["local_field", "foreign_field"])
|
|
581
586
|
lookup_stage = {
|
|
582
587
|
"$lookup": {
|
|
583
588
|
"from": from_collection_name,
|
|
@@ -632,7 +637,7 @@ class Aggify:
|
|
|
632
637
|
return self
|
|
633
638
|
|
|
634
639
|
@staticmethod
|
|
635
|
-
def get_model_field(model:
|
|
640
|
+
def get_model_field(model: Type[Document], field: str) -> mongoengine_fields:
|
|
636
641
|
"""
|
|
637
642
|
Get the field definition of a specified field in a MongoDB model.
|
|
638
643
|
|
|
@@ -757,15 +762,15 @@ class Aggify:
|
|
|
757
762
|
# List of valid redaction values
|
|
758
763
|
redact_values = ["DESCEND", "PRUNE", "KEEP"]
|
|
759
764
|
|
|
760
|
-
def clean_then_else(
|
|
765
|
+
def clean_then_else(_then_value, _else_value):
|
|
761
766
|
"""
|
|
762
767
|
Helper function to sanitize then_value and else_value.
|
|
763
768
|
|
|
764
769
|
Strips the dollar sign and converts values to uppercase.
|
|
765
770
|
"""
|
|
766
771
|
return (
|
|
767
|
-
|
|
768
|
-
|
|
772
|
+
_then_value.replace("$", "").upper(),
|
|
773
|
+
_else_value.replace("$", "").upper(),
|
|
769
774
|
)
|
|
770
775
|
|
|
771
776
|
# Clean the provided then_value and else_value
|
|
@@ -8,15 +8,16 @@ from aggify.utilty import get_db_field
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Operators:
|
|
11
|
+
# noinspection SpellCheckingInspection
|
|
11
12
|
QUERY_OPERATORS = {
|
|
12
13
|
"exact": "$eq",
|
|
13
14
|
"iexact": "$regex",
|
|
14
15
|
"contains": "$regex",
|
|
15
|
-
"icontains": "$regex",
|
|
16
|
+
"icontains": "$regex",
|
|
16
17
|
"startswith": "$regex",
|
|
17
|
-
"istartswith": "$regex",
|
|
18
|
+
"istartswith": "$regex",
|
|
18
19
|
"endswith": "$regex",
|
|
19
|
-
"iendswith": "$regex",
|
|
20
|
+
"iendswith": "$regex",
|
|
20
21
|
"in": "$in",
|
|
21
22
|
"nin": "$nin",
|
|
22
23
|
"ne": "$ne",
|
|
@@ -35,6 +36,7 @@ class Operators:
|
|
|
35
36
|
**COMPARISON_OPERATORS,
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
# noinspection SpellCheckingInspection
|
|
38
40
|
REGEX_PATTERNS = {
|
|
39
41
|
"iexact": "^{value}$",
|
|
40
42
|
"contains": "{value}",
|
|
@@ -45,6 +47,7 @@ class Operators:
|
|
|
45
47
|
"iendswith": "{value}$",
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
# noinspection SpellCheckingInspection
|
|
48
51
|
REGEX_OPTIONS = {
|
|
49
52
|
"iexact": "i",
|
|
50
53
|
"icontains": "i",
|
|
@@ -79,11 +82,6 @@ class Operators:
|
|
|
79
82
|
}
|
|
80
83
|
else:
|
|
81
84
|
self.match_query[field] = {Operators.ALL_OPERATORS[operator]: value}
|
|
82
|
-
else:
|
|
83
|
-
# Default behavior
|
|
84
|
-
self.match_query[field] = {
|
|
85
|
-
Operators.ALL_OPERATORS.get(operator, operator): value
|
|
86
|
-
}
|
|
87
85
|
|
|
88
86
|
return self.match_query
|
|
89
87
|
|
|
@@ -104,7 +102,7 @@ class Q:
|
|
|
104
102
|
yield "$match", self.conditions
|
|
105
103
|
|
|
106
104
|
def __or__(self, other):
|
|
107
|
-
if self.conditions.get("$or"
|
|
105
|
+
if self.conditions.get("$or"):
|
|
108
106
|
self.conditions["$or"].append(dict(other)["$match"])
|
|
109
107
|
combined_conditions = self.conditions
|
|
110
108
|
|
|
@@ -113,7 +111,7 @@ class Q:
|
|
|
113
111
|
return Q(**combined_conditions)
|
|
114
112
|
|
|
115
113
|
def __and__(self, other):
|
|
116
|
-
if self.conditions.get("$and"
|
|
114
|
+
if self.conditions.get("$and"):
|
|
117
115
|
self.conditions["$and"].append(dict(other)["$match"])
|
|
118
116
|
combined_conditions = self.conditions
|
|
119
117
|
else:
|
|
@@ -135,7 +133,7 @@ class F:
|
|
|
135
133
|
def to_dict(self):
|
|
136
134
|
return self.field
|
|
137
135
|
|
|
138
|
-
def __add__(self, other):
|
|
136
|
+
def __add__(self, other):
|
|
139
137
|
if isinstance(other, F):
|
|
140
138
|
other = other.field
|
|
141
139
|
|
|
@@ -245,7 +243,12 @@ class Match:
|
|
|
245
243
|
|
|
246
244
|
@staticmethod
|
|
247
245
|
def validate_operator(key: str):
|
|
248
|
-
|
|
246
|
+
_op = key.rsplit("__", 1)
|
|
247
|
+
try:
|
|
248
|
+
operator = _op[1]
|
|
249
|
+
except IndexError:
|
|
250
|
+
raise InvalidOperator(str(_op)) from None
|
|
251
|
+
|
|
249
252
|
if operator not in Operators.COMPARISON_OPERATORS:
|
|
250
253
|
raise InvalidOperator(operator)
|
|
251
254
|
|
|
@@ -272,15 +275,15 @@ class Match:
|
|
|
272
275
|
def compile(self, pipelines: list) -> Dict[str, Dict[str, list]]:
|
|
273
276
|
match_query = {}
|
|
274
277
|
for key, value in self.matches.items():
|
|
278
|
+
if isinstance(value, F):
|
|
279
|
+
if F.is_suitable_for_match(key) is False:
|
|
280
|
+
raise InvalidOperator(key)
|
|
281
|
+
|
|
275
282
|
if "__" not in key:
|
|
276
283
|
key = get_db_field(self.base_model, key)
|
|
277
284
|
match_query[key] = value
|
|
278
285
|
continue
|
|
279
286
|
|
|
280
|
-
if isinstance(value, F):
|
|
281
|
-
if F.is_suitable_for_match(key) is False:
|
|
282
|
-
raise InvalidOperator(key)
|
|
283
|
-
|
|
284
287
|
field, operator, *_ = key.split("__")
|
|
285
288
|
if (
|
|
286
289
|
self.is_base_model_field(field)
|
|
@@ -28,7 +28,9 @@ class AnnotationError(InvalidPipelineStageError):
|
|
|
28
28
|
|
|
29
29
|
class OutStageError(InvalidPipelineStageError):
|
|
30
30
|
def __init__(self, stage):
|
|
31
|
-
self.message =
|
|
31
|
+
self.message = (
|
|
32
|
+
f"You cannot add a {self!r} pipeline after $out stage! stage : {stage}"
|
|
33
|
+
)
|
|
32
34
|
super().__init__(self.message)
|
|
33
35
|
|
|
34
36
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from typing import Union, Dict, TypeVar, Callable
|
|
2
|
-
from mongoengine import Document
|
|
3
2
|
from bson import ObjectId
|
|
3
|
+
from mongoengine import Document
|
|
4
4
|
|
|
5
5
|
QueryParams = Union[int, None, str, bool, float, Dict, ObjectId]
|
|
6
|
-
|
|
7
6
|
CollectionType = TypeVar("CollectionType", bound=Callable[..., Document])
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
from typing import Any, Union, List, Dict
|
|
2
2
|
|
|
3
3
|
from mongoengine import Document
|
|
4
|
-
|
|
5
|
-
from aggify.exceptions import MongoIndexError, InvalidField, AlreadyExistsField
|
|
6
4
|
from aggify.types import CollectionType
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def int_to_slice(final_index: int) -> slice:
|
|
10
|
-
"""
|
|
11
|
-
Converts an integer to a slice, assuming that the start index is 0.
|
|
12
|
-
|
|
13
|
-
Examples:
|
|
14
|
-
>>> int_to_slice(3)
|
|
15
|
-
slice(0, 2)
|
|
16
|
-
"""
|
|
17
|
-
return slice(0, final_index)
|
|
5
|
+
from aggify.exceptions import MongoIndexError, InvalidField, AlreadyExistsField
|
|
18
6
|
|
|
19
7
|
|
|
20
8
|
def to_mongo_positive_index(index: Union[int, slice]) -> slice:
|
|
File without changes
|
|
File without changes
|