Skip to content

Commit 7f7a77e

Browse files
authored
Merge pull request #20 from volfpeter/feat/add-regex-operator
Add aggregation utility and tests, refs #19
2 parents 967346d + 6886788 commit 7f7a77e

File tree

11 files changed

+319
-24
lines changed

11 files changed

+319
-24
lines changed

docs/api/aggregation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# ::: motorhead.aggregation
2+
3+
options:
4+
show_root_heading: true

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ nav:
4949
- api/service.md
5050
- api/query.md
5151
- api/operator.md
52+
- api/aggregation.md
5253
- Model:
5354
- api/model/document.md
5455
- api/model/objectid.md

motorhead/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from . import operator as operator
2+
from .aggregation import Aggregation as Aggregation
3+
from .aggregation import AggregationStage as AggregationStage
4+
from .aggregation import make_aggregation_stage as make_aggregation_stage
25
from .bound_method_wrapper import BoundMethodWrapper as BoundMethodWrapper
36
from .delete_rule import DeleteConfig as DeleteConfig
47
from .delete_rule import DeleteError as DeleteError
@@ -13,13 +16,9 @@
1316
from .query import Q as Q
1417
from .query import Query as Query
1518
from .query import Queryable as Queryable
16-
from .service import DeleteResult as DeleteResult
17-
from .service import InsertManyResult as InsertManyResult
18-
from .service import InsertOneResult as InsertOneResult
1919
from .service import Service as Service
2020
from .service import ServiceConfig as ServiceConfig
2121
from .service import ServiceException as ServiceException
22-
from .service import UpdateResult as UpdateResult
2322
from .typing import AgnosticClient as AgnosticClient
2423
from .typing import AgnosticClientSession as AgnosticClientSession
2524
from .typing import AgnosticCollection as AgnosticCollection
@@ -35,15 +34,19 @@
3534
from .typing import CollectionOptions as CollectionOptions
3635
from .typing import DatabaseProvider as DatabaseProvider
3736
from .typing import DeleteOptions as DeleteOptions
37+
from .typing import DeleteResult as DeleteResult
3838
from .typing import FindOptions as FindOptions
3939
from .typing import IndexData as IndexData
4040
from .typing import InsertManyOptions as InsertManyOptions
41+
from .typing import InsertManyResult as InsertManyResult
4142
from .typing import InsertOneOptions as InsertOneOptions
43+
from .typing import InsertOneResult as InsertOneResult
4244
from .typing import MongoProjection as MongoProjection
4345
from .typing import MongoQuery as MongoQuery
4446
from .typing import UpdateManyOptions as UpdateManyOptions
4547
from .typing import UpdateObject as UpdateObject
4648
from .typing import UpdateOneOptions as UpdateOneOptions
49+
from .typing import UpdateResult as UpdateResult
4750
from .validator import ValidationError as ValidationError
4851
from .validator import Validator as Validator
4952
from .validator import validator as validator

motorhead/aggregation.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any, Literal
4+
5+
if TYPE_CHECKING:
6+
from collections.abc import Iterable
7+
from typing import TypeAlias
8+
9+
from typing_extensions import Self
10+
11+
from .typing import Clause
12+
13+
AggregationStage = Literal[
14+
"$addFields",
15+
"$bucket",
16+
"$bucketAuto",
17+
"$changeStream",
18+
"$changeStreamSplitLargeEvent",
19+
"$collStats",
20+
"$count",
21+
"$currentOp",
22+
"$densify",
23+
"$documents",
24+
"$facet",
25+
"$fill",
26+
"$geoNear",
27+
"$graphLookup",
28+
"$group",
29+
"$indexStats",
30+
"$limit",
31+
"$listLocalSessions",
32+
"$listSampledQueries",
33+
"$listSearchIndexes",
34+
"$listSessions",
35+
"$lookup",
36+
"$match",
37+
"$merge",
38+
"$out",
39+
"$planCacheStats",
40+
"$project",
41+
"$querySettings",
42+
"$redact",
43+
"$replaceRoot",
44+
"$replaceWith",
45+
"$sample",
46+
"$search",
47+
"$searchMeta",
48+
"$set",
49+
"$setWindowFields",
50+
"$shardedDataDistribution",
51+
"$skip",
52+
"$sort",
53+
"$sortByCount",
54+
"$unionWith",
55+
"$unset",
56+
"$unwind",
57+
"$vectorSearch",
58+
]
59+
"""Aggregation pipeline stage."""
60+
61+
62+
AggregationData: TypeAlias = Any
63+
64+
65+
def make_aggregation_stage(
66+
stage: AggregationStage, value: AggregationData | Clause
67+
) -> dict[str, AggregationData]:
68+
"""
69+
Creates an aggregation pipeline stage.
70+
71+
Arguments:
72+
stage: The stage operator.
73+
value: The stage operator's content.
74+
75+
Returns:
76+
The aggregation pipeline stage.
77+
"""
78+
return {stage: value.to_mongo() if hasattr(value, "to_mongo") else value}
79+
80+
81+
class Aggregation(list[dict[str, AggregationData]]):
82+
"""Aggregation pipeline."""
83+
84+
def __init__(self, stages: Iterable[AggregationData] = ()) -> None:
85+
"""
86+
Initialization.
87+
88+
Arguments:
89+
stages: The aggregation pipeline stages.
90+
"""
91+
super().__init__(stages)
92+
93+
def stage(self, stage: AggregationStage, value: AggregationData | Clause) -> Self:
94+
"""
95+
Adds the stage to the aggregation pipeline.
96+
97+
Arguments:
98+
stage: The stage operator.
99+
value: The stage operator's content.
100+
101+
Returns:
102+
The aggregation pipeline.
103+
"""
104+
self.append(make_aggregation_stage(stage, value))
105+
return self

