mongo-pipebuilder 0.4.0__tar.gz → 0.5.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 (23) hide show
  1. {mongo_pipebuilder-0.4.0/src/mongo_pipebuilder.egg-info → mongo_pipebuilder-0.5.0}/PKG-INFO +16 -4
  2. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/README.md +14 -1
  3. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/pyproject.toml +3 -4
  4. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder/__init__.py +1 -1
  5. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder/builder.py +31 -2
  6. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0/src/mongo_pipebuilder.egg-info}/PKG-INFO +16 -4
  7. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder.egg-info/SOURCES.txt +1 -0
  8. mongo_pipebuilder-0.5.0/tests/test_builder_add_stages.py +95 -0
  9. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/LICENSE +0 -0
  10. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/MANIFEST.in +0 -0
  11. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/setup.cfg +0 -0
  12. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder.egg-info/dependency_links.txt +0 -0
  13. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder.egg-info/requires.txt +0 -0
  14. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/src/mongo_pipebuilder.egg-info/top_level.txt +0 -0
  15. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder.py +0 -0
  16. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_debug.py +0 -0
  17. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_insert.py +0 -0
  18. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_lookup_let.py +0 -0
  19. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_match_expr.py +0 -0
  20. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_union_with.py +0 -0
  21. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_validation.py +0 -0
  22. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_validation_existing.py +0 -0
  23. {mongo_pipebuilder-0.4.0 → mongo_pipebuilder-0.5.0}/tests/test_builder_validation_new.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mongo-pipebuilder
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Type-safe, fluent MongoDB aggregation pipeline builder
5
5
  Author-email: seligoroff <seligoroff@gmail.com>
6
6
  License-Expression: MIT
@@ -12,14 +12,13 @@ Keywords: mongodb,aggregation,pipeline,builder,query
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Topic :: Database
21
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Python: >=3.8
21
+ Requires-Python: >=3.9
23
22
  Description-Content-Type: text/markdown
24
23
  License-File: LICENSE
25
24
  Requires-Dist: typing_extensions>=4.0.0; python_version < "3.11"
@@ -28,7 +27,7 @@ Dynamic: license-file
28
27
  # mongo-pipebuilder
29
28
 
