mongo-pipebuilder 0.3.0__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.3.0 → mongo_pipebuilder-0.4.0}/LICENSE +0 -0
  2. mongo_pipebuilder-0.4.0/MANIFEST.in +3 -0
  3. {mongo_pipebuilder-0.3.0/src/mongo_pipebuilder.egg-info → mongo_pipebuilder-0.4.0}/PKG-INFO +123 -5
  4. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/README.md +121 -2
  5. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/pyproject.toml +46 -5
  6. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/setup.cfg +0 -0
  7. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/__init__.py +1 -1
  8. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder/builder.py +371 -134
  9. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0/src/mongo_pipebuilder.egg-info}/PKG-INFO +123 -5
  10. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/SOURCES.txt +4 -0
  11. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/dependency_links.txt +0 -0
  12. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/requires.txt +0 -0
  13. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/src/mongo_pipebuilder.egg-info/top_level.txt +0 -0
  14. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder.py +9 -10
  15. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder_debug.py +102 -50
  16. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder_insert.py +25 -24
  17. mongo_pipebuilder-0.4.0/tests/test_builder_lookup_let.py +167 -0
  18. mongo_pipebuilder-0.4.0/tests/test_builder_match_expr.py +69 -0
  19. mongo_pipebuilder-0.4.0/tests/test_builder_union_with.py +95 -0
  20. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation.py +13 -14
  21. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_existing.py +8 -1
  22. {mongo_pipebuilder-0.3.0 → mongo_pipebuilder-0.4.0}/tests/test_builder_validation_new.py +1 -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.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.
@@ -252,7 +297,7 @@ builder.prepend({"$match": {"deleted": False}})
252
297
  Inserts a stage at a specific position (0-based index) in the pipeline.
253
298
 
254
299
  ```python
255
- builder.match({"status": "active"}).group({"_id": "$category"}, {"count": {"$sum": 1}})
300
+ builder.match({"status": "active"}).group("$category", {"count": {"$sum": 1}})
256
301
  builder.insert_at(1, {"$sort": {"name": 1}})
257
302
  # Pipeline: [{"$match": {...}}, {"$sort": {...}}, {"$group": {...}}]
258
303
  ```
@@ -327,6 +372,15 @@ print(builder.pretty_print())
327
372
  # ]
328
373
  ```
329
374
 
375
+ ##### `pretty_print_stage(stage: Union[int, Dict[str, Any]], indent: int = 2, ensure_ascii: bool = False) -> str`
376
+
377
+ Returns a formatted JSON string representation of a single stage (by index or by dict).
378
+
379
+ ```python
380
+ builder = PipelineBuilder().match({"status": "active"}).limit(10)
381
+ print(builder.pretty_print_stage(0)) # Prints the $match stage
382
+ ```
383
+
330
384
  ##### `to_json_file(filepath: Union[str, Path], indent: int = 2, ensure_ascii: bool = False, metadata: Optional[Dict[str, Any]] = None) -> None`
331
385
 
332
386
  Saves the pipeline to a JSON file. Useful for debugging, comparison, or versioning.
@@ -345,6 +399,17 @@ builder.to_json_file(
345
399
  )
346
400
  ```
347
401
 
402
+ ##### `compare_with(other: PipelineBuilder, context_lines: int = 3) -> str`
403
+
404
+ Returns a unified diff between two pipelines (useful for comparing “new” builder pipelines vs legacy/template pipelines).
405
+
406
+ ```python
407
+ legacy = PipelineBuilder().match({"status": "active"}).limit(10)
408
+ new = PipelineBuilder().match({"status": "inactive"}).limit(10)
409
+
410
+ print(new.compare_with(legacy))
411
+ ```
412
+
348
413
  ##### `build() -> List[Dict[str, Any]]`
349
414
 
350
415
  Returns the complete pipeline as a list of stage dictionaries.
@@ -391,6 +456,31 @@ pipeline = (
391
456
  )
392
457
  ```
393
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
+
394
484
  ### Aggregation with Grouping
395
485
 
396
486
  ```python
@@ -500,11 +590,39 @@ base = get_base_pipeline(user_id)
500
590
  # Create multiple queries from cached base
501
591
  recent = base.copy().sort({"createdAt": -1}).limit(10).build()
502
592
  by_category = base.copy().match({"category": "tech"}).build()
503
- with_stats = base.copy().group({"_id": "$category"}, {"count": {"$sum": 1}}).build()
593
+ with_stats = base.copy().group("$category", {"count": {"$sum": 1}}).build()
504
594
 
505
595
  # Base pipeline is safely cached and reused
506
596
  ```
507
597
 
598
+ ## Best Practices
599
+
600
+ ### Array `_id` after `$group`: prefer `$arrayElemAt` and materialize fields
601
+
602
+ If you use `$group` with an array `_id` (e.g. `["_idSeason", "_idTournament"]`), avoid relying on `$_id` later in the pipeline.
603
+ Instead, **extract elements with `$arrayElemAt` and store them into explicit fields**, then use those fields in subsequent stages.
604
+
605
+ ```python
606
+ pipeline = (
607
+ PipelineBuilder()
608
+ .group(
609
+ group_by=["$idSeason", "$idTournament"],
610
+ accumulators={"idTeams": {"$addToSet": "$idTeam"}},
611
+ )
612
+ .project({
613
+ "idSeason": {"$arrayElemAt": ["$_id", 0]},
614
+ "idTournament": {"$arrayElemAt": ["$_id", 1]},
615
+ "idTeams": 1,
616
+ # Optional: preserve array _id explicitly if you really need it later
617
+ # "_id": "$_id",
618
+ })
619
+ .build()
620
+ )
621
+ ```
622
+
623
+ This pattern reduces surprises and helps avoid errors like:
624
+ `$first's argument must be an array, but is object`.
625
+
508
626
  #### Example: Pipeline Factories