motorhead/operator.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

3-
from collections.abc import Generator
4-
from typing import TYPE_CHECKING, Any
3+
from typing import TYPE_CHECKING
54

65
if TYPE_CHECKING:
6+
from collections.abc import Generator
7+
from typing import Any
8+
79
from .query import Field
810
from .typing import Clause
911

motorhead/query.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any, TypeVar
3+
from typing import TYPE_CHECKING, TypeVar
44

55
from pydantic import BaseModel
66

@@ -23,12 +23,14 @@
2323
Size,
2424
Type,
2525
)
26-
from .typing import Clause
27-
28-
_T = TypeVar("_T", bound=BaseModel)
2926

3027
if TYPE_CHECKING:
31-
from .typing import MongoQuery
28+
from typing import Any
29+
30+
from .typing import Clause, MongoQuery
31+
32+
33+
_T = TypeVar("_T", bound=BaseModel)
3234

3335

3436
class Field:

motorhead/service.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
from __future__ import annotations
22

3-
from collections.abc import AsyncGenerator, Callable, Coroutine, Generator, Iterable, Mapping, Sequence
43
from contextlib import AbstractAsyncContextManager, asynccontextmanager, nullcontext
5-
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, TypeVar, get_args
4+
from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, get_args
65

76
from bson import ObjectId
87
from pydantic import BaseModel
9-
from pymongo.results import DeleteResult, InsertManyResult, InsertOneResult, UpdateResult
108

9+
from .delete_rule import DeleteRule
1110
from .operator import ensure_dict
12-
from .typing import ClauseOrMongoQuery
11+
from .validator import Validator
1312

1413
if TYPE_CHECKING:
14+
from collections.abc import AsyncGenerator, Callable, Coroutine, Generator, Iterable, Mapping, Sequence
15+
from typing import Any, ClassVar
16+
1517
from .typing import (
1618
AgnosticClient,
1719
AgnosticClientSession,
@@ -20,29 +22,30 @@
2022
AgnosticCursor,
2123
AgnosticDatabase,
2224
AgnosticLatentCommandCursor,
25+
ClauseOrMongoQuery,
2326
Collation,
2427
CollectionOptions,
2528
DeleteOptions,
29+
DeleteResult,
2630
FindOptions,
2731
IndexData,
2832
InsertManyOptions,
33+
InsertManyResult,
2934
InsertOneOptions,
35+
InsertOneResult,
3036
MongoProjection,
3137
UpdateManyOptions,
3238
UpdateObject,
3339
UpdateOneOptions,
40+
UpdateResult,
3441
)
3542

36-
from .delete_rule import DeleteRule
37-
from .validator import Validator
3843

3944
__all__ = (
4045
"BaseService",
41-
"DeleteResult",
42-
"InsertManyResult",
43-
"InsertOneResult",
4446
"Service",
45-
"UpdateResult",
47+
"ServiceConfig",
48+
"ServiceException",
4649
)
4750

4851
TInsert = TypeVar("TInsert", bound=BaseModel)

motorhead/typing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
from motor.core import AgnosticDatabase as _AgnosticDatabase
1313
from motor.core import AgnosticLatentCommandCursor as _AgnosticLatentCommandCursor
1414
from pymongo.collation import Collation as PMCollation
15+
from pymongo.results import DeleteResult as DeleteResult
16+
from pymongo.results import InsertManyResult as InsertManyResult
17+
from pymongo.results import InsertOneResult as InsertOneResult
18+
from pymongo.results import UpdateResult as UpdateResult
1519

1620
if TYPE_CHECKING:
1721
from bson.codec_options import CodecOptions

motorhead/validator.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
from collections.abc import Callable, Coroutine
2-
from typing import Literal, TypeVar
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Literal, TypeVar
34

45
from .bound_method_wrapper import BoundMethodWrapper
56
from .typing import ClauseOrMongoQuery
67

8+
if TYPE_CHECKING:
9+
from collections.abc import Callable, Coroutine
10+
711
__all__ = (
812
"ValidationError",
913
"Validator",

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ tracker = "https://github.com/volfpeter/motorhead/issues"
3131

3232
[tool.poetry]
3333
name = "motorhead"
34-
version = "0.2501.1"
34+
version = "0.2501.2"
3535
description = "Async MongoDB with vanilla Pydantic v2+ - made easy."
3636
authors = ["Peter Volf <[email protected]>"]
3737
readme = "README.md"
@@ -52,6 +52,7 @@ pytest = "^8.3.3"
5252
pytest-asyncio = "^0.24.0"
5353
pytest-docker = "^3.1.1"
5454
pytest-random-order = "^1.1.1"
55+
typing-extensions = "^4.12.2"
5556

5657
[tool.mypy]
5758
strict = true

0 commit comments

Comments
 (0)