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,425 @@
|
|
|
1
|
+
"""Array expression operators for MongoDB aggregation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import model_serializer
|
|
6
|
+
|
|
7
|
+
from mongo_aggro.base import serialize_value
|
|
8
|
+
from mongo_aggro.expressions.base import ExpressionBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArraySizeExpr(ExpressionBase):
|
|
12
|
+
"""
|
|
13
|
+
$size expression operator - returns array length.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> ArraySizeExpr(array=F("items")).model_dump()
|
|
17
|
+
{"$size": "$items"}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
array: Any
|
|
21
|
+
|
|
22
|
+
@model_serializer
|
|
23
|
+
def serialize(self) -> dict[str, Any]:
|
|
24
|
+
"""Serialize to MongoDB $size expression."""
|
|
25
|
+
return {"$size": serialize_value(self.array)}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SliceExpr(ExpressionBase):
|
|
29
|
+
"""
|
|
30
|
+
$slice expression operator - returns subset of array.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> SliceExpr(array=F("items"), n=5).model_dump()
|
|
34
|
+
{"$slice": ["$items", 5]}
|
|
35
|
+
|
|
36
|
+
>>> SliceExpr(array=F("items"), position=2, n=3).model_dump()
|
|
37
|
+
{"$slice": ["$items", 2, 3]}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
array: Any
|
|
41
|
+
n: int
|
|
42
|
+
position: int | None = None
|
|
43
|
+
|
|
44
|
+
@model_serializer
|
|
45
|
+
def serialize(self) -> dict[str, Any]:
|
|
46
|
+
"""Serialize to MongoDB $slice expression."""
|
|
47
|
+
if self.position is not None:
|
|
48
|
+
return {
|
|
49
|
+
"$slice": [serialize_value(self.array), self.position, self.n]
|
|
50
|
+
}
|
|
51
|
+
return {"$slice": [serialize_value(self.array), self.n]}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class FilterExpr(ExpressionBase):
|
|
55
|
+
"""
|
|
56
|
+
$filter expression operator - filters array elements.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> FilterExpr(
|
|
60
|
+
... input=F("items"),
|
|
61
|
+
... as_="item",
|
|
62
|
+
... cond=GteExpr(left=Field("$$item.price"), right=100)
|
|
63
|
+
... ).model_dump()
|
|
64
|
+
{"$filter": {"input": "$items", "as": "item", "cond": {...}}}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
input: Any
|
|
68
|
+
cond: Any
|
|
69
|
+
as_: str = "this"
|
|
70
|
+
limit: int | None = None
|
|
71
|
+
|
|
72
|
+
@model_serializer
|
|
73
|
+
def serialize(self) -> dict[str, Any]:
|
|
74
|
+
"""Serialize to MongoDB $filter expression."""
|
|
75
|
+
result: dict[str, Any] = {
|
|
76
|
+
"$filter": {
|
|
77
|
+
"input": serialize_value(self.input),
|
|
78
|
+
"as": self.as_,
|
|
79
|
+
"cond": serialize_value(self.cond),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if self.limit is not None:
|
|
83
|
+
result["$filter"]["limit"] = self.limit
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MapExpr(ExpressionBase):
|
|
88
|
+
"""
|
|
89
|
+
$map expression operator - applies expression to each array element.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> MapExpr(
|
|
93
|
+
... input=F("items"),
|
|
94
|
+
... as_="item",
|
|
95
|
+
... in_=MultiplyExpr(operands=[Field("$$item.price"), 1.1])
|
|
96
|
+
... ).model_dump()
|
|
97
|
+
{"$map": {"input": "$items", "as": "item", "in": {...}}}
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
input: Any
|
|
101
|
+
in_: Any
|
|
102
|
+
as_: str = "this"
|
|
103
|
+
|
|
104
|
+
@model_serializer
|
|
105
|
+
def serialize(self) -> dict[str, Any]:
|
|
106
|
+
"""Serialize to MongoDB $map expression."""
|
|
107
|
+
return {
|
|
108
|
+
"$map": {
|
|
109
|
+
"input": serialize_value(self.input),
|
|
110
|
+
"as": self.as_,
|
|
111
|
+
"in": serialize_value(self.in_),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ReduceExpr(ExpressionBase):
|
|
117
|
+
"""
|
|
118
|
+
$reduce expression operator - reduces array to single value.
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> ReduceExpr(
|
|
122
|
+
... input=F("items"),
|
|
123
|
+
... initial_value=0,
|
|
124
|
+
... in_=AddExpr(operands=[Field("$$value"), Field("$$this.qty")])
|
|
125
|
+
... ).model_dump()
|
|
126
|
+
{"$reduce": {"input": "$items", "initialValue": 0, "in": {...}}}
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
input: Any
|
|
130
|
+
initial_value: Any
|
|
131
|
+
in_: Any
|
|
132
|
+
|
|
133
|
+
@model_serializer
|
|
134
|
+
def serialize(self) -> dict[str, Any]:
|
|
135
|
+
"""Serialize to MongoDB $reduce expression."""
|
|
136
|
+
return {
|
|
137
|
+
"$reduce": {
|
|
138
|
+
"input": serialize_value(self.input),
|
|
139
|
+
"initialValue": serialize_value(self.initial_value),
|
|
140
|
+
"in": serialize_value(self.in_),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ArrayElemAtExpr(ExpressionBase):
|
|
146
|
+
"""
|
|
147
|
+
$arrayElemAt expression operator - gets element at array index.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
>>> ArrayElemAtExpr(array=F("items"), index=0).model_dump()
|
|
151
|
+
{"$arrayElemAt": ["$items", 0]}
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
array: Any
|
|
155
|
+
index: Any
|
|
156
|
+
|
|
157
|
+
@model_serializer
|
|
158
|
+
def serialize(self) -> dict[str, Any]:
|
|
159
|
+
"""Serialize to MongoDB $arrayElemAt expression."""
|
|
160
|
+
return {
|
|
161
|
+
"$arrayElemAt": [
|
|
162
|
+
serialize_value(self.array),
|
|
163
|
+
serialize_value(self.index),
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ConcatArraysExpr(ExpressionBase):
|
|
169
|
+
"""
|
|
170
|
+
$concatArrays expression operator - concatenates arrays.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
>>> ConcatArraysExpr(arrays=[F("arr1"), F("arr2")]).model_dump()
|
|
174
|
+
{"$concatArrays": ["$arr1", "$arr2"]}
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
arrays: list[Any]
|
|
178
|
+
|
|
179
|
+
@model_serializer
|
|
180
|
+
def serialize(self) -> dict[str, Any]:
|
|
181
|
+
"""Serialize to MongoDB $concatArrays expression."""
|
|
182
|
+
return {"$concatArrays": [serialize_value(a) for a in self.arrays]}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class InArrayExpr(ExpressionBase):
|
|
186
|
+
"""
|
|
187
|
+
$in expression operator - checks if value is in array.
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> InArrayExpr(value="admin", array=F("roles")).model_dump()
|
|
191
|
+
{"$in": ["admin", "$roles"]}
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
value: Any
|
|
195
|
+
array: Any
|
|
196
|
+
|
|
197
|
+
@model_serializer
|
|
198
|
+
def serialize(self) -> dict[str, Any]:
|
|
199
|
+
"""Serialize to MongoDB $in expression."""
|
|
200
|
+
return {
|
|
201
|
+
"$in": [serialize_value(self.value), serialize_value(self.array)]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class IndexOfArrayExpr(ExpressionBase):
|
|
206
|
+
"""
|
|
207
|
+
$indexOfArray expression operator - finds index of value in array.
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
>>> IndexOfArrayExpr(array=F("items"), value="target").model_dump()
|
|
211
|
+
{"$indexOfArray": ["$items", "target"]}
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
array: Any
|
|
215
|
+
value: Any
|
|
216
|
+
start: int | None = None
|
|
217
|
+
end: int | None = None
|
|
218
|
+
|
|
219
|
+
@model_serializer
|
|
220
|
+
def serialize(self) -> dict[str, Any]:
|
|
221
|
+
"""Serialize to MongoDB $indexOfArray expression."""
|
|
222
|
+
args: list[Any] = [
|
|
223
|
+
serialize_value(self.array),
|
|
224
|
+
serialize_value(self.value),
|
|
225
|
+
]
|
|
226
|
+
if self.start is not None:
|
|
227
|
+
args.append(self.start)
|
|
228
|
+
if self.end is not None:
|
|
229
|
+
args.append(self.end)
|
|
230
|
+
return {"$indexOfArray": args}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class IsArrayExpr(ExpressionBase):
|
|
234
|
+
"""
|
|
235
|
+
$isArray expression operator - checks if value is an array.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
>>> IsArrayExpr(input=F("field")).model_dump()
|
|
239
|
+
{"$isArray": "$field"}
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
input: Any
|
|
243
|
+
|
|
244
|
+
@model_serializer
|
|
245
|
+
def serialize(self) -> dict[str, Any]:
|
|
246
|
+
"""Serialize to MongoDB $isArray expression."""
|
|
247
|
+
return {"$isArray": serialize_value(self.input)}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ReverseArrayExpr(ExpressionBase):
|
|
251
|
+
"""
|
|
252
|
+
$reverseArray expression operator - reverses array elements.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
>>> ReverseArrayExpr(input=F("items")).model_dump()
|
|
256
|
+
{"$reverseArray": "$items"}
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
input: Any
|
|
260
|
+
|
|
261
|
+
@model_serializer
|
|
262
|
+
def serialize(self) -> dict[str, Any]:
|
|
263
|
+
"""Serialize to MongoDB $reverseArray expression."""
|
|
264
|
+
return {"$reverseArray": serialize_value(self.input)}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class SortArrayExpr(ExpressionBase):
|
|
268
|
+
"""
|
|
269
|
+
$sortArray expression operator - sorts array elements.
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
>>> SortArrayExpr(input=F("scores"), sort_by={"score": -1}).model_dump()
|
|
273
|
+
{"$sortArray": {"input": "$scores", "sortBy": {"score": -1}}}
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
input: Any
|
|
277
|
+
sort_by: dict[str, int] | int
|
|
278
|
+
|
|
279
|
+
@model_serializer
|
|
280
|
+
def serialize(self) -> dict[str, Any]:
|
|
281
|
+
"""Serialize to MongoDB $sortArray expression."""
|
|
282
|
+
return {
|
|
283
|
+
"$sortArray": {
|
|
284
|
+
"input": serialize_value(self.input),
|
|
285
|
+
"sortBy": self.sort_by,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class RangeExpr(ExpressionBase):
|
|
291
|
+
"""
|
|
292
|
+
$range expression operator - generates sequence of numbers.
|
|
293
|
+
|
|
294
|
+
Example:
|
|
295
|
+
>>> RangeExpr(start=0, end=10, step=2).model_dump()
|
|
296
|
+
{"$range": [0, 10, 2]}
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
start: Any
|
|
300
|
+
end: Any
|
|
301
|
+
step: Any = 1
|
|
302
|
+
|
|
303
|
+
@model_serializer
|
|
304
|
+
def serialize(self) -> dict[str, Any]:
|
|
305
|
+
"""Serialize to MongoDB $range expression."""
|
|
306
|
+
return {
|
|
307
|
+
"$range": [
|
|
308
|
+
serialize_value(self.start),
|
|
309
|
+
serialize_value(self.end),
|
|
310
|
+
serialize_value(self.step),
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class FirstNExpr(ExpressionBase):
|
|
316
|
+
"""
|
|
317
|
+
$firstN expression operator - returns first N elements of array.
|
|
318
|
+
|
|
319
|
+
Example:
|
|
320
|
+
>>> FirstNExpr(input=F("items"), n=3).model_dump()
|
|
321
|
+
{"$firstN": {"input": "$items", "n": 3}}
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
input: Any
|
|
325
|
+
n: Any
|
|
326
|
+
|
|
327
|
+
@model_serializer
|
|
328
|
+
def serialize(self) -> dict[str, Any]:
|
|
329
|
+
"""Serialize to MongoDB $firstN expression."""
|
|
330
|
+
return {
|
|
331
|
+
"$firstN": {
|
|
332
|
+
"input": serialize_value(self.input),
|
|
333
|
+
"n": serialize_value(self.n),
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class LastNExpr(ExpressionBase):
|
|
339
|
+
"""
|
|
340
|
+
$lastN expression operator - returns last N elements of array.
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
>>> LastNExpr(input=F("items"), n=3).model_dump()
|
|
344
|
+
{"$lastN": {"input": "$items", "n": 3}}
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
input: Any
|
|
348
|
+
n: Any
|
|
349
|
+
|
|
350
|
+
@model_serializer
|
|
351
|
+
def serialize(self) -> dict[str, Any]:
|
|
352
|
+
"""Serialize to MongoDB $lastN expression."""
|
|
353
|
+
return {
|
|
354
|
+
"$lastN": {
|
|
355
|
+
"input": serialize_value(self.input),
|
|
356
|
+
"n": serialize_value(self.n),
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class MaxNExpr(ExpressionBase):
|
|
362
|
+
"""
|
|
363
|
+
$maxN expression operator - returns N largest values from array.
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
>>> MaxNExpr(input=F("scores"), n=3).model_dump()
|
|
367
|
+
{"$maxN": {"input": "$scores", "n": 3}}
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
input: Any
|
|
371
|
+
n: Any
|
|
372
|
+
|
|
373
|
+
@model_serializer
|
|
374
|
+
def serialize(self) -> dict[str, Any]:
|
|
375
|
+
"""Serialize to MongoDB $maxN expression."""
|
|
376
|
+
return {
|
|
377
|
+
"$maxN": {
|
|
378
|
+
"input": serialize_value(self.input),
|
|
379
|
+
"n": serialize_value(self.n),
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class MinNExpr(ExpressionBase):
|
|
385
|
+
"""
|
|
386
|
+
$minN expression operator - returns N smallest values from array.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> MinNExpr(input=F("scores"), n=3).model_dump()
|
|
390
|
+
{"$minN": {"input": "$scores", "n": 3}}
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
input: Any
|
|
394
|
+
n: Any
|
|
395
|
+
|
|
396
|
+
@model_serializer
|
|
397
|
+
def serialize(self) -> dict[str, Any]:
|
|
398
|
+
"""Serialize to MongoDB $minN expression."""
|
|
399
|
+
return {
|
|
400
|
+
"$minN": {
|
|
401
|
+
"input": serialize_value(self.input),
|
|
402
|
+
"n": serialize_value(self.n),
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
__all__ = [
|
|
408
|
+
"ArraySizeExpr",
|
|
409
|
+
"SliceExpr",
|
|
410
|
+
"FilterExpr",
|
|
411
|
+
"MapExpr",
|
|
412
|
+
"ReduceExpr",
|
|
413
|
+
"ArrayElemAtExpr",
|
|
414
|
+
"ConcatArraysExpr",
|
|
415
|
+
"InArrayExpr",
|
|
416
|
+
"IndexOfArrayExpr",
|
|
417
|
+
"IsArrayExpr",
|
|
418
|
+
"ReverseArrayExpr",
|
|
419
|
+
"SortArrayExpr",
|
|
420
|
+
"RangeExpr",
|
|
421
|
+
"FirstNExpr",
|
|
422
|
+
"LastNExpr",
|
|
423
|
+
"MaxNExpr",
|
|
424
|
+
"MinNExpr",
|
|
425
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Base classes for MongoDB expression operators."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
from mongo_aggro.base import serialize_value
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from mongo_aggro.expressions.comparison import (
|
|
11
|
+
EqExpr,
|
|
12
|
+
GteExpr,
|
|
13
|
+
GtExpr,
|
|
14
|
+
LteExpr,
|
|
15
|
+
LtExpr,
|
|
16
|
+
NeExpr,
|
|
17
|
+
)
|
|
18
|
+
from mongo_aggro.expressions.logical import AndExpr, NotExpr, OrExpr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Field:
|
|
22
|
+
"""
|
|
23
|
+
Field reference with Python operator overloading support.
|
|
24
|
+
|
|
25
|
+
Enables natural Python syntax for building MongoDB expressions:
|
|
26
|
+
>>> (F("status") == "active") & (F("age") > 18)
|
|
27
|
+
|
|
28
|
+
Note: Use & instead of 'and', | instead of 'or', ~ instead of 'not'.
|
|
29
|
+
Parentheses are required due to operator precedence.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> F("age") > 18
|
|
33
|
+
GtExpr(left=Field("age"), right=18)
|
|
34
|
+
|
|
35
|
+
>>> (F("status") == "active") & (F("age") > 18)
|
|
36
|
+
AndExpr([EqExpr(...), GtExpr(...)])
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
__slots__ = ("_path",)
|
|
40
|
+
|
|
41
|
+
def __init__(self, path: str) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Initialize a field reference.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path: Field path (with or without $ prefix)
|
|
47
|
+
"""
|
|
48
|
+
self._path = path if path.startswith("$") else f"${path}"
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
"""Return the field path with $ prefix."""
|
|
52
|
+
return self._path
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
"""Return a string representation for debugging."""
|
|
56
|
+
return f"Field({self._path!r})"
|
|
57
|
+
|
|
58
|
+
def __hash__(self) -> int:
|
|
59
|
+
"""Make Field hashable."""
|
|
60
|
+
return hash(self._path)
|
|
61
|
+
|
|
62
|
+
# Comparison operators - return expression objects
|
|
63
|
+
def __eq__(self, other: Any) -> "EqExpr": # type: ignore[override]
|
|
64
|
+
"""Create equality expression: F("field") == value."""
|
|
65
|
+
from mongo_aggro.expressions.comparison import EqExpr
|
|
66
|
+
|
|
67
|
+
return EqExpr(left=self, right=other)
|
|
68
|
+
|
|
69
|
+
def __ne__(self, other: Any) -> "NeExpr": # type: ignore[override]
|
|
70
|
+
"""Create not-equal expression: F("field") != value."""
|
|
71
|
+
from mongo_aggro.expressions.comparison import NeExpr
|
|
72
|
+
|
|
73
|
+
return NeExpr(left=self, right=other)
|
|
74
|
+
|
|
75
|
+
def __gt__(self, other: Any) -> "GtExpr":
|
|
76
|
+
"""Create greater-than expression: F("field") > value."""
|
|
77
|
+
from mongo_aggro.expressions.comparison import GtExpr
|
|
78
|
+
|
|
79
|
+
return GtExpr(left=self, right=other)
|
|
80
|
+
|
|
81
|
+
def __ge__(self, other: Any) -> "GteExpr":
|
|
82
|
+
"""Create greater-than-or-equal expression: F("field") >= value."""
|
|
83
|
+
from mongo_aggro.expressions.comparison import GteExpr
|
|
84
|
+
|
|
85
|
+
return GteExpr(left=self, right=other)
|
|
86
|
+
|
|
87
|
+
def __lt__(self, other: Any) -> "LtExpr":
|
|
88
|
+
"""Create less-than expression: F("field") < value."""
|
|
89
|
+
from mongo_aggro.expressions.comparison import LtExpr
|
|
90
|
+
|
|
91
|
+
return LtExpr(left=self, right=other)
|
|
92
|
+
|
|
93
|
+
def __le__(self, other: Any) -> "LteExpr":
|
|
94
|
+
"""Create less-than-or-equal expression: F("field") <= value."""
|
|
95
|
+
from mongo_aggro.expressions.comparison import LteExpr
|
|
96
|
+
|
|
97
|
+
return LteExpr(left=self, right=other)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def F(path: str) -> Field:
|
|
101
|
+
"""
|
|
102
|
+
Create a field reference with operator overloading support.
|
|
103
|
+
|
|
104
|
+
This is the primary way to reference document fields in expressions.
|
|
105
|
+
Returns a Field object that supports Python comparison operators.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
path: Field path (e.g., "status", "user.name", "$existing_ref")
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Field object with operator overloading
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> F("status") == "active"
|
|
115
|
+
EqExpr(left=Field("$status"), right="active")
|
|
116
|
+
|
|
117
|
+
>>> F("price") > F("cost")
|
|
118
|
+
GtExpr(left=Field("$price"), right=Field("$cost"))
|
|
119
|
+
"""
|
|
120
|
+
return Field(path)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ExpressionBase(BaseModel):
|
|
124
|
+
"""
|
|
125
|
+
Base class for all MongoDB expression operators.
|
|
126
|
+
|
|
127
|
+
Provides logical operator support (&, |, ~) for combining expressions.
|
|
128
|
+
All expression subclasses should inherit from this class.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
model_config = ConfigDict(
|
|
132
|
+
populate_by_name=True,
|
|
133
|
+
extra="forbid",
|
|
134
|
+
arbitrary_types_allowed=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def __and__(self, other: "ExpressionBase | dict[str, Any]") -> "AndExpr":
|
|
138
|
+
"""
|
|
139
|
+
Combine expressions with AND: expr1 & expr2.
|
|
140
|
+
|
|
141
|
+
Automatically flattens nested ANDs for cleaner output.
|
|
142
|
+
"""
|
|
143
|
+
from mongo_aggro.expressions.logical import AndExpr
|
|
144
|
+
|
|
145
|
+
left = self.conditions if isinstance(self, AndExpr) else [self]
|
|
146
|
+
if isinstance(other, AndExpr):
|
|
147
|
+
right = other.conditions
|
|
148
|
+
else:
|
|
149
|
+
right = [other]
|
|
150
|
+
return AndExpr(conditions=left + right)
|
|
151
|
+
|
|
152
|
+
def __or__(self, other: "ExpressionBase | dict[str, Any]") -> "OrExpr":
|
|
153
|
+
"""
|
|
154
|
+
Combine expressions with OR: expr1 | expr2.
|
|
155
|
+
|
|
156
|
+
Automatically flattens nested ORs for cleaner output.
|
|
157
|
+
"""
|
|
158
|
+
from mongo_aggro.expressions.logical import OrExpr
|
|
159
|
+
|
|
160
|
+
left = self.conditions if isinstance(self, OrExpr) else [self]
|
|
161
|
+
if isinstance(other, OrExpr):
|
|
162
|
+
right = other.conditions
|
|
163
|
+
else:
|
|
164
|
+
right = [other]
|
|
165
|
+
return OrExpr(conditions=left + right)
|
|
166
|
+
|
|
167
|
+
def __invert__(self) -> "NotExpr":
|
|
168
|
+
"""Negate expression with NOT: ~expr."""
|
|
169
|
+
from mongo_aggro.expressions.logical import NotExpr
|
|
170
|
+
|
|
171
|
+
return NotExpr(condition=self)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Re-export serialize_value for use by expression modules
|
|
175
|
+
__all__ = [
|
|
176
|
+
"Field",
|
|
177
|
+
"F",
|
|
178
|
+
"ExpressionBase",
|
|
179
|
+
"serialize_value",
|
|
180
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Bitwise expression operators for MongoDB aggregation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import model_serializer
|
|
6
|
+
|
|
7
|
+
from mongo_aggro.base import serialize_value
|
|
8
|
+
from mongo_aggro.expressions.base import ExpressionBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BitAndExpr(ExpressionBase):
|
|
12
|
+
"""
|
|
13
|
+
$bitAnd expression operator - bitwise AND.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> BitAndExpr(operands=[F("a"), F("b")]).model_dump()
|
|
17
|
+
{"$bitAnd": ["$a", "$b"]}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
operands: list[Any]
|
|
21
|
+
|
|
22
|
+
@model_serializer
|
|
23
|
+
def serialize(self) -> dict[str, Any]:
|
|
24
|
+
"""Serialize to MongoDB $bitAnd expression."""
|
|
25
|
+
return {"$bitAnd": [serialize_value(o) for o in self.operands]}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BitOrExpr(ExpressionBase):
|
|
29
|
+
"""
|
|
30
|
+
$bitOr expression operator - bitwise OR.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> BitOrExpr(operands=[F("a"), F("b")]).model_dump()
|
|
34
|
+
{"$bitOr": ["$a", "$b"]}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
operands: list[Any]
|
|
38
|
+
|
|
39
|
+
@model_serializer
|
|
40
|
+
def serialize(self) -> dict[str, Any]:
|
|
41
|
+
"""Serialize to MongoDB $bitOr expression."""
|
|
42
|
+
return {"$bitOr": [serialize_value(o) for o in self.operands]}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BitXorExpr(ExpressionBase):
|
|
46
|
+
"""
|
|
47
|
+
$bitXor expression operator - bitwise XOR.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> BitXorExpr(operands=[F("a"), F("b")]).model_dump()
|
|
51
|
+
{"$bitXor": ["$a", "$b"]}
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
operands: list[Any]
|
|
55
|
+
|
|
56
|
+
@model_serializer
|
|
57
|
+
def serialize(self) -> dict[str, Any]:
|
|
58
|
+
"""Serialize to MongoDB $bitXor expression."""
|
|
59
|
+
return {"$bitXor": [serialize_value(o) for o in self.operands]}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BitNotExpr(ExpressionBase):
|
|
63
|
+
"""
|
|
64
|
+
$bitNot expression operator - bitwise NOT.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> BitNotExpr(input=F("value")).model_dump()
|
|
68
|
+
{"$bitNot": "$value"}
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
input: Any
|
|
72
|
+
|
|
73
|
+
@model_serializer
|
|
74
|
+
def serialize(self) -> dict[str, Any]:
|
|
75
|
+
"""Serialize to MongoDB $bitNot expression."""
|
|
76
|
+
return {"$bitNot": serialize_value(self.input)}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
"BitAndExpr",
|
|
81
|
+
"BitOrExpr",
|
|
82
|
+
"BitXorExpr",
|
|
83
|
+
"BitNotExpr",
|
|
84
|
+
]
|