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.
- mongo_aggro/__init__.py +400 -0
- mongo_aggro/accumulators.py +30 -12
- mongo_aggro/base.py +49 -9
- mongo_aggro/expressions/__init__.py +396 -0
- mongo_aggro/expressions/arithmetic.py +329 -0
- mongo_aggro/expressions/array.py +425 -0
- mongo_aggro/expressions/base.py +180 -0
- mongo_aggro/expressions/bitwise.py +84 -0
- mongo_aggro/expressions/comparison.py +161 -0
- mongo_aggro/expressions/conditional.py +117 -0
- mongo_aggro/expressions/date.py +665 -0
- mongo_aggro/expressions/encrypted.py +116 -0
- mongo_aggro/expressions/logical.py +72 -0
- mongo_aggro/expressions/object.py +122 -0
- mongo_aggro/expressions/set.py +150 -0
- mongo_aggro/expressions/size.py +48 -0
- mongo_aggro/expressions/string.py +365 -0
- mongo_aggro/expressions/trigonometry.py +283 -0
- mongo_aggro/expressions/type.py +205 -0
- mongo_aggro/expressions/variable.py +73 -0
- mongo_aggro/expressions/window.py +327 -0
- mongo_aggro/operators/__init__.py +65 -0
- mongo_aggro/operators/array.py +41 -0
- mongo_aggro/operators/base.py +15 -0
- mongo_aggro/operators/bitwise.py +81 -0
- mongo_aggro/operators/comparison.py +82 -0
- mongo_aggro/operators/element.py +32 -0
- mongo_aggro/operators/geo.py +171 -0
- mongo_aggro/operators/logical.py +111 -0
- mongo_aggro/operators/misc.py +102 -0
- mongo_aggro/operators/regex.py +25 -0
- mongo_aggro/stages/__init__.py +110 -0
- mongo_aggro/stages/array.py +69 -0
- mongo_aggro/stages/change.py +109 -0
- mongo_aggro/stages/core.py +170 -0
- mongo_aggro/stages/geo.py +93 -0
- mongo_aggro/stages/group.py +154 -0
- mongo_aggro/stages/join.py +221 -0
- mongo_aggro/stages/misc.py +45 -0
- mongo_aggro/stages/output.py +136 -0
- mongo_aggro/stages/search.py +315 -0
- mongo_aggro/stages/session.py +111 -0
- mongo_aggro/stages/stats.py +152 -0
- mongo_aggro/stages/transform.py +136 -0
- mongo_aggro/stages/window.py +139 -0
- mongo_aggro-0.2.2.dist-info/METADATA +193 -0
- mongo_aggro-0.2.2.dist-info/RECORD +49 -0
- {mongo_aggro-0.1.0.dist-info → mongo_aggro-0.2.2.dist-info}/WHEEL +1 -1
- mongo_aggro/operators.py +0 -247
- mongo_aggro/stages.py +0 -990
- mongo_aggro-0.1.0.dist-info/METADATA +0 -537
- mongo_aggro-0.1.0.dist-info/RECORD +0 -9
- {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
|
+
]
|