mongo-pipebuilder 0.3.1__tar.gz → 0.4.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.
- mongo_pipebuilder-0.4.0/MANIFEST.in +3 -0
- {mongo_pipebuilder-0.3.1/src/mongo_pipebuilder.egg-info → mongo_pipebuilder-0.4.0}/PKG-INFO +73 -3
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/README.md +71 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/pyproject.toml +46 -5
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/__init__.py +1 -1
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/builder.py +274 -139
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0/src/mongo_pipebuilder.egg-info}/PKG-INFO +73 -3
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/SOURCES.txt +4 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder.py +9 -10
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_debug.py +51 -50
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_insert.py +22 -21
- mongo_pipebuilder-0.4.0/tests/test_builder_lookup_let.py +167 -0
- mongo_pipebuilder-0.4.0/tests/test_builder_match_expr.py +69 -0
- mongo_pipebuilder-0.4.0/tests/test_builder_union_with.py +95 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation.py +13 -14
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_existing.py +2 -1
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_new.py +1 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/LICENSE +0 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/setup.cfg +0 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/dependency_links.txt +0 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/requires.txt +0 -0
- {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mongo-pipebuilder
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Type-safe, fluent MongoDB aggregation pipeline builder
|
|
5
5
|
Author-email: seligoroff <seligoroff@gmail.com>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/seligoroff/mongo-pipebuilder
|
|
8
8
|
Project-URL: Documentation, https://github.com/seligoroff/mongo-pipebuilder#readme
|
|
9
9
|
Project-URL: Repository, https://github.com/seligoroff/mongo-pipebuilder
|
|
@@ -11,7 +11,6 @@ Project-URL: Issues, https://github.com/seligoroff/mongo-pipebuilder/issues
|
|
|
11
11
|
Keywords: mongodb,aggregation,pipeline,builder,query
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -98,6 +97,15 @@ Adds a `$match` stage to filter documents.
|
|
|
98
97
|
.match({"status": "active", "age": {"$gte": 18}})
|
|
99
98
|
```
|
|
100
99
|
|
|
100
|
+
##### `match_expr(expr: Dict[str, Any]) -> Self`
|
|
101
|
+
|
|
102
|
+
Adds a `$match` stage with an `$expr` condition (expression-based filter; useful for comparing fields or using variables from `let` in subpipelines).
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
.match_expr({"$eq": ["$id", "$$teamId"]})
|
|
106
|
+
.match_expr({"$and": [{"$gte": ["$field", "$other"]}, {"$lte": ["$score", 100]}]})
|
|
107
|
+
```
|
|
108
|
+
|
|
101
109
|
##### `lookup(from_collection: str, local_field: str, foreign_field: str, as_field: str, pipeline: Optional[List[Dict[str, Any]]] = None) -> Self`
|
|
102
110
|
|
|
103
111
|
Adds a `$lookup` stage to join with another collection.
|
|
@@ -112,6 +120,43 @@ Adds a `$lookup` stage to join with another collection.
|
|
|
112
120
|
)
|
|
113
121
|
```
|
|
114
122
|
|
|
123
|
+
##### `lookup_let(from_collection: str, let: Dict[str, Any], pipeline: Union[List[Dict[str, Any]], PipelineBuilder], as_field: str) -> Self`
|
|
124
|
+
|
|
125
|
+
Adds a `$lookup` stage with `let` and `pipeline` (join by expression; variables from the current document are available in the subpipeline as `$$var`). Use this when the join condition is an expression (e.g. `$expr`) rather than equality of two fields.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# With list of stages
|
|
129
|
+
.lookup_let(
|
|
130
|
+
from_collection="teams",
|
|
131
|
+
let={"teamId": "$idTeam"},
|
|
132
|
+
pipeline=[
|
|
133
|
+
{"$match": {"$expr": {"$eq": ["$_id", "$$teamId"]}}},
|
|
134
|
+
{"$project": {"name": 1, "_id": 0}}
|
|
135
|
+
],
|
|
136
|
+
as_field="team"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# With PipelineBuilder for the subpipeline (optionally using match_expr)
|
|
140
|
+
sub = PipelineBuilder().match_expr({"$eq": ["$_id", "$$teamId"]}).project({"name": 1, "_id": 0})
|
|
141
|
+
.lookup_let("teams", {"teamId": "$idTeam"}, sub, as_field="team")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
##### `union_with(coll: str, pipeline: Optional[Union[List[Dict[str, Any]], PipelineBuilder]] = None) -> Self`
|
|
145
|
+
|
|
146
|
+
Adds a `$unionWith` stage to combine documents from the current pipeline with documents from another collection. Optionally runs a subpipeline on the other collection before merging.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# Union with another collection (no subpipeline)
|
|
150
|
+
.union_with("other_coll")
|
|
151
|
+
|
|
152
|
+
# With subpipeline as list of stages
|
|
153
|
+
.union_with("logs", [{"$match": {"level": "error"}}, {"$limit": 100}])
|
|
154
|
+
|
|
155
|
+
# With PipelineBuilder for the subpipeline
|
|
156
|
+
sub = PipelineBuilder().match({"source": "individual"}).project({"name": 1})
|
|
157
|
+
.union_with("sso_individual_statistics", sub)
|
|
158
|
+
```
|
|
159
|
+
|
|
115
160
|
##### `add_fields(fields: Dict[str, Any]) -> Self`
|
|
116
161
|
|
|
117
162
|
Adds a `$addFields` stage to add or modify fields.
|
|
@@ -411,6 +456,31 @@ pipeline = (
|
|
|
411
456
|
)
|
|
412
457
|
```
|
|
413
458
|
|
|
459
|
+
### Lookup by expression (lookup_let)
|
|
460
|
+
|
|
461
|
+
When the join condition is an expression (e.g. `$expr`) rather than matching two fields, use `lookup_let`. The subpipeline can be built with `match_expr()`:
|
|
462
|
+
|
|
463
|
+
```python
|
|
464
|
+
sub = (
|
|
465
|
+
PipelineBuilder()
|
|
466
|
+
.match_expr({"$eq": ["$_id", "$$teamId"]})
|
|
467
|
+
.project({"name": 1, "slug": 1, "_id": 0})
|
|
468
|
+
)
|
|
469
|
+
pipeline = (
|
|
470
|
+
PipelineBuilder()
|
|
471
|
+
.match({"status": "active"})
|
|
472
|
+
.lookup_let(
|
|
473
|
+
from_collection="teams",
|
|
474
|
+
let={"teamId": "$idTeam"},
|
|
475
|
+
pipeline=sub,
|
|
476
|
+
as_field="team"
|
|
477
|
+
)
|
|
478
|
+
.unwind("team", preserve_null_and_empty_arrays=True)
|
|
479
|
+
.project({"title": 1, "teamName": "$team.name"})
|
|
480
|
+
.build()
|
|
481
|
+
)
|
|
482
|
+
```
|
|
483
|
+
|
|
414
484
|
### Aggregation with Grouping
|
|
415
485
|
|
|
416
486
|
```python
|
|
@@ -70,6 +70,15 @@ Adds a `$match` stage to filter documents.
|
|
|
70
70
|
.match({"status": "active", "age": {"$gte": 18}})
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
##### `match_expr(expr: Dict[str, Any]) -> Self`
|
|
74
|
+
|
|
75
|
+
Adds a `$match` stage with an `$expr` condition (expression-based filter; useful for comparing fields or using variables from `let` in subpipelines).
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
.match_expr({"$eq": ["$id", "$$teamId"]})
|
|
79
|
+
.match_expr({"$and": [{"$gte": ["$field", "$other"]}, {"$lte": ["$score", 100]}]})
|
|
80
|
+
```
|
|
81
|
+
|
|
73
82
|
##### `lookup(from_collection: str, local_field: str, foreign_field: str, as_field: str, pipeline: Optional[List[Dict[str, Any]]] = None) -> Self`
|
|
74
83
|
|
|
75
84
|
Adds a `$lookup` stage to join with another collection.
|
|
@@ -84,6 +93,43 @@ Adds a `$lookup` stage to join with another collection.
|
|
|
84
93
|
)
|
|
85
94
|
```
|
|
86
95
|
|
|
96
|
+
##### `lookup_let(from_collection: str, let: Dict[str, Any], pipeline: Union[List[Dict[str, Any]], PipelineBuilder], as_field: str) -> Self`
|
|
97
|
+
|
|
98
|
+
Adds a `$lookup` stage with `let` and `pipeline` (join by expression; variables from the current document are available in the subpipeline as `$$var`). Use this when the join condition is an expression (e.g. `$expr`) rather than equality of two fields.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# With list of stages
|
|
102
|
+
.lookup_let(
|
|
103
|
+
from_collection="teams",
|
|
104
|
+
let={"teamId": "$idTeam"},
|
|
105
|
+
pipeline=[
|
|
106
|
+
{"$match": {"$expr": {"$eq": ["$_id", "$$teamId"]}}},
|
|
107
|
+
{"$project": {"name": 1, "_id": 0}}
|
|
108
|
+
],
|
|
109
|
+
as_field="team"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# With PipelineBuilder for the subpipeline (optionally using match_expr)
|
|
113
|
+
sub = PipelineBuilder().match_expr({"$eq": ["$_id", "$$teamId"]}).project({"name": 1, "_id": 0})
|
|
114
|
+
.lookup_let("teams", {"teamId": "$idTeam"}, sub, as_field="team")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
##### `union_with(coll: str, pipeline: Optional[Union[List[Dict[str, Any]], PipelineBuilder]] = None) -> Self`
|
|
118
|
+
|
|
119
|
+
Adds a `$unionWith` stage to combine documents from the current pipeline with documents from another collection. Optionally runs a subpipeline on the other collection before merging.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# Union with another collection (no subpipeline)
|
|
123
|
+
.union_with("other_coll")
|
|
124
|
+
|
|
125
|
+
# With subpipeline as list of stages
|
|
126
|
+
.union_with("logs", [{"$match": {"level": "error"}}, {"$limit": 100}])
|
|
127
|
+
|
|
128
|
+
# With PipelineBuilder for the subpipeline
|
|
129
|
+
sub = PipelineBuilder().match({"source": "individual"}).project({"name": 1})
|
|
130
|
+
.union_with("sso_individual_statistics", sub)
|
|
131
|
+
```
|
|
132
|
+
|
|
87
133
|
##### `add_fields(fields: Dict[str, Any]) -> Self`
|
|
88
134
|
|
|
89
135
|
Adds a `$addFields` stage to add or modify fields.
|
|
@@ -383,6 +429,31 @@ pipeline = (
|
|
|
383
429
|
)
|
|
384
430
|
```
|
|
385
431
|
|
|
432
|
+
### Lookup by expression (lookup_let)
|
|
433
|
+
|
|
434
|
+
When the join condition is an expression (e.g. `$expr`) rather than matching two fields, use `lookup_let`. The subpipeline can be built with `match_expr()`:
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
sub = (
|
|
438
|
+
PipelineBuilder()
|
|
439
|
+
.match_expr({"$eq": ["$_id", "$$teamId"]})
|
|
440
|
+
.project({"name": 1, "slug": 1, "_id": 0})
|
|
441
|
+
)
|
|
442
|
+
pipeline = (
|
|
443
|
+
PipelineBuilder()
|
|
444
|
+
.match({"status": "active"})
|
|
445
|
+
.lookup_let(
|
|
446
|
+
from_collection="teams",
|
|
447
|
+
let={"teamId": "$idTeam"},
|
|
448
|
+
pipeline=sub,
|
|
449
|
+
as_field="team"
|
|
450
|
+
)
|
|
451
|
+
.unwind("team", preserve_null_and_empty_arrays=True)
|
|
452
|
+
.project({"title": 1, "teamName": "$team.name"})
|
|
453
|
+
.build()
|
|
454
|
+
)
|
|
455
|
+
```
|
|
456
|
+
|
|
386
457
|
### Aggregation with Grouping
|
|
387
458
|
|
|
388
459
|
```python
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mongo-pipebuilder"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Type-safe, fluent MongoDB aggregation pipeline builder"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"typing_extensions>=4.0.0; python_version<'3.11'",
|
|
13
13
|
]
|
|
14
|
-
license =
|
|
14
|
+
license = "MIT"
|
|
15
15
|
authors = [
|
|
16
16
|
{name = "seligoroff", email = "seligoroff@gmail.com"}
|
|
17
17
|
]
|
|
@@ -19,7 +19,6 @@ keywords = ["mongodb", "aggregation", "pipeline", "builder", "query"]
|
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Development Status :: 3 - Alpha",
|
|
21
21
|
"Intended Audience :: Developers",
|
|
22
|
-
"License :: OSI Approved :: MIT License",
|
|
23
22
|
"Programming Language :: Python :: 3",
|
|
24
23
|
"Programming Language :: Python :: 3.8",
|
|
25
24
|
"Programming Language :: Python :: 3.9",
|
|
@@ -51,12 +50,13 @@ addopts = [
|
|
|
51
50
|
"--cov=src/mongo_pipebuilder",
|
|
52
51
|
"--cov-report=term-missing",
|
|
53
52
|
"--cov-report=html:tests/htmlcov",
|
|
54
|
-
"--cov-report=json:coverage.json",
|
|
55
|
-
"--cov-report=xml:coverage.xml",
|
|
53
|
+
"--cov-report=json:tests/coverage.json",
|
|
54
|
+
"--cov-report=xml:tests/coverage.xml",
|
|
56
55
|
"--cov-branch",
|
|
57
56
|
]
|
|
58
57
|
|
|
59
58
|
[tool.coverage.run]
|
|
59
|
+
data_file = "tests/.coverage"
|
|
60
60
|
source = ["src/mongo_pipebuilder"]
|
|
61
61
|
omit = [
|
|
62
62
|
"*/tests/*",
|
|
@@ -99,3 +99,44 @@ warn_no_return = true
|
|
|
99
99
|
module = "tests.*"
|
|
100
100
|
disallow_untyped_defs = false
|
|
101
101
|
|
|
102
|
+
[tool.ruff]
|
|
103
|
+
line-length = 110
|
|
104
|
+
target-version = "py311"
|
|
105
|
+
# В Ruff лучше использовать .gitignore, но если нужно строго как было:
|
|
106
|
+
#respect-gitignore = false
|
|
107
|
+
#extend-exclude = ["venv", "deprecated"]
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint]
|
|
110
|
+
# E, F — база (Flake8), B — Bugbear (ошибки логики), I — Isort (импорты)
|
|
111
|
+
select = ["E", "F", "W", "B", "C901", "I", "Q", "T20", "COM"]
|
|
112
|
+
ignore = [
|
|
113
|
+
"B008", # Аргументы-функции в дефолтных значениях (часто нужно для FastAPI Depends)
|
|
114
|
+
"E266", # Слишком много # в комментариях
|
|
115
|
+
"E712", # Сравнение с True через == (иногда нужно в SQLAlchemy)
|
|
116
|
+
"COM812", # Конфликт с форматтером (запятые)
|
|
117
|
+
"COM819", # Trailing comma в type hints
|
|
118
|
+
"E203", # Пробел перед двоеточием (совместимость с Black)
|
|
119
|
+
"Q003", # Кавычки в f-строках
|
|
120
|
+
"PT006", # Типы параметров pytest
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[tool.ruff.lint.per-file-ignores]
|
|
124
|
+
# Игнорируем специфичные ошибки в тестах, скриптах, примерах и инициализации
|
|
125
|
+
"tests/*" = ["T201"]
|
|
126
|
+
"examples/*" = ["T201"]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
[tool.ruff.lint.mccabe]
|
|
130
|
+
max-complexity = 12
|
|
131
|
+
|
|
132
|
+
[tool.ruff.lint.isort]
|
|
133
|
+
# Автоматически определяет внутренние модули, чтобы не мешать их с pip-пакетами
|
|
134
|
+
known-first-party = [
|
|
135
|
+
"api", "cli", "constants", "core", "helpers",
|
|
136
|
+
"infrastructure", "schemas", "scripts", "services",
|
|
137
|
+
"startup", "tasks", "tests"
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
[tool.ruff.format]
|
|
141
|
+
quote-style = "double"
|
|
142
|
+
indent-style = "space"
|