30
29
  [![PyPI version](https://badge.fury.io/py/mongo-pipebuilder.svg)](https://badge.fury.io/py/mongo-pipebuilder)
31
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
30
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
32
31
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
32
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
34
33
  [![Test Coverage](https://img.shields.io/badge/coverage-96%25-green.svg)](https://github.com/seligoroff/mongo-pipebuilder)
@@ -282,6 +281,19 @@ Adds a custom stage for advanced use cases.
282
281
  }})
283
282
  ```
284
283
 
284
+ ##### `add_stages(stages: Iterable[Dict[str, Any]]) -> Self`
285
+
286
+ Adds multiple stages at once (e.g. a subpipeline from another builder). Empty dicts are skipped. Useful to avoid loops when inserting a ready-made list of stages.
287
+
288
+ ```python
289
+ # From a list
290
+ .add_stages([{"$match": {"level": "error"}}, {"$limit": 100}])
291
+
292
+ # From another builder
293
+ sub = PipelineBuilder().match({"source": "api"}).project({"name": 1})
294
+ .add_stages(sub.build())
295
+ ```
296
+
285
297
  ##### `prepend(stage: Dict[str, Any]) -> Self`
286
298
 
287
299
  Adds a stage at the beginning of the pipeline.
@@ -1,7 +1,7 @@
1
1
  # mongo-pipebuilder
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/mongo-pipebuilder.svg)](https://badge.fury.io/py/mongo-pipebuilder)
4
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
4
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
7
7
  [![Test Coverage](https://img.shields.io/badge/coverage-96%25-green.svg)](https://github.com/seligoroff/mongo-pipebuilder)
@@ -255,6 +255,19 @@ Adds a custom stage for advanced use cases.
255
255
  }})
256
256
  ```
257
257
 
258
+ ##### `add_stages(stages: Iterable[Dict[str, Any]]) -> Self`
259
+
260
+ Adds multiple stages at once (e.g. a subpipeline from another builder). Empty dicts are skipped. Useful to avoid loops when inserting a ready-made list of stages.
261
+
262
+ ```python
263
+ # From a list
264
+ .add_stages([{"$match": {"level": "error"}}, {"$limit": 100}])
265
+
266
+ # From another builder
267
+ sub = PipelineBuilder().match({"source": "api"}).project({"name": 1})
268
+ .add_stages(sub.build())
269
+ ```
270
+
258
271
  ##### `prepend(stage: Dict[str, Any]) -> Self`
259
272
 
260
273
  Adds a stage at the beginning of the pipeline.
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mongo-pipebuilder"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Type-safe, fluent MongoDB aggregation pipeline builder"
9
9
  readme = "README.md"
10
- requires-python = ">=3.8"
10
+ requires-python = ">=3.9"
11
11
  dependencies = [
12
12
  "typing_extensions>=4.0.0; python_version<'3.11'",
13
13
  ]
@@ -20,7 +20,6 @@ classifiers = [
20
20
  "Development Status :: 3 - Alpha",
21
21
  "Intended Audience :: Developers",
22
22
  "Programming Language :: Python :: 3",
23
- "Programming Language :: Python :: 3.8",
24
23
  "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
@@ -84,7 +83,7 @@ skip_empty = true
84
83
  directory = "tests/htmlcov"
85
84
 
86
85
  [tool.mypy]
87
- python_version = "3.8"
86
+ python_version = "3.9"
88
87
  warn_return_any = true
89
88
  warn_unused_configs = true
90
89
  disallow_untyped_defs = false
@@ -9,6 +9,6 @@ Author: seligoroff
9
9
 
10
10
  from mongo_pipebuilder.builder import PipelineBuilder
11
11
 
12
- __version__ = "0.4.0"
12
+ __version__ = "0.5.0"
13
13
  __all__ = ["PipelineBuilder"]
14
14
 
@@ -10,9 +10,9 @@ import copy
10
10
  import difflib
11
11
  import json
12
12
  from pathlib import Path
13
- from typing import Any, Dict, List, Optional, Union
13
+ from typing import Any, Dict, Iterable, List, Optional, Union
14
14
 
15
- # For compatibility with Python < 3.11 and mypy with python_version 3.8
15
+ # For compatibility with Python < 3.11 (Self is in typing from 3.11)
16
16
  from typing_extensions import Self
17
17
 
18
18
 
@@ -686,6 +686,35 @@ class PipelineBuilder:
686
686
  self._stages.append(stage)
687
687
  return self
688
688
 
689
+ def add_stages(self, stages: Iterable[Dict[str, Any]]) -> Self:
690
+ """
691
+ Add multiple pipeline stages at once (e.g. a subpipeline from another builder).
692
+
693
+ Empty dict stages are skipped, as with add_stage. Each element must be a
694
+ dictionary.
695
+
696
+ Args:
697
+ stages: Iterable of stage dictionaries (e.g. list, or result of .build()).
698
+
699
+ Returns:
700
+ Self for method chaining.
701
+
702
+ Raises:
703
+ TypeError: If stages is None or any element is not a dictionary.
704
+
705
+ Example:
706
+ >>> builder.add_stages([{"$match": {"x": 1}}, {"$limit": 10}])
707
+ >>> builder.add_stages(other_builder.build())
708
+ """
709
+ if stages is None:
710
+ raise TypeError("stages must not be None")
711
+ for stage in stages:
712
+ if not isinstance(stage, dict):
713
+ raise TypeError("All stages must be dictionaries")
714
+ if stage:
715
+ self._stages.append(stage)
716
+ return self
717
+
689
718
  def __len__(self) -> int:
690
719
  """
691
720
  Return the number of stages in the pipeline.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mongo-pipebuilder
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Type-safe, fluent MongoDB aggregation pipeline builder
5
5
  Author-email: seligoroff <seligoroff@gmail.com>
6
6
  License-Expression: MIT
@@ -12,14 +12,13 @@ Keywords: mongodb,aggregation,pipeline,builder,query
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Topic :: Database
21
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Python: >=3.8
21
+ Requires-Python: >=3.9
23
22
  Description-Content-Type: text/markdown
24
23
  License-File: LICENSE
25
24
  Requires-Dist: typing_extensions>=4.0.0; python_version < "3.11"
@@ -28,7 +27,7 @@ Dynamic: license-file
28
27
  # mongo-pipebuilder
29
28
 
30
29
  [![PyPI version](https://badge.fury.io/py/mongo-pipebuilder.svg)](https://badge.fury.io/py/mongo-pipebuilder)
31
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
30
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
32
31
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
32
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
34
33
  [![Test Coverage](https://img.shields.io/badge/coverage-96%25-green.svg)](https://github.com/seligoroff/mongo-pipebuilder)
@@ -282,6 +281,19 @@ Adds a custom stage for advanced use cases.
282
281
  }})
283
282
  ```
284
283
 
284
+ ##### `add_stages(stages: Iterable[Dict[str, Any]]) -> Self`
285
+
286
+ Adds multiple stages at once (e.g. a subpipeline from another builder). Empty dicts are skipped. Useful to avoid loops when inserting a ready-made list of stages.
287
+
288
+ ```python
289
+ # From a list
290
+ .add_stages([{"$match": {"level": "error"}}, {"$limit": 100}])
291
+
292
+ # From another builder
293
+ sub = PipelineBuilder().match({"source": "api"}).project({"name": 1})
294
+ .add_stages(sub.build())
295
+ ```
296
+
285
297
  ##### `prepend(stage: Dict[str, Any]) -> Self`
286
298
 
287
299
  Adds a stage at the beginning of the pipeline.
@@ -10,6 +10,7 @@ src/mongo_pipebuilder.egg-info/dependency_links.txt
10
10
  src/mongo_pipebuilder.egg-info/requires.txt
11
11
  src/mongo_pipebuilder.egg-info/top_level.txt
12
12
  tests/test_builder.py
13
+ tests/test_builder_add_stages.py
13
14
  tests/test_builder_debug.py
14
15
  tests/test_builder_insert.py
15
16
  tests/test_builder_lookup_let.py
@@ -0,0 +1,95 @@
1
+ """
2
+ Tests for PipelineBuilder.add_stages (add multiple stages at once).
3
+
4
+ Author: seligoroff
5
+ """
6
+ import pytest
7
+
8
+ from mongo_pipebuilder import PipelineBuilder
9
+
10
+
11
+ class TestAddStages:
12
+ """Tests for add_stages method."""
13
+
14
+ def test_add_stages_empty_list_adds_nothing(self):
15
+ """add_stages([]) adds no stages."""
16
+ builder = PipelineBuilder()
17
+ builder.match({"x": 1}).add_stages([])
18
+ pipeline = builder.build()
19
+ assert len(pipeline) == 1
20
+ assert pipeline[0] == {"$match": {"x": 1}}
21
+
22
+ def test_add_stages_one_stage(self):
23
+ """add_stages([stage]) adds one stage."""
24
+ builder = PipelineBuilder()
25
+ builder.add_stages([{"$match": {"x": 1}}])
26
+ pipeline = builder.build()
27
+ assert len(pipeline) == 1
28
+ assert pipeline[0] == {"$match": {"x": 1}}
29
+
30
+ def test_add_stages_two_stages(self):
31
+ """add_stages([s1, s2]) adds two stages in order."""
32
+ builder = PipelineBuilder()
33
+ builder.add_stages([{"$match": {"a": 1}}, {"$limit": 10}])
34
+ pipeline = builder.build()
35
+ assert len(pipeline) == 2
36
+ assert pipeline[0] == {"$match": {"a": 1}}
37
+ assert pipeline[1] == {"$limit": 10}
38
+
39
+ def test_add_stages_skips_empty_dict_stages(self):
40
+ """add_stages([{}, stage, ...]) skips empty dicts, adds only non-empty stages."""
41
+ builder = PipelineBuilder()
42
+ builder.add_stages([{}, {"$match": {"x": 1}}])
43
+ pipeline = builder.build()
44
+ assert len(pipeline) == 1
45
+ assert pipeline[0] == {"$match": {"x": 1}}
46
+
47
+ def test_add_stages_from_other_builder_build(self):
48
+ """add_stages(other_builder.build()) appends subpipeline; order and content preserved."""
49
+ sub = PipelineBuilder().match({"source": "api"}).project({"name": 1, "_id": 0})
50
+ builder = PipelineBuilder()
51
+ builder.match({"status": "active"}).add_stages(sub.build())
52
+ pipeline = builder.build()
53
+ assert len(pipeline) == 3
54
+ assert pipeline[0] == {"$match": {"status": "active"}}
55
+ assert pipeline[1] == {"$match": {"source": "api"}}
56
+ assert pipeline[2] == {"$project": {"name": 1, "_id": 0}}
57
+ assert len(sub) == 2
58
+
59
+ def test_add_stages_none_raises(self):
60
+ """add_stages(None) raises TypeError."""
61
+ builder = PipelineBuilder()
62
+ with pytest.raises(TypeError, match="stages must not be None"):
63
+ builder.add_stages(None)
64
+
65
+ def test_add_stages_non_dict_element_raises(self):
66
+ """add_stages(iterable with non-dict element) raises TypeError."""
67
+ builder = PipelineBuilder()
68
+ with pytest.raises(TypeError, match="All stages must be dictionaries"):
69
+ builder.add_stages([{"$match": {}}, "not a stage"])
70
+
71
+ def test_add_stages_chaining(self):
72
+ """add_stages returns self; can chain with match, limit, etc."""
73
+ builder = PipelineBuilder()
74
+ pipeline = (
75
+ builder.add_stages([{"$match": {"source": "api"}}])
76
+ .match({"status": "active"})
77
+ .limit(10)
78
+ .build()
79
+ )
80
+ assert len(pipeline) == 3
81
+ assert pipeline[0] == {"$match": {"source": "api"}}
82
+ assert pipeline[1] == {"$match": {"status": "active"}}
83
+ assert pipeline[2] == {"$limit": 10}
84
+
85
+ def test_add_stages_copy_independent(self):
86
+ """copy() after add_stages yields independent builder; modifying copy does not change original."""
87
+ builder = PipelineBuilder()
88
+ builder.add_stages([{"$match": {"x": 1}}])
89
+ c = builder.copy()
90
+ c.limit(5)
91
+ assert len(builder) == 1
92
+ assert len(c) == 2
93
+ assert builder.build()[0] == {"$match": {"x": 1}}
94
+ assert c.build()[0] == {"$match": {"x": 1}}
95
+ assert c.build()[1] == {"$limit": 5}