aggify 0.2.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aggify
3
- Version: 0.2.9
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
@@ -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
-
@@ -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, Callable, TypeVar
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 = ["id"]
89
- delete_id = kwargs.get("id") == 0
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.append(key)
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
- ): # noqa
99
- to_keep_values.append(key)
100
- self.base_model._fields[key] = fields.IntField() # noqa
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 = set(self.base_model._fields.keys()) - set( # noqa
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:
@@ -144,19 +142,18 @@ class Aggify:
144
142
  return self
145
143
 
146
144
  @last_out_stage_check
147
- def add_fields(self, **_fields) -> "Aggify": # noqa
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
- _fields: A dictionary of field expressions and values.
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 _fields.items():
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[field.replace("$", "")] = fields.IntField() # noqa
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
- join_field.document_type, EmbeddedDocument # noqa
261
- ) # Check whether this field is embedded field or not
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 != {}:
@@ -357,15 +351,6 @@ class Aggify:
357
351
  self.pipelines.append(unwind_stage)
358
352
  return self
359
353
 
360
- def aggregate(self):
361
- """
362
- Returns the aggregated results.
363
-
364
- Returns:
365
- The aggregated results.
366
- """
367
- return self.base_model.objects.aggregate(*self.pipelines) # type: ignore
368
-
369
354
  def annotate(
370
355
  self, annotate_name: str, accumulator: str, f: Union[Union[str, Dict], F, int]
371
356
  ) -> "Aggify":
@@ -389,31 +374,49 @@ class Aggify:
389
374
  """
390
375
 
391
376
  # Some of the accumulator fields might be false and should be checked.
377
+ # noinspection SpellCheckingInspection
392
378
  aggregation_mapping: Dict[str, Type] = {
393
- "sum": (fields.FloatField(), "$sum"),
394
- "avg": (fields.FloatField(), "$avg"),
395
- "stdDevPop": (fields.FloatField(), "$stdDevPop"),
396
- "stdDevSamp": (fields.FloatField(), "$stdDevSamp"), # noqa
397
- "push": (fields.ListField(), "$push"),
398
- "addToSet": (fields.ListField(), "$addToSet"),
399
- "count": (fields.IntField(), "$count"),
400
- "first": (fields.EmbeddedDocumentField(fields.EmbeddedDocument), "$first"),
401
- "last": (fields.EmbeddedDocumentField(fields.EmbeddedDocument), "$last"),
402
- "max": (fields.DynamicField(), "$max"),
403
- "accumulator": (fields.DynamicField(), "$accumulator"),
404
- "min": (fields.DynamicField(), "$min"),
405
- "median": (fields.DynamicField(), "$median"),
406
- "mergeObjects": (fields.DictField(), "$mergeObjects"),
407
- "top": (fields.EmbeddedDocumentField(fields.EmbeddedDocument), "$top"),
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
+ ),
408
409
  "bottom": (
409
- fields.EmbeddedDocumentField(fields.EmbeddedDocument),
410
+ mongoengine_fields.EmbeddedDocumentField(
411
+ mongoengine_fields.EmbeddedDocument
412
+ ),
410
413
  "$bottom",
411
414
  ),
412
- "topN": (fields.ListField(), "$topN"),
413
- "bottomN": (fields.ListField(), "$bottomN"),
414
- "firstN": (fields.ListField(), "$firstN"),
415
- "lastN": (fields.ListField(), "$lastN"),
416
- "maxN": (fields.ListField(), "$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"),
417
420
  }
418
421
 
419
422
  try:
@@ -552,19 +555,23 @@ class Aggify:
552
555
  foreign_field: Union[str, None] = None,
553
556
  ) -> "Aggify":
554
557
  """
555
- Generates a MongoDB lookup pipeline stage.
558
+ Generates a MongoDB lookup pipeline stage.
556
559
 
557
- Args:
558
- from_collection (Document): The document representing the collection to perform the lookup on.
559
- as_name (str): The name of the new field to create.
560
- query (list[Q] | Union[Q, None], optional): List of desired queries with Q function or a single query.
561
- let (Union[List[str], None], optional): The local field(s) to join on. If provided,
562
- localField and foreignField are not used.
563
- local_field (Union[str, None], optional): The local field to join on when let not provided.
564
- foreign_field (Union[str, None], optional): The foreign field to join on when let not provided.
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
- Returns:
567
- Aggify: An instance of the Aggify class representing a MongoDB lookup pipeline stage.
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: CollectionType, field: str) -> fields:
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
 
@@ -82,11 +82,6 @@ class Operators:
82
82
  }
83
83
  else:
84
84
  self.match_query[field] = {Operators.ALL_OPERATORS[operator]: value}
85
- else:
86
- # Default behavior
87
- self.match_query[field] = {
88
- Operators.ALL_OPERATORS.get(operator, operator): value
89
- }
90
85
 
91
86
  return self.match_query
92
87
 
@@ -107,7 +102,7 @@ class Q:
107
102
  yield "$match", self.conditions
108
103
 
109
104
  def __or__(self, other):
110
- if self.conditions.get("$or", None):
105
+ if self.conditions.get("$or"):
111
106
  self.conditions["$or"].append(dict(other)["$match"])
112
107
  combined_conditions = self.conditions
113
108
 
@@ -116,7 +111,7 @@ class Q:
116
111
  return Q(**combined_conditions)
117
112
 
118
113
  def __and__(self, other):
119
- if self.conditions.get("$and", None):
114
+ if self.conditions.get("$and"):
120
115
  self.conditions["$and"].append(dict(other)["$match"])
121
116
  combined_conditions = self.conditions
122
117
  else:
@@ -138,7 +133,7 @@ class F:
138
133
  def to_dict(self):
139
134
  return self.field
140
135
 
141
- def __add__(self, other): # TODO: add type for 'other'
136
+ def __add__(self, other):
142
137
  if isinstance(other, F):
143
138
  other = other.field
144
139
 
@@ -248,7 +243,12 @@ class Match:
248
243
 
249
244
  @staticmethod
250
245
  def validate_operator(key: str):
251
- operator = key.rsplit("__", 1)[1]
246
+ _op = key.rsplit("__", 1)
247
+ try:
248
+ operator = _op[1]
249
+ except IndexError:
250
+ raise InvalidOperator(str(_op)) from None
251
+
252
252
  if operator not in Operators.COMPARISON_OPERATORS:
253
253
  raise InvalidOperator(operator)
254
254
 
@@ -275,15 +275,15 @@ class Match:
275
275
  def compile(self, pipelines: list) -> Dict[str, Dict[str, list]]:
276
276
  match_query = {}
277
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
+
278
282
  if "__" not in key:
279
283
  key = get_db_field(self.base_model, key)
280
284
  match_query[key] = value
281
285
  continue
282
286
 
283
- if isinstance(value, F):
284
- if F.is_suitable_for_match(key) is False:
285
- raise InvalidOperator(key)
286
-
287
287
  field, operator, *_ = key.split("__")
288
288
  if (
289
289
  self.is_base_model_field(field)
@@ -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:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aggify"
3
- version = "0.2.9"
3
+ version = "0.3.0"
4
4
  authors = ["SeYeD.Dev <me@seyed.dev>"]
5
5
  repository = "https://github.com/Aggify/aggify"
6
6
  documentation = "https://github.com/Aggify/aggify/docs"
File without changes
File without changes
File without changes