509
627
 
510
628
  ```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.
@@ -224,7 +270,7 @@ builder.prepend({"$match": {"deleted": False}})
224
270
  Inserts a stage at a specific position (0-based index) in the pipeline.
225
271
 
226
272
  ```python
227
- builder.match({"status": "active"}).group({"_id": "$category"}, {"count": {"$sum": 1}})
273
+ builder.match({"status": "active"}).group("$category", {"count": {"$sum": 1}})
228
274
  builder.insert_at(1, {"$sort": {"name": 1}})
229
275
  # Pipeline: [{"$match": {...}}, {"$sort": {...}}, {"$group": {...}}]
230
276
  ```
@@ -299,6 +345,15 @@ print(builder.pretty_print())
299
345
  # ]
300
346
  ```
301
347
 
348
+ ##### `pretty_print_stage(stage: Union[int, Dict[str, Any]], indent: int = 2, ensure_ascii: bool = False) -> str`
349
+
350
+ Returns a formatted JSON string representation of a single stage (by index or by dict).
351
+
352
+ ```python
353
+ builder = PipelineBuilder().match({"status": "active"}).limit(10)
354
+ print(builder.pretty_print_stage(0)) # Prints the $match stage
355
+ ```
356
+
302
357
  ##### `to_json_file(filepath: Union[str, Path], indent: int = 2, ensure_ascii: bool = False, metadata: Optional[Dict[str, Any]] = None) -> None`
303
358
 
304
359
  Saves the pipeline to a JSON file. Useful for debugging, comparison, or versioning.
@@ -317,6 +372,17 @@ builder.to_json_file(
317
372
  )
318
373
  ```
319
374
 
375
+ ##### `compare_with(other: PipelineBuilder, context_lines: int = 3) -> str`
376
+
377
+ Returns a unified diff between two pipelines (useful for comparing “new” builder pipelines vs legacy/template pipelines).
378
+
379
+ ```python
380
+ legacy = PipelineBuilder().match({"status": "active"}).limit(10)
381
+ new = PipelineBuilder().match({"status": "inactive"}).limit(10)
382
+
383
+ print(new.compare_with(legacy))
384
+ ```
385
+
320
386
  ##### `build() -> List[Dict[str, Any]]`
321
387
 
322
388
  Returns the complete pipeline as a list of stage dictionaries.
@@ -363,6 +429,31 @@ pipeline = (
363
429
  )
364
430
  ```
365
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
+
366
457
  ### Aggregation with Grouping
367
458
 
368
459
  ```python
@@ -472,11 +563,39 @@ base = get_base_pipeline(user_id)
472
563
  # Create multiple queries from cached base
473
564
  recent = base.copy().sort({"createdAt": -1}).limit(10).build()
474
565
  by_category = base.copy().match({"category": "tech"}).build()
475
- with_stats = base.copy().group({"_id": "$category"}, {"count": {"$sum": 1}}).build()
566
+ with_stats = base.copy().group("$category", {"count": {"$sum": 1}}).build()
476
567
 
477
568
  # Base pipeline is safely cached and reused
478
569
  ```
479
570
 
571
+ ## Best Practices
572
+
573
+ ### Array `_id` after `$group`: prefer `$arrayElemAt` and materialize fields
574
+
575
+ If you use `$group` with an array `_id` (e.g. `["_idSeason", "_idTournament"]`), avoid relying on `$_id` later in the pipeline.
576
+ Instead, **extract elements with `$arrayElemAt` and store them into explicit fields**, then use those fields in subsequent stages.
577
+
578
+ ```python
579
+ pipeline = (
580
+ PipelineBuilder()
581
+ .group(
582
+ group_by=["$idSeason", "$idTournament"],
583
+ accumulators={"idTeams": {"$addToSet": "$idTeam"}},
584
+ )
585
+ .project({
586
+ "idSeason": {"$arrayElemAt": ["$_id", 0]},
587
+ "idTournament": {"$arrayElemAt": ["$_id", 1]},
588
+ "idTeams": 1,
589
+ # Optional: preserve array _id explicitly if you really need it later
590
+ # "_id": "$_id",
591
+ })
592
+ .build()
593
+ )
594
+ ```
595
+
596
+ This pattern reduces surprises and helps avoid errors like:
597
+ `$first's argument must be an array, but is object`.
598
+
480
599
  #### Example: Pipeline Factories
481
600
 
482
601
  ```python
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mongo-pipebuilder"
7
- version = "0.3.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 = {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.0"
12
+ __version__ = "0.4.0"
13
13
  __all__ = ["PipelineBuilder"]
14
14