mongo-aggro 0.1.0__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 +188 -0
- mongo_aggro/accumulators.py +474 -0
- mongo_aggro/base.py +195 -0
- mongo_aggro/operators.py +247 -0
- mongo_aggro/stages.py +990 -0
- mongo_aggro-0.1.0.dist-info/METADATA +537 -0
- mongo_aggro-0.1.0.dist-info/RECORD +9 -0
- mongo_aggro-0.1.0.dist-info/WHEEL +4 -0
- mongo_aggro-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mongo-aggro
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MongoDB Aggregation Pipeline Builder with Pydantic
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: mongodb,aggregation,pipeline,pydantic,database
|
|
8
|
+
Author: Hamed Ghenaat
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: black (>=24.10.0) ; extra == "dev"
|
|
23
|
+
Requires-Dist: isort (>=5.13.2) ; extra == "dev"
|
|
24
|
+
Requires-Dist: mkdocs (>=1.6.0) ; extra == "docs"
|
|
25
|
+
Requires-Dist: mkdocs-material (>=9.5.0) ; extra == "docs"
|
|
26
|
+
Requires-Dist: mkdocstrings (>=0.27.0) ; extra == "docs"
|
|
27
|
+
Requires-Dist: mkdocstrings-python (>=1.12.0) ; extra == "docs"
|
|
28
|
+
Requires-Dist: mypy (>=1.13.0) ; extra == "dev"
|
|
29
|
+
Requires-Dist: pre-commit (>=4.0.0) ; extra == "dev"
|
|
30
|
+
Requires-Dist: pydantic (>=2.10.0)
|
|
31
|
+
Requires-Dist: pytest (>=8.0.0) ; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-cov (>=6.0.0) ; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-mock (>=3.14.0) ; extra == "test"
|
|
34
|
+
Requires-Dist: pyupgrade (>=3.19.0) ; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff (>=0.8.0) ; extra == "dev"
|
|
36
|
+
Project-URL: Documentation, https://hamedghenaat.github.io/mongo-aggro/
|
|
37
|
+
Project-URL: Homepage, https://github.com/hamedghenaat/mongo-aggro
|
|
38
|
+
Project-URL: Repository, https://github.com/hamedghenaat/mongo-aggro
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# Mongo Aggro - MongoDB Aggregation Pipeline Builder
|
|
42
|
+
|
|
43
|
+
A Python package for building MongoDB aggregation pipelines with strong type checking using Pydantic.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Type-safe pipeline building** - Pydantic models ensure type safety at runtime
|
|
48
|
+
- **Direct MongoDB integration** - Pass `Pipeline` directly to `collection.aggregate()` without calling any methods
|
|
49
|
+
- **Comprehensive stage support** - All major MongoDB aggregation stages supported
|
|
50
|
+
- **Nested pipelines** - Stages like `$lookup`, `$facet`, and `$unionWith` support nested pipelines
|
|
51
|
+
- **Query operators** - Built-in support for logical operators (`$and`, `$or`, `$not`, `$nor`) and comparison operators
|
|
52
|
+
- **Accumulator classes** - Type-safe accumulator builders for `$group` stage
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
poetry add mongo-aggro
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from mongo_aggro import Pipeline, Match, Unwind, Group, Sort, Limit
|
|
64
|
+
|
|
65
|
+
# Create a pipeline
|
|
66
|
+
pipeline = Pipeline()
|
|
67
|
+
pipeline.add_stage(Match(query={"status": "active"}))
|
|
68
|
+
pipeline.add_stage(Unwind(path="items"))
|
|
69
|
+
pipeline.add_stage(Group(id="$category", accumulators={"count": {"$sum": 1}}))
|
|
70
|
+
pipeline.add_stage(Sort(fields={"count": -1}))
|
|
71
|
+
pipeline.add_stage(Limit(count=10))
|
|
72
|
+
|
|
73
|
+
# Pass directly to MongoDB - no need to call any methods
|
|
74
|
+
results = collection.aggregate(pipeline)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or initialize with stages in the constructor:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
pipeline = Pipeline([
|
|
81
|
+
Match(query={"status": "active"}),
|
|
82
|
+
Unwind(path="items"),
|
|
83
|
+
Group(id="$category", accumulators={"count": {"$sum": 1}}),
|
|
84
|
+
Sort(fields={"count": -1}),
|
|
85
|
+
Limit(count=10)
|
|
86
|
+
])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Supported Stages
|
|
90
|
+
|
|
91
|
+
### Document Filtering & Transformation
|
|
92
|
+
- **Match** - Filter documents (`$match`)
|
|
93
|
+
- **Project** - Shape documents (`$project`)
|
|
94
|
+
- **AddFields / Set** - Add new fields (`$addFields`, `$set`)
|
|
95
|
+
- **Unset** - Remove fields (`$unset`)
|
|
96
|
+
- **ReplaceRoot / ReplaceWith** - Replace document root (`$replaceRoot`, `$replaceWith`)
|
|
97
|
+
- **Redact** - Restrict document content (`$redact`)
|
|
98
|
+
|
|
99
|
+
### Grouping & Aggregation
|
|
100
|
+
- **Group** - Group and aggregate (`$group`)
|
|
101
|
+
- **Bucket** - Categorize into buckets (`$bucket`)
|
|
102
|
+
- **BucketAuto** - Auto-categorize into buckets (`$bucketAuto`)
|
|
103
|
+
- **SortByCount** - Group, count, and sort (`$sortByCount`)
|
|
104
|
+
- **Count** - Count documents (`$count`)
|
|
105
|
+
|
|
106
|
+
### Array Operations
|
|
107
|
+
- **Unwind** - Deconstruct arrays (`$unwind`)
|
|
108
|
+
|
|
109
|
+
### Sorting & Pagination
|
|
110
|
+
- **Sort** - Sort documents (`$sort`)
|
|
111
|
+
- **Limit** - Limit results (`$limit`)
|
|
112
|
+
- **Skip** - Skip documents (`$skip`)
|
|
113
|
+
- **Sample** - Random sampling (`$sample`)
|
|
114
|
+
|
|
115
|
+
### Joins & Lookups
|
|
116
|
+
- **Lookup** - Left outer join (`$lookup`)
|
|
117
|
+
- **GraphLookup** - Recursive search (`$graphLookup`)
|
|
118
|
+
|
|
119
|
+
### Multiple Pipelines
|
|
120
|
+
- **Facet** - Multiple pipelines in single stage (`$facet`)
|
|
121
|
+
- **UnionWith** - Union with another collection (`$unionWith`)
|
|
122
|
+
|
|
123
|
+
### Output
|
|
124
|
+
- **Out** - Write to collection (`$out`)
|
|
125
|
+
- **Merge** - Merge into collection (`$merge`)
|
|
126
|
+
|
|
127
|
+
### Geospatial
|
|
128
|
+
- **GeoNear** - Geospatial queries (`$geoNear`)
|
|
129
|
+
|
|
130
|
+
### Window Functions & Analytics
|
|
131
|
+
- **SetWindowFields** - Window calculations (`$setWindowFields`)
|
|
132
|
+
- **Densify** - Fill gaps in data (`$densify`)
|
|
133
|
+
- **Fill** - Fill null/missing values (`$fill`)
|
|
134
|
+
|
|
135
|
+
### Utility
|
|
136
|
+
- **Documents** - Return literal documents (`$documents`)
|
|
137
|
+
|
|
138
|
+
## Accumulators
|
|
139
|
+
|
|
140
|
+
Type-safe accumulator classes for the `$group` stage:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from mongo_aggro import Sum, Avg, Min, Max, First, Last, Push, AddToSet, Count_
|
|
144
|
+
from mongo_aggro import merge_accumulators
|
|
145
|
+
|
|
146
|
+
# Each accumulator returns a dictionary
|
|
147
|
+
Sum(name="totalQuantity", field="quantity").model_dump()
|
|
148
|
+
# Output: {"totalQuantity": {"$sum": "$quantity"}}
|
|
149
|
+
|
|
150
|
+
Avg(name="avgPrice", field="price").model_dump()
|
|
151
|
+
# Output: {"avgPrice": {"$avg": "$price"}}
|
|
152
|
+
|
|
153
|
+
# Use value=1 for counting
|
|
154
|
+
Sum(name="count", value=1).model_dump()
|
|
155
|
+
# Output: {"count": {"$sum": 1}}
|
|
156
|
+
|
|
157
|
+
# Push with expression
|
|
158
|
+
Push(name="orderDetails", expression={"item": "$item", "qty": "$quantity"}).model_dump()
|
|
159
|
+
# Output: {"orderDetails": {"$push": {"item": "$item", "qty": "$quantity"}}}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Merging Accumulators
|
|
163
|
+
|
|
164
|
+
Use `merge_accumulators` to combine multiple accumulators:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from mongo_aggro import Group, Sum, Avg, Max, Min, merge_accumulators
|
|
168
|
+
|
|
169
|
+
# Merge multiple accumulators for Group stage
|
|
170
|
+
group = Group(
|
|
171
|
+
id="$category",
|
|
172
|
+
accumulators=merge_accumulators(
|
|
173
|
+
Sum(name="totalSales", field="amount"),
|
|
174
|
+
Avg(name="avgPrice", field="price"),
|
|
175
|
+
Max(name="maxPrice", field="price"),
|
|
176
|
+
Min(name="minPrice", field="price"),
|
|
177
|
+
Sum(name="orderCount", value=1)
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
# Output: {"$group": {
|
|
181
|
+
# "_id": "$category",
|
|
182
|
+
# "totalSales": {"$sum": "$amount"},
|
|
183
|
+
# "avgPrice": {"$avg": "$price"},
|
|
184
|
+
# "maxPrice": {"$max": "$price"},
|
|
185
|
+
# "minPrice": {"$min": "$price"},
|
|
186
|
+
# "orderCount": {"$sum": 1}
|
|
187
|
+
# }}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Available Accumulators
|
|
191
|
+
|
|
192
|
+
| Accumulator | Description |
|
|
193
|
+
|-------------|-------------|
|
|
194
|
+
| `Sum` | Sum values or count with `value=1` |
|
|
195
|
+
| `Avg` | Calculate average |
|
|
196
|
+
| `Min` | Get minimum value |
|
|
197
|
+
| `Max` | Get maximum value |
|
|
198
|
+
| `First` | First value in group |
|
|
199
|
+
| `Last` | Last value in group |
|
|
200
|
+
| `Push` | Create array of values |
|
|
201
|
+
| `AddToSet` | Create array of unique values |
|
|
202
|
+
| `StdDevPop` | Population standard deviation |
|
|
203
|
+
| `StdDevSamp` | Sample standard deviation |
|
|
204
|
+
| `Count_` | Count documents (MongoDB 5.0+) |
|
|
205
|
+
| `MergeObjects` | Merge documents |
|
|
206
|
+
| `TopN` | Top N elements (MongoDB 5.2+) |
|
|
207
|
+
| `BottomN` | Bottom N elements (MongoDB 5.2+) |
|
|
208
|
+
| `FirstN` | First N elements (MongoDB 5.2+) |
|
|
209
|
+
| `LastN` | Last N elements (MongoDB 5.2+) |
|
|
210
|
+
| `MaxN` | N maximum values (MongoDB 5.2+) |
|
|
211
|
+
| `MinN` | N minimum values (MongoDB 5.2+) |
|
|
212
|
+
|
|
213
|
+
## Examples
|
|
214
|
+
|
|
215
|
+
### Complex Match with Logical Operators
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from mongo_aggro import Match, And, Or
|
|
219
|
+
|
|
220
|
+
# Using $and and $or directly in query dict
|
|
221
|
+
match = Match(query={
|
|
222
|
+
"$and": [
|
|
223
|
+
{"status": "active"},
|
|
224
|
+
{"$or": [
|
|
225
|
+
{"type": "premium"},
|
|
226
|
+
{"balance": {"$gt": 1000}}
|
|
227
|
+
]}
|
|
228
|
+
]
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
# Or use operator classes for building conditions
|
|
232
|
+
and_cond = And(conditions=[
|
|
233
|
+
{"status": "active"},
|
|
234
|
+
{"age": {"$gte": 18}}
|
|
235
|
+
])
|
|
236
|
+
print(and_cond.model_dump()) # {"$and": [{"status": "active"}, {"age": {"$gte": 18}}]}
|
|
237
|
+
|
|
238
|
+
# Complex nested conditions
|
|
239
|
+
or_cond = Or(conditions=[
|
|
240
|
+
{"region": "US"},
|
|
241
|
+
And(conditions=[{"region": "EU"}, {"premium": True}]).model_dump()
|
|
242
|
+
])
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Combining Stages with Operators
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from mongo_aggro import (
|
|
249
|
+
Pipeline, Match, Group, Sort, Limit, Project,
|
|
250
|
+
And, Or, Expr, In, Gt, Regex,
|
|
251
|
+
Sum, Avg, Max, merge_accumulators
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Build a complex analytics pipeline
|
|
255
|
+
pipeline = Pipeline()
|
|
256
|
+
|
|
257
|
+
# Stage 1: Match with complex conditions
|
|
258
|
+
pipeline.add_stage(Match(query={
|
|
259
|
+
"$and": [
|
|
260
|
+
{"status": {"$in": ["completed", "shipped"]}},
|
|
261
|
+
{"orderDate": {"$gte": "2024-01-01"}},
|
|
262
|
+
{"$or": [
|
|
263
|
+
{"totalAmount": {"$gt": 100}},
|
|
264
|
+
{"priority": "high"}
|
|
265
|
+
]}
|
|
266
|
+
]
|
|
267
|
+
}))
|
|
268
|
+
|
|
269
|
+
# Stage 2: Group with multiple accumulators
|
|
270
|
+
pipeline.add_stage(Group(
|
|
271
|
+
id={"region": "$region", "category": "$category"},
|
|
272
|
+
accumulators=merge_accumulators(
|
|
273
|
+
Sum(name="totalRevenue", field="totalAmount"),
|
|
274
|
+
Avg(name="avgOrderValue", field="totalAmount"),
|
|
275
|
+
Max(name="largestOrder", field="totalAmount"),
|
|
276
|
+
Sum(name="orderCount", value=1)
|
|
277
|
+
)
|
|
278
|
+
))
|
|
279
|
+
|
|
280
|
+
# Stage 3: Match groups with significant revenue
|
|
281
|
+
pipeline.add_stage(Match(query={
|
|
282
|
+
"totalRevenue": {"$gt": 10000}
|
|
283
|
+
}))
|
|
284
|
+
|
|
285
|
+
# Stage 4: Sort by revenue
|
|
286
|
+
pipeline.add_stage(Sort(fields={"totalRevenue": -1}))
|
|
287
|
+
|
|
288
|
+
# Stage 5: Limit results
|
|
289
|
+
pipeline.add_stage(Limit(count=20))
|
|
290
|
+
|
|
291
|
+
# Stage 6: Project final shape
|
|
292
|
+
pipeline.add_stage(Project(fields={
|
|
293
|
+
"_id": 0,
|
|
294
|
+
"region": "$_id.region",
|
|
295
|
+
"category": "$_id.category",
|
|
296
|
+
"totalRevenue": 1,
|
|
297
|
+
"avgOrderValue": {"$round": ["$avgOrderValue", 2]},
|
|
298
|
+
"orderCount": 1
|
|
299
|
+
}))
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Lookup with Nested Pipeline
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from mongo_aggro import Pipeline, Match, Lookup
|
|
306
|
+
|
|
307
|
+
lookup = Lookup(
|
|
308
|
+
from_collection="orders",
|
|
309
|
+
let={"customerId": "$_id"},
|
|
310
|
+
pipeline=Pipeline([
|
|
311
|
+
Match(query={"$expr": {"$eq": ["$customerId", "$$customerId"]}}),
|
|
312
|
+
Match(query={"status": "completed"})
|
|
313
|
+
]),
|
|
314
|
+
as_field="completedOrders"
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Facet with Multiple Pipelines
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from mongo_aggro import Pipeline, Facet, Group, Sort, Limit, Sum, merge_accumulators
|
|
322
|
+
|
|
323
|
+
facet = Facet(pipelines={
|
|
324
|
+
"byCategory": Pipeline([
|
|
325
|
+
Group(
|
|
326
|
+
id="$category",
|
|
327
|
+
accumulators=merge_accumulators(
|
|
328
|
+
Sum(name="count", value=1),
|
|
329
|
+
Sum(name="total", field="amount")
|
|
330
|
+
)
|
|
331
|
+
),
|
|
332
|
+
Sort(fields={"count": -1})
|
|
333
|
+
]),
|
|
334
|
+
"byRegion": Pipeline([
|
|
335
|
+
Group(
|
|
336
|
+
id="$region",
|
|
337
|
+
accumulators=merge_accumulators(
|
|
338
|
+
Sum(name="count", value=1),
|
|
339
|
+
Avg(name="avgAmount", field="amount")
|
|
340
|
+
)
|
|
341
|
+
),
|
|
342
|
+
Sort(fields={"avgAmount": -1})
|
|
343
|
+
]),
|
|
344
|
+
"topProducts": Pipeline([
|
|
345
|
+
Sort(fields={"sales": -1}),
|
|
346
|
+
Limit(count=10)
|
|
347
|
+
])
|
|
348
|
+
})
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Using Query Operators with Match
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from mongo_aggro import Match, Regex, In, Exists, ElemMatch
|
|
355
|
+
|
|
356
|
+
# Text search with regex
|
|
357
|
+
pipeline.add_stage(Match(query={
|
|
358
|
+
"name": Regex(pattern="^John", options="i").model_dump()
|
|
359
|
+
}))
|
|
360
|
+
|
|
361
|
+
# Check field existence
|
|
362
|
+
pipeline.add_stage(Match(query={
|
|
363
|
+
"email": Exists(exists=True).model_dump(),
|
|
364
|
+
"deletedAt": Exists(exists=False).model_dump()
|
|
365
|
+
}))
|
|
366
|
+
|
|
367
|
+
# Array element matching
|
|
368
|
+
pipeline.add_stage(Match(query={
|
|
369
|
+
"items": ElemMatch(conditions={
|
|
370
|
+
"quantity": {"$gt": 5},
|
|
371
|
+
"price": {"$lt": 100}
|
|
372
|
+
}).model_dump()
|
|
373
|
+
}))
|
|
374
|
+
|
|
375
|
+
# Combining multiple operator types
|
|
376
|
+
pipeline.add_stage(Match(query={
|
|
377
|
+
"$and": [
|
|
378
|
+
{"status": In(values=["active", "pending"]).model_dump()},
|
|
379
|
+
{"score": Gt(value=80).model_dump()},
|
|
380
|
+
{"tags": {"$exists": True}}
|
|
381
|
+
]
|
|
382
|
+
}))
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Unwind with Options
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from mongo_aggro import Unwind
|
|
389
|
+
|
|
390
|
+
# Simple unwind
|
|
391
|
+
unwind = Unwind(path="items")
|
|
392
|
+
# Output: {"$unwind": "$items"}
|
|
393
|
+
|
|
394
|
+
# With options
|
|
395
|
+
unwind = Unwind(
|
|
396
|
+
path="items",
|
|
397
|
+
include_array_index="itemIndex",
|
|
398
|
+
preserve_null_and_empty=True
|
|
399
|
+
)
|
|
400
|
+
# Output: {"$unwind": {"path": "$items", "includeArrayIndex": "itemIndex", "preserveNullAndEmptyArrays": true}}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Group with Accumulators
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
from mongo_aggro import Group, Sum, Avg, Max, Push, merge_accumulators
|
|
407
|
+
|
|
408
|
+
group = Group(
|
|
409
|
+
id="$category",
|
|
410
|
+
accumulators=merge_accumulators(
|
|
411
|
+
Sum(name="totalQuantity", field="quantity"),
|
|
412
|
+
Avg(name="avgPrice", field="price"),
|
|
413
|
+
Max(name="maxPrice", field="price"),
|
|
414
|
+
Push(name="items", field="name")
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Complete E-commerce Analytics Example
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
from mongo_aggro import (
|
|
423
|
+
Pipeline, Match, Unwind, Group, Sort, Limit, Project, Lookup,
|
|
424
|
+
Sum, Avg, Max, First, Push, merge_accumulators
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Analyze orders with customer details
|
|
428
|
+
pipeline = Pipeline([
|
|
429
|
+
# Filter recent orders
|
|
430
|
+
Match(query={
|
|
431
|
+
"orderDate": {"$gte": "2024-01-01"},
|
|
432
|
+
"status": {"$ne": "cancelled"}
|
|
433
|
+
}),
|
|
434
|
+
|
|
435
|
+
# Join with customers
|
|
436
|
+
Lookup(
|
|
437
|
+
from_collection="customers",
|
|
438
|
+
local_field="customerId",
|
|
439
|
+
foreign_field="_id",
|
|
440
|
+
as_field="customer"
|
|
441
|
+
),
|
|
442
|
+
|
|
443
|
+
# Unwind customer (single element array)
|
|
444
|
+
Unwind(path="customer"),
|
|
445
|
+
|
|
446
|
+
# Unwind order items
|
|
447
|
+
Unwind(path="items"),
|
|
448
|
+
|
|
449
|
+
# Group by product and customer region
|
|
450
|
+
Group(
|
|
451
|
+
id={
|
|
452
|
+
"product": "$items.productId",
|
|
453
|
+
"region": "$customer.region"
|
|
454
|
+
},
|
|
455
|
+
accumulators=merge_accumulators(
|
|
456
|
+
Sum(name="totalQuantity", field="items.quantity"),
|
|
457
|
+
Sum(name="totalRevenue", field="items.subtotal"),
|
|
458
|
+
Avg(name="avgQuantity", field="items.quantity"),
|
|
459
|
+
Sum(name="orderCount", value=1),
|
|
460
|
+
First(name="productName", field="items.name")
|
|
461
|
+
)
|
|
462
|
+
),
|
|
463
|
+
|
|
464
|
+
# Filter significant sales
|
|
465
|
+
Match(query={"totalRevenue": {"$gt": 1000}}),
|
|
466
|
+
|
|
467
|
+
# Sort by revenue
|
|
468
|
+
Sort(fields={"totalRevenue": -1}),
|
|
469
|
+
|
|
470
|
+
# Top 50 results
|
|
471
|
+
Limit(count=50),
|
|
472
|
+
|
|
473
|
+
# Final projection
|
|
474
|
+
Project(fields={
|
|
475
|
+
"_id": 0,
|
|
476
|
+
"product": "$productName",
|
|
477
|
+
"region": "$_id.region",
|
|
478
|
+
"totalQuantity": 1,
|
|
479
|
+
"totalRevenue": {"$round": ["$totalRevenue", 2]},
|
|
480
|
+
"avgQuantity": {"$round": ["$avgQuantity", 1]},
|
|
481
|
+
"orderCount": 1
|
|
482
|
+
})
|
|
483
|
+
])
|
|
484
|
+
|
|
485
|
+
# Execute
|
|
486
|
+
results = db.orders.aggregate(pipeline)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Query Operators
|
|
490
|
+
|
|
491
|
+
The package includes query operators for building complex conditions:
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
from mongo_aggro import And, Or, Not, Nor, Expr, Eq, Gt, In, Regex
|
|
495
|
+
|
|
496
|
+
# Logical operators
|
|
497
|
+
And(conditions=[{"a": 1}, {"b": 2}]).model_dump() # {"$and": [...]}
|
|
498
|
+
Or(conditions=[{"a": 1}, {"a": 2}]).model_dump() # {"$or": [...]}
|
|
499
|
+
Not(condition={"$regex": "^test"}).model_dump() # {"$not": {...}}
|
|
500
|
+
Nor(conditions=[{"a": 1}, {"b": 2}]).model_dump() # {"$nor": [...]}
|
|
501
|
+
|
|
502
|
+
# Comparison operators
|
|
503
|
+
Eq(value=5).model_dump() # {"$eq": 5}
|
|
504
|
+
Gt(value=10).model_dump() # {"$gt": 10}
|
|
505
|
+
In(values=[1, 2, 3]).model_dump() # {"$in": [1, 2, 3]}
|
|
506
|
+
Regex(pattern="^test", options="i").model_dump() # {"$regex": "^test", "$options": "i"}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Method Chaining
|
|
510
|
+
|
|
511
|
+
The `add_stage` method returns the pipeline, enabling method chaining:
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
pipeline = (
|
|
515
|
+
Pipeline()
|
|
516
|
+
.add_stage(Match(query={"active": True}))
|
|
517
|
+
.add_stage(Sort(fields={"createdAt": -1}))
|
|
518
|
+
.add_stage(Limit(count=100))
|
|
519
|
+
)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## How It Works
|
|
523
|
+
|
|
524
|
+
The `Pipeline` class implements `__iter__`, which yields each stage's dictionary representation when iterated. MongoDB's `aggregate()` method iterates over the pipeline argument, so no conversion is needed:
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
# This works because MongoDB iterates over the pipeline
|
|
528
|
+
collection.aggregate(pipeline)
|
|
529
|
+
|
|
530
|
+
# Equivalent to:
|
|
531
|
+
collection.aggregate([
|
|
532
|
+
{"$match": {"status": "active"}},
|
|
533
|
+
{"$unwind": "$items"},
|
|
534
|
+
# ...
|
|
535
|
+
])
|
|
536
|
+
```
|
|
537
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
mongo_aggro/__init__.py,sha256=pTbpK7bdK5iDITTkalZSma4dax_-g_bqJjseXhx5xU0,2960
|
|
2
|
+
mongo_aggro/accumulators.py,sha256=v1UR0R8Sgx5K3aZRntP-G4jhRlQ2Ff189cadMOt2Gwk,14241
|
|
3
|
+
mongo_aggro/base.py,sha256=76BwUUfyWNDLnRnRX6xt2NcRgdj7u1enk4QZ4ywl9Fg,6090
|
|
4
|
+
mongo_aggro/operators.py,sha256=-PnF-R65usJfvatktLGUoXaPbdq6uhxHJSIX66nb7GQ,6657
|
|
5
|
+
mongo_aggro/stages.py,sha256=sMglloJn5wAnDs3CjWleFU_cse3jGhE348VsTcvBI44,30320
|
|
6
|
+
mongo_aggro-0.1.0.dist-info/METADATA,sha256=qQq3mvw_6NNQyDyecVhBBbQKpmmAEKAEbE1oYnJkTzY,15377
|
|
7
|
+
mongo_aggro-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
8
|
+
mongo_aggro-0.1.0.dist-info/licenses/LICENSE,sha256=XnrHxv3Dgf-ttUmsrNPMnQ69JLMED67bxKk3zrOdB40,1070
|
|
9
|
+
mongo_aggro-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamed Ghenaat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|