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.
Files changed (22) hide show
  1. mongo_pipebuilder-0.4.0/MANIFEST.in +3 -0
  2. {mongo_pipebuilder-0.3.1/src/mongo_pipebuilder.egg-info → mongo_pipebuilder-0.4.0}/PKG-INFO +73 -3
  3. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/README.md +71 -0
  4. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/pyproject.toml +46 -5
  5. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/__init__.py +1 -1
  6. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/builder.py +274 -139
  7. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0/src/mongo_pipebuilder.egg-info}/PKG-INFO +73 -3
  8. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/SOURCES.txt +4 -0
  9. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder.py +9 -10
  10. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_debug.py +51 -50
  11. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_insert.py +22 -21
  12. mongo_pipebuilder-0.4.0/tests/test_builder_lookup_let.py +167 -0
  13. mongo_pipebuilder-0.4.0/tests/test_builder_match_expr.py +69 -0
  14. mongo_pipebuilder-0.4.0/tests/test_builder_union_with.py +95 -0
  15. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation.py +13 -14
  16. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_existing.py +2 -1
  17. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_new.py +1 -0
  18. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/LICENSE +0 -0
  19. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/setup.cfg +0 -0
  20. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/dependency_links.txt +0 -0
  21. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/requires.txt +0 -0
  22. {mongo_pipebuilder-0.3.1 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/top_level.txt +0 -0
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mongo-pipebuilder
3
- Version: 0.3.1
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.3.1"
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 = {text = "MIT"}
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"
@@ -9,6 +9,6 @@ Author: seligoroff
9
9
 
10
10
  from mongo_pipebuilder.builder import PipelineBuilder
11
11
 
12
- __version__ = "0.3.1"
12
+ __version__ = "0.4.0"
13
13
  __all__ = ["PipelineBuilder"]
14
14