mongo-aggro 0.1.0__py3-none-any.whl → 0.2.2__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.
Files changed (53) hide show
  1. mongo_aggro/__init__.py +400 -0
  2. mongo_aggro/accumulators.py +30 -12
  3. mongo_aggro/base.py +49 -9
  4. mongo_aggro/expressions/__init__.py +396 -0
  5. mongo_aggro/expressions/arithmetic.py +329 -0
  6. mongo_aggro/expressions/array.py +425 -0
  7. mongo_aggro/expressions/base.py +180 -0
  8. mongo_aggro/expressions/bitwise.py +84 -0
  9. mongo_aggro/expressions/comparison.py +161 -0
  10. mongo_aggro/expressions/conditional.py +117 -0
  11. mongo_aggro/expressions/date.py +665 -0
  12. mongo_aggro/expressions/encrypted.py +116 -0
  13. mongo_aggro/expressions/logical.py +72 -0
  14. mongo_aggro/expressions/object.py +122 -0
  15. mongo_aggro/expressions/set.py +150 -0
  16. mongo_aggro/expressions/size.py +48 -0
  17. mongo_aggro/expressions/string.py +365 -0
  18. mongo_aggro/expressions/trigonometry.py +283 -0
  19. mongo_aggro/expressions/type.py +205 -0
  20. mongo_aggro/expressions/variable.py +73 -0
  21. mongo_aggro/expressions/window.py +327 -0
  22. mongo_aggro/operators/__init__.py +65 -0
  23. mongo_aggro/operators/array.py +41 -0
  24. mongo_aggro/operators/base.py +15 -0
  25. mongo_aggro/operators/bitwise.py +81 -0
  26. mongo_aggro/operators/comparison.py +82 -0
  27. mongo_aggro/operators/element.py +32 -0
  28. mongo_aggro/operators/geo.py +171 -0
  29. mongo_aggro/operators/logical.py +111 -0
  30. mongo_aggro/operators/misc.py +102 -0
  31. mongo_aggro/operators/regex.py +25 -0
  32. mongo_aggro/stages/__init__.py +110 -0
  33. mongo_aggro/stages/array.py +69 -0
  34. mongo_aggro/stages/change.py +109 -0
  35. mongo_aggro/stages/core.py +170 -0
  36. mongo_aggro/stages/geo.py +93 -0
  37. mongo_aggro/stages/group.py +154 -0
  38. mongo_aggro/stages/join.py +221 -0
  39. mongo_aggro/stages/misc.py +45 -0
  40. mongo_aggro/stages/output.py +136 -0
  41. mongo_aggro/stages/search.py +315 -0
  42. mongo_aggro/stages/session.py +111 -0
  43. mongo_aggro/stages/stats.py +152 -0
  44. mongo_aggro/stages/transform.py +136 -0
  45. mongo_aggro/stages/window.py +139 -0
  46. mongo_aggro-0.2.2.dist-info/METADATA +193 -0
  47. mongo_aggro-0.2.2.dist-info/RECORD +49 -0
  48. {mongo_aggro-0.1.0.dist-info → mongo_aggro-0.2.2.dist-info}/WHEEL +1 -1
  49. mongo_aggro/operators.py +0 -247
  50. mongo_aggro/stages.py +0 -990
  51. mongo_aggro-0.1.0.dist-info/METADATA +0 -537
  52. mongo_aggro-0.1.0.dist-info/RECORD +0 -9
  53. {mongo_aggro-0.1.0.dist-info → mongo_aggro-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,221 @@
1
+ """Join-related MongoDB aggregation pipeline stages.
2
+
3
+ This module contains stages for joining and combining data from multiple
4
+ collections: Lookup, UnionWith, and GraphLookup.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ from mongo_aggro.base import Pipeline
12
+
13
+
14
+ class Lookup(BaseModel):
15
+ """
16
+ $lookup stage - performs a left outer join.
17
+
18
+ Example:
19
+ >>> # Simple lookup
20
+ >>> Lookup(
21
+ ... from_collection="products",
22
+ ... local_field="product_id",
23
+ ... foreign_field="_id",
24
+ ... as_field="product"
25
+ ... ).model_dump()
26
+ {"$lookup": {
27
+ "from": "products",
28
+ "localField": "product_id",
29
+ "foreignField": "_id",
30
+ "as": "product"
31
+ }}
32
+
33
+ >>> # With pipeline
34
+ >>> Lookup(
35
+ ... from_collection="orders",
36
+ ... let={"customerId": "$_id"},
37
+ ... pipeline=Pipeline([Match(query={"status": "active"})]),
38
+ ... as_field="orders"
39
+ ... ).model_dump()
40
+ """
41
+
42
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
43
+
44
+ from_collection: str = Field(
45
+ ...,
46
+ validation_alias="from",
47
+ serialization_alias="from",
48
+ description="Foreign collection name",
49
+ )
50
+ local_field: str | None = Field(
51
+ default=None,
52
+ validation_alias="localField",
53
+ serialization_alias="localField",
54
+ description="Local field for join",
55
+ )
56
+ foreign_field: str | None = Field(
57
+ default=None,
58
+ validation_alias="foreignField",
59
+ serialization_alias="foreignField",
60
+ description="Foreign field for join",
61
+ )
62
+ let: dict[str, Any] | None = Field(
63
+ default=None, description="Variables for pipeline"
64
+ )
65
+ pipeline: Pipeline | list[dict[str, Any]] | None = Field(
66
+ default=None, description="Sub-pipeline for complex joins"
67
+ )
68
+ as_field: str = Field(
69
+ ...,
70
+ validation_alias="as",
71
+ serialization_alias="as",
72
+ description="Output array field name",
73
+ )
74
+
75
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
76
+ result: dict[str, Any] = {
77
+ "from": self.from_collection,
78
+ "as": self.as_field,
79
+ }
80
+
81
+ if self.local_field is not None:
82
+ result["localField"] = self.local_field
83
+ if self.foreign_field is not None:
84
+ result["foreignField"] = self.foreign_field
85
+ if self.let is not None:
86
+ result["let"] = self.let
87
+ if self.pipeline is not None:
88
+ if isinstance(self.pipeline, Pipeline):
89
+ result["pipeline"] = self.pipeline.to_list()
90
+ else:
91
+ result["pipeline"] = self.pipeline
92
+
93
+ return {"$lookup": result}
94
+
95
+
96
+ class UnionWith(BaseModel):
97
+ """
98
+ $unionWith stage - combines pipeline results with another collection.
99
+
100
+ Example:
101
+ >>> UnionWith(collection="archive").model_dump()
102
+ {"$unionWith": "archive"}
103
+
104
+ >>> UnionWith(
105
+ ... collection="archive",
106
+ ... pipeline=Pipeline([Match(query={"year": 2023})])
107
+ ... ).model_dump()
108
+ {"$unionWith": {"coll": "archive", "pipeline": [...]}}
109
+ """
110
+
111
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
112
+
113
+ collection: str = Field(
114
+ ...,
115
+ validation_alias="coll",
116
+ serialization_alias="coll",
117
+ description="Collection to union",
118
+ )
119
+ pipeline: Pipeline | list[dict[str, Any]] | None = Field(
120
+ default=None, description="Pipeline for the other collection"
121
+ )
122
+
123
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
124
+ if self.pipeline is None:
125
+ return {"$unionWith": self.collection}
126
+
127
+ pl = (
128
+ self.pipeline.to_list()
129
+ if isinstance(self.pipeline, Pipeline)
130
+ else self.pipeline
131
+ )
132
+ return {"$unionWith": {"coll": self.collection, "pipeline": pl}}
133
+
134
+
135
+ class GraphLookup(BaseModel):
136
+ """
137
+ $graphLookup stage - performs recursive search.
138
+
139
+ Example:
140
+ >>> GraphLookup(
141
+ ... from_collection="employees",
142
+ ... start_with="$reportsTo",
143
+ ... connect_from_field="reportsTo",
144
+ ... connect_to_field="name",
145
+ ... as_field="reportingHierarchy"
146
+ ... ).model_dump()
147
+ """
148
+
149
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
150
+
151
+ from_collection: str = Field(
152
+ ...,
153
+ validation_alias="from",
154
+ serialization_alias="from",
155
+ description="Collection to search",
156
+ )
157
+ start_with: Any = Field(
158
+ ...,
159
+ validation_alias="startWith",
160
+ serialization_alias="startWith",
161
+ description="Expression for starting point",
162
+ )
163
+ connect_from_field: str = Field(
164
+ ...,
165
+ validation_alias="connectFromField",
166
+ serialization_alias="connectFromField",
167
+ description="Field to recurse from",
168
+ )
169
+ connect_to_field: str = Field(
170
+ ...,
171
+ validation_alias="connectToField",
172
+ serialization_alias="connectToField",
173
+ description="Field to match",
174
+ )
175
+ as_field: str = Field(
176
+ ...,
177
+ validation_alias="as",
178
+ serialization_alias="as",
179
+ description="Output array field",
180
+ )
181
+ max_depth: int | None = Field(
182
+ default=None,
183
+ validation_alias="maxDepth",
184
+ serialization_alias="maxDepth",
185
+ description="Maximum recursion depth",
186
+ )
187
+ depth_field: str | None = Field(
188
+ default=None,
189
+ validation_alias="depthField",
190
+ serialization_alias="depthField",
191
+ description="Field for recursion depth",
192
+ )
193
+ restrict_search_with_match: dict[str, Any] | None = Field(
194
+ default=None,
195
+ validation_alias="restrictSearchWithMatch",
196
+ serialization_alias="restrictSearchWithMatch",
197
+ description="Additional match conditions",
198
+ )
199
+
200
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
201
+ result: dict[str, Any] = {
202
+ "from": self.from_collection,
203
+ "startWith": self.start_with,
204
+ "connectFromField": self.connect_from_field,
205
+ "connectToField": self.connect_to_field,
206
+ "as": self.as_field,
207
+ }
208
+ if self.max_depth is not None:
209
+ result["maxDepth"] = self.max_depth
210
+ if self.depth_field is not None:
211
+ result["depthField"] = self.depth_field
212
+ if self.restrict_search_with_match is not None:
213
+ result["restrictSearchWithMatch"] = self.restrict_search_with_match
214
+ return {"$graphLookup": result}
215
+
216
+
217
+ __all__ = [
218
+ "Lookup",
219
+ "UnionWith",
220
+ "GraphLookup",
221
+ ]
@@ -0,0 +1,45 @@
1
+ """Miscellaneous MongoDB aggregation pipeline stages.
2
+
3
+ This module contains less commonly used stages:
4
+ ListClusterCatalog and QuerySettings.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, ConfigDict
10
+
11
+
12
+ class ListClusterCatalog(BaseModel):
13
+ """
14
+ $listClusterCatalog stage - lists collections in a cluster.
15
+
16
+ Example:
17
+ >>> ListClusterCatalog().model_dump()
18
+ {"$listClusterCatalog": {}}
19
+ """
20
+
21
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
22
+
23
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
24
+ return {"$listClusterCatalog": {}}
25
+
26
+
27
+ class QuerySettings(BaseModel):
28
+ """
29
+ $querySettings stage - returns query settings (MongoDB 8.0+).
30
+
31
+ Example:
32
+ >>> QuerySettings().model_dump()
33
+ {"$querySettings": {}}
34
+ """
35
+
36
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
37
+
38
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
39
+ return {"$querySettings": {}}
40
+
41
+
42
+ __all__ = [
43
+ "ListClusterCatalog",
44
+ "QuerySettings",
45
+ ]
@@ -0,0 +1,136 @@
1
+ """Output and sampling MongoDB aggregation pipeline stages.
2
+
3
+ This module contains stages for writing output and sampling:
4
+ Out, Merge, Sample, and Documents.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+
12
+ class Sample(BaseModel):
13
+ """
14
+ $sample stage - randomly selects documents.
15
+
16
+ Example:
17
+ >>> Sample(size=10).model_dump()
18
+ {"$sample": {"size": 10}}
19
+ """
20
+
21
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
22
+
23
+ size: int = Field(..., gt=0, description="Number of documents to sample")
24
+
25
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
26
+ return {"$sample": {"size": self.size}}
27
+
28
+
29
+ class Out(BaseModel):
30
+ """
31
+ $out stage - writes results to a collection.
32
+
33
+ Example:
34
+ >>> Out(collection="results").model_dump()
35
+ {"$out": "results"}
36
+
37
+ >>> Out(collection="results", db="analytics").model_dump()
38
+ {"$out": {"db": "analytics", "coll": "results"}}
39
+ """
40
+
41
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
42
+
43
+ collection: str = Field(..., description="Output collection name")
44
+ db: str | None = Field(default=None, description="Output database name")
45
+
46
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
47
+ if self.db is not None:
48
+ return {"$out": {"db": self.db, "coll": self.collection}}
49
+ return {"$out": self.collection}
50
+
51
+
52
+ class Merge(BaseModel):
53
+ """
54
+ $merge stage - writes results to a collection with merge behavior.
55
+
56
+ Example:
57
+ >>> Merge(
58
+ ... into="reports",
59
+ ... on="_id",
60
+ ... when_matched="merge",
61
+ ... when_not_matched="insert"
62
+ ... ).model_dump()
63
+ {"$merge": {
64
+ "into": "reports",
65
+ "on": "_id",
66
+ "whenMatched": "merge",
67
+ "whenNotMatched": "insert"
68
+ }}
69
+ """
70
+
71
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
72
+
73
+ into: str | dict[str, str] = Field(
74
+ ..., description="Target collection (or {db, coll})"
75
+ )
76
+ on: str | list[str] | None = Field(
77
+ default=None, description="Field(s) to match on"
78
+ )
79
+ let: dict[str, Any] | None = Field(
80
+ default=None, description="Variables for pipeline"
81
+ )
82
+ when_matched: str | list[dict[str, Any]] | None = Field(
83
+ default=None,
84
+ validation_alias="whenMatched",
85
+ serialization_alias="whenMatched",
86
+ description="Action when matched (replace, keepExisting, merge, fail, "
87
+ "or pipeline)",
88
+ )
89
+ when_not_matched: str | None = Field(
90
+ default=None,
91
+ validation_alias="whenNotMatched",
92
+ serialization_alias="whenNotMatched",
93
+ description="Action when not matched (insert, discard, fail)",
94
+ )
95
+
96
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
97
+ result: dict[str, Any] = {"into": self.into}
98
+ if self.on is not None:
99
+ result["on"] = self.on
100
+ if self.let is not None:
101
+ result["let"] = self.let
102
+ if self.when_matched is not None:
103
+ result["whenMatched"] = self.when_matched
104
+ if self.when_not_matched is not None:
105
+ result["whenNotMatched"] = self.when_not_matched
106
+ return {"$merge": result}
107
+
108
+
109
+ class Documents(BaseModel):
110
+ """
111
+ $documents stage - returns literal documents.
112
+
113
+ Example:
114
+ >>> Documents(documents=[
115
+ ... {"x": 1, "y": 2},
116
+ ... {"x": 3, "y": 4}
117
+ ... ]).model_dump()
118
+ {"$documents": [{"x": 1, "y": 2}, {"x": 3, "y": 4}]}
119
+ """
120
+
121
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
122
+
123
+ documents: list[dict[str, Any]] = Field(
124
+ ..., description="Documents to return"
125
+ )
126
+
127
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
128
+ return {"$documents": self.documents}
129
+
130
+
131
+ __all__ = [
132
+ "Out",
133
+ "Merge",
134
+ "Sample",
135
+ "Documents",
136
+ ]