dynamic-expressions 0.1.1__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. dynamic_expressions-0.1.1/PKG-INFO +11 -0
  2. dynamic_expressions-0.1.1/README.md +0 -0
  3. dynamic_expressions-0.1.1/dynamic_expressions/__init__.py +0 -0
  4. dynamic_expressions-0.1.1/dynamic_expressions/cache/__init__.py +5 -0
  5. dynamic_expressions-0.1.1/dynamic_expressions/cache/base.py +81 -0
  6. dynamic_expressions-0.1.1/dynamic_expressions/cache/redis.py +39 -0
  7. dynamic_expressions-0.1.1/dynamic_expressions/dispatcher.py +64 -0
  8. dynamic_expressions-0.1.1/dynamic_expressions/extensions.py +16 -0
  9. dynamic_expressions-0.1.1/dynamic_expressions/nodes.py +36 -0
  10. dynamic_expressions-0.1.1/dynamic_expressions/py.typed +0 -0
  11. dynamic_expressions-0.1.1/dynamic_expressions/serialization/__init__.py +5 -0
  12. dynamic_expressions-0.1.1/dynamic_expressions/serialization/_serialization.py +6 -0
  13. dynamic_expressions-0.1.1/dynamic_expressions/serialization/msgspec.py +24 -0
  14. dynamic_expressions-0.1.1/dynamic_expressions/serialization/pydantic.py +104 -0
  15. dynamic_expressions-0.1.1/dynamic_expressions/types.py +29 -0
  16. dynamic_expressions-0.1.1/dynamic_expressions/visitors.py +105 -0
  17. dynamic_expressions-0.1.1/dynamic_expressions.egg-info/PKG-INFO +11 -0
  18. dynamic_expressions-0.1.1/dynamic_expressions.egg-info/SOURCES.txt +21 -0
  19. dynamic_expressions-0.1.1/dynamic_expressions.egg-info/dependency_links.txt +1 -0
  20. dynamic_expressions-0.1.1/dynamic_expressions.egg-info/requires.txt +9 -0
  21. dynamic_expressions-0.1.1/dynamic_expressions.egg-info/top_level.txt +1 -0
  22. dynamic_expressions-0.1.1/pyproject.toml +144 -0
  23. dynamic_expressions-0.1.1/setup.cfg +4 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.2
2
+ Name: dynamic-expressions
3
+ Version: 0.1.1
4
+ Requires-Python: >=3.12
5
+ Description-Content-Type: text/markdown
6
+ Provides-Extra: cache-redis
7
+ Requires-Dist: redis>=5.2.1; extra == "cache-redis"
8
+ Provides-Extra: serialization-pydantic
9
+ Requires-Dist: pydantic>=2.10.6; extra == "serialization-pydantic"
10
+ Provides-Extra: serialization-msgspec
11
+ Requires-Dist: msgspec>=0.19.0; extra == "serialization-msgspec"
File without changes
@@ -0,0 +1,5 @@
1
+ from .base import CachePolicy
2
+
3
+ __all__ = [
4
+ "CachePolicy",
5
+ ]
@@ -0,0 +1,81 @@
1
+ import abc
2
+ import contextlib
3
+ import dataclasses
4
+ from collections.abc import AsyncIterator, Callable, MutableMapping, Sequence
5
+ from datetime import timedelta
6
+ from typing import Any
7
+
8
+ from dynamic_expressions.extensions import OnVisitExtension
9
+ from dynamic_expressions.nodes import Node
10
+ from dynamic_expressions.serialization import Serializer
11
+ from dynamic_expressions.types import EmptyContext, ExecutionContext
12
+
13
+
14
+ @dataclasses.dataclass(slots=True, kw_only=True)
15
+ class CachePolicy[Context: EmptyContext]:
16
+ types: tuple[type[Node], ...]
17
+ key: Callable[[Node, Context], str]
18
+ ttl: timedelta
19
+ serializer: Serializer[Any] | None = None
20
+
21
+
22
+ class CacheExtension[
23
+ Context: EmptyContext,
24
+ ](OnVisitExtension[Context]):
25
+ policies: Sequence[CachePolicy[Context]]
26
+ default_serializer: Serializer[Any]
27
+ _policy_cache: MutableMapping[type[Node], CachePolicy[Context] | None]
28
+
29
+ @contextlib.asynccontextmanager
30
+ async def on_visit(
31
+ self,
32
+ *,
33
+ node: Node,
34
+ provided_context: Context,
35
+ execution_context: ExecutionContext,
36
+ ) -> AsyncIterator[None]:
37
+ policy = self._get_policy(node)
38
+
39
+ if policy is None or node in execution_context.cache:
40
+ yield
41
+ return
42
+
43
+ key = policy.key(node, provided_context)
44
+ serializer = policy.serializer or self.default_serializer
45
+ cached_value = await self.get_cache(key)
46
+ if cached_value is not None:
47
+ execution_context.cache[node] = serializer.deserialize(cached_value)
48
+
49
+ yield
50
+
51
+ if (
52
+ node in execution_context.cache
53
+ and cached_value != execution_context.cache[node]
54
+ ):
55
+ await self.set_cache(
56
+ key=key,
57
+ value=serializer.serialize(execution_context.cache[node]),
58
+ policy=policy,
59
+ )
60
+
61
+ def _get_policy(self, node: Node) -> CachePolicy[Context] | None:
62
+ node_cls = type(node)
63
+ if node_cls in self._policy_cache:
64
+ return self._policy_cache[node_cls]
65
+
66
+ self._policy_cache[node_cls] = next(
67
+ (policy for policy in self.policies if isinstance(node, policy.types)),
68
+ None,
69
+ )
70
+ return self._policy_cache[node_cls]
71
+
72
+ @abc.abstractmethod
73
+ async def get_cache(self, key: str) -> Any | None: ... # noqa: ANN401
74
+
75
+ @abc.abstractmethod
76
+ async def set_cache(
77
+ self,
78
+ key: str,
79
+ value: Any, # noqa: ANN401
80
+ policy: CachePolicy[Context],
81
+ ) -> None: ...
@@ -0,0 +1,39 @@
1
+ from collections.abc import Sequence
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ from redis.asyncio import Redis
5
+
6
+ from dynamic_expressions.cache.base import CacheExtension, CachePolicy
7
+ from dynamic_expressions.serialization import Serializer
8
+ from dynamic_expressions.types import EmptyContext
9
+
10
+ if TYPE_CHECKING:
11
+ RedisClient = Redis[bytes]
12
+ else:
13
+ RedisClient = Redis
14
+
15
+
16
+ class RedisCacheExtension[
17
+ Context: EmptyContext,
18
+ ](CacheExtension[Context]):
19
+ def __init__(
20
+ self,
21
+ client: RedisClient,
22
+ policies: Sequence[CachePolicy[Context]],
23
+ default_serializer: Serializer[Any],
24
+ ) -> None:
25
+ self.policies = policies
26
+ self.default_serializer = default_serializer
27
+ self._client = client
28
+ self._policy_cache = {}
29
+
30
+ async def get_cache(self, key: str) -> bytes | None:
31
+ return await self._client.get(name=key)
32
+
33
+ async def set_cache(
34
+ self,
35
+ key: str,
36
+ value: bytes,
37
+ policy: CachePolicy[Context],
38
+ ) -> None:
39
+ await self._client.set(name=key, value=value, ex=policy.ttl)
@@ -0,0 +1,64 @@
1
+ import functools
2
+ from collections.abc import Mapping, Sequence
3
+ from contextlib import AsyncExitStack
4
+ from typing import Any
5
+
6
+ from dynamic_expressions.extensions import (
7
+ OnVisitExtension,
8
+ )
9
+ from dynamic_expressions.nodes import Node
10
+ from dynamic_expressions.types import EmptyContext, ExecutionContext
11
+ from dynamic_expressions.visitors import Visitor
12
+
13
+
14
+ class VisitorDispatcher[Context: EmptyContext]:
15
+ def __init__(
16
+ self,
17
+ visitors: Mapping[type[Node], Visitor[Any, Context]],
18
+ extensions: Sequence[OnVisitExtension[Context]] = (),
19
+ ) -> None:
20
+ self._visitors = visitors
21
+ self._on_visit_exts = extensions
22
+
23
+ async def visit(
24
+ self,
25
+ node: Node,
26
+ context: Context,
27
+ ) -> Any: # noqa: ANN401
28
+ execution_context = ExecutionContext()
29
+ return await self._visit(
30
+ node=node,
31
+ context=context,
32
+ execution_context=execution_context,
33
+ )
34
+
35
+ async def _visit(
36
+ self,
37
+ node: Node,
38
+ context: Context,
39
+ execution_context: ExecutionContext,
40
+ ) -> Any: # noqa: ANN401
41
+ async with AsyncExitStack() as stack:
42
+ for ext in self._on_visit_exts:
43
+ await stack.enter_async_context(
44
+ ext.on_visit(
45
+ node=node,
46
+ provided_context=context,
47
+ execution_context=execution_context,
48
+ )
49
+ )
50
+
51
+ if node in execution_context.cache:
52
+ return execution_context.cache[node]
53
+
54
+ visitor = self._visitors[type(node)]
55
+ result = await visitor.visit(
56
+ node=node,
57
+ context=context,
58
+ dispatch=functools.partial(
59
+ self._visit,
60
+ execution_context=execution_context,
61
+ ),
62
+ )
63
+ execution_context.cache[node] = result
64
+ return result
@@ -0,0 +1,16 @@
1
+ from contextlib import AbstractAsyncContextManager
2
+ from typing import Protocol, runtime_checkable
3
+
4
+ from dynamic_expressions.nodes import Node
5
+ from dynamic_expressions.types import EmptyContext, ExecutionContext
6
+
7
+
8
+ @runtime_checkable
9
+ class OnVisitExtension[Context: EmptyContext](Protocol):
10
+ def on_visit(
11
+ self,
12
+ *,
13
+ node: Node,
14
+ provided_context: Context,
15
+ execution_context: ExecutionContext,
16
+ ) -> AbstractAsyncContextManager[None]: ...
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from dynamic_expressions.types import BinaryExpressionOperator
8
+
9
+
10
+ @dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
11
+ class Node: ...
12
+
13
+
14
+ @dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
15
+ class AnyOfNode(Node):
16
+ expressions: tuple[Node, ...]
17
+
18
+
19
+ @dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
20
+ class AllOfNode(Node):
21
+ expressions: tuple[Node, ...]
22
+
23
+
24
+ @dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
25
+ class BinaryExpressionNode(Node):
26
+ operator: BinaryExpressionOperator
27
+ left: Node
28
+ right: Node
29
+
30
+
31
+ @dataclass(slots=True, frozen=True, kw_only=True)
32
+ class LiteralNode(Node):
33
+ value: Any
34
+
35
+ def __hash__(self) -> int:
36
+ return hash((self.value, type(self.value)))
File without changes
@@ -0,0 +1,5 @@
1
+ from ._serialization import Serializer
2
+
3
+ __all__ = [
4
+ "Serializer",
5
+ ]
@@ -0,0 +1,6 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class Serializer[TResult](Protocol):
5
+ def serialize(self, value: TResult) -> bytes: ...
6
+ def deserialize(self, value: bytes) -> TResult: ...
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+
3
+ import msgspec
4
+
5
+ from ._serialization import Serializer
6
+
7
+
8
+ class MsgSpecSerializer[T](Serializer[T]):
9
+ def __init__(self, instance_of: type[T]) -> None:
10
+ self.instance_of = instance_of
11
+
12
+ def serialize(self, value: T) -> bytes:
13
+ return msgspec.json.encode(value)
14
+
15
+ def deserialize(self, value: bytes) -> T:
16
+ return msgspec.json.decode(value, type=self.instance_of)
17
+
18
+
19
+ class MsgSpecScalarSerializer(Serializer[Any]):
20
+ def serialize(self, value: Any) -> bytes: # noqa: ANN401
21
+ return msgspec.json.encode(value)
22
+
23
+ def deserialize(self, value: bytes) -> Any: # noqa: ANN401
24
+ return msgspec.json.decode(value)
@@ -0,0 +1,104 @@
1
+ import abc
2
+ from collections.abc import Sequence
3
+ from typing import Any, Literal, Union, overload
4
+
5
+ from pydantic import BaseModel, ConfigDict, TypeAdapter
6
+
7
+ from dynamic_expressions.nodes import (
8
+ AllOfNode,
9
+ AnyOfNode,
10
+ BinaryExpressionNode,
11
+ LiteralNode,
12
+ Node,
13
+ )
14
+ from dynamic_expressions.types import BinaryExpressionOperator
15
+
16
+ from ._serialization import Serializer
17
+
18
+
19
+ class NodeSchema(BaseModel):
20
+ model_config = ConfigDict(strict=True)
21
+
22
+ @abc.abstractmethod
23
+ def to_node(self) -> Node: ...
24
+
25
+
26
+ class AnyOfNodeSchema[T: NodeSchema](NodeSchema):
27
+ type: Literal["any-of"]
28
+ expressions: tuple[T, ...]
29
+
30
+ def to_node(self) -> AnyOfNode:
31
+ return AnyOfNode(expressions=tuple(expr.to_node() for expr in self.expressions))
32
+
33
+
34
+ class AllOfNodeSchema[T: NodeSchema](NodeSchema):
35
+ type: Literal["all-of"]
36
+ expressions: tuple[T, ...]
37
+
38
+ def to_node(self) -> AllOfNode:
39
+ return AllOfNode(expressions=tuple(expr.to_node() for expr in self.expressions))
40
+
41
+
42
+ class BinaryExpressionNodeSchema[T: NodeSchema](NodeSchema):
43
+ type: Literal["binary"]
44
+ operator: BinaryExpressionOperator
45
+ left: T
46
+ right: T
47
+
48
+ def to_node(self) -> BinaryExpressionNode:
49
+ return BinaryExpressionNode(
50
+ operator=self.operator,
51
+ left=self.left.to_node(),
52
+ right=self.right.to_node(),
53
+ )
54
+
55
+
56
+ class LiteralNodeSchema[T](NodeSchema):
57
+ type: Literal["literal"]
58
+ value: int | str | bool
59
+
60
+ def to_node(self) -> LiteralNode:
61
+ return LiteralNode(value=self.value)
62
+
63
+
64
+ BUILTIN_SCHEMAS: Sequence[type[NodeSchema]] = [
65
+ AnyOfNodeSchema,
66
+ AllOfNodeSchema,
67
+ BinaryExpressionNodeSchema,
68
+ LiteralNodeSchema,
69
+ ]
70
+
71
+
72
+ class PydanticExpressionParser:
73
+ def __init__(self, types: Sequence[type[NodeSchema]]) -> None:
74
+ self._types = list(types)
75
+ self._needs_rebuild = True
76
+ self._type_adapter: TypeAdapter[NodeSchema] | None = None
77
+
78
+ def add_type(self, cls: type[NodeSchema]) -> None:
79
+ self._types.append(cls)
80
+
81
+ @property
82
+ def type_adapter(self) -> TypeAdapter[NodeSchema]:
83
+ if self._needs_rebuild or self._type_adapter is None:
84
+ union = Union[tuple(model["union"] for model in self._types)] # type: ignore[valid-type,index] # noqa: UP007
85
+ self._type_adapter = TypeAdapter(union)
86
+
87
+ return self._type_adapter
88
+
89
+
90
+ class PydanticSerializer[T](Serializer[T]):
91
+ @overload
92
+ def __init__(self, instance_of: type[T]) -> None: ...
93
+
94
+ @overload
95
+ def __init__(self, instance_of: Any) -> None: ... # noqa: ANN401
96
+
97
+ def __init__(self, instance_of: Any) -> None:
98
+ self._type_adapter = TypeAdapter[T](instance_of)
99
+
100
+ def serialize(self, value: T) -> bytes:
101
+ return self._type_adapter.dump_json(value, by_alias=True)
102
+
103
+ def deserialize(self, value: bytes) -> T:
104
+ return self._type_adapter.validate_json(value, strict=True)
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Literal, Protocol
3
+
4
+ from dynamic_expressions.nodes import Node
5
+
6
+
7
+ class EmptyContext(Protocol): ...
8
+
9
+
10
+ @dataclass
11
+ class ExecutionContext:
12
+ cache: dict[Node, Any] = field(default_factory=dict)
13
+
14
+
15
+ BinaryExpressionOperator = Literal[
16
+ "=",
17
+ ">",
18
+ ">=",
19
+ "<",
20
+ "<=",
21
+ "!=",
22
+ "in",
23
+ "+",
24
+ "-",
25
+ "*",
26
+ "/",
27
+ "//",
28
+ "^",
29
+ ]
@@ -0,0 +1,105 @@
1
+ import abc
2
+ import operator
3
+ from collections.abc import Callable, Mapping
4
+ from typing import Any, ClassVar, Protocol
5
+
6
+ from dynamic_expressions.nodes import (
7
+ AllOfNode,
8
+ AnyOfNode,
9
+ BinaryExpressionNode,
10
+ LiteralNode,
11
+ Node,
12
+ )
13
+ from dynamic_expressions.types import BinaryExpressionOperator, EmptyContext
14
+
15
+
16
+ class Dispatch[TContext: EmptyContext](Protocol):
17
+ async def __call__(self, node: Node, context: TContext) -> Any: ... # noqa: ANN401
18
+
19
+
20
+ class Visitor[TNode: Node, TContext: EmptyContext]:
21
+ @abc.abstractmethod
22
+ async def visit(
23
+ self,
24
+ *,
25
+ node: TNode,
26
+ dispatch: Dispatch[TContext],
27
+ context: TContext,
28
+ ) -> Any: ... # noqa: ANN401
29
+
30
+
31
+ class AnyOfVisitor(Visitor[AnyOfNode, EmptyContext]):
32
+ async def visit(
33
+ self,
34
+ *,
35
+ node: AnyOfNode,
36
+ dispatch: Dispatch[EmptyContext],
37
+ context: object,
38
+ ) -> bool:
39
+ for expr in node.expressions:
40
+ value = await dispatch(expr, context)
41
+ if value:
42
+ return True
43
+ return False
44
+
45
+
46
+ class AllOfVisitor(Visitor[AllOfNode, EmptyContext]):
47
+ async def visit(
48
+ self,
49
+ *,
50
+ node: AllOfNode,
51
+ dispatch: Dispatch[EmptyContext],
52
+ context: EmptyContext,
53
+ ) -> bool:
54
+ for expr in node.expressions:
55
+ value = await dispatch(expr, context)
56
+ if not value:
57
+ return False
58
+ return True
59
+
60
+
61
+ class BinaryExpressionVisitor(Visitor[BinaryExpressionNode, EmptyContext]):
62
+ operator_mapping: ClassVar[
63
+ Mapping[BinaryExpressionOperator, Callable[[Any, Any], bool]]
64
+ ] = {
65
+ "=": operator.eq,
66
+ ">": operator.gt,
67
+ ">=": operator.ge,
68
+ "<": operator.lt,
69
+ "<=": operator.le,
70
+ "!=": operator.ne,
71
+ "in": operator.contains,
72
+ "+": operator.add,
73
+ "-": operator.sub,
74
+ "*": operator.mul,
75
+ "/": operator.truediv,
76
+ "//": operator.floordiv,
77
+ "^": operator.pow,
78
+ }
79
+
80
+ async def visit(
81
+ self,
82
+ *,
83
+ node: BinaryExpressionNode,
84
+ dispatch: Dispatch[EmptyContext],
85
+ context: EmptyContext,
86
+ ) -> bool:
87
+ left = await dispatch(node.left, context)
88
+ right = await dispatch(node.right, context)
89
+
90
+ operator_callable = self.operator_mapping.get(node.operator)
91
+ if operator_callable is None:
92
+ msg = f"Unknown operator '{node.operator}'"
93
+ raise ValueError(msg)
94
+ return operator_callable(left, right)
95
+
96
+
97
+ class LiteralVisitor(Visitor[LiteralNode, EmptyContext]):
98
+ async def visit(
99
+ self,
100
+ *,
101
+ node: LiteralNode,
102
+ dispatch: Dispatch[EmptyContext], # noqa: ARG002
103
+ context: EmptyContext, # noqa: ARG002
104
+ ) -> Any: # noqa: ANN401
105
+ return node.value
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.2
2
+ Name: dynamic-expressions
3
+ Version: 0.1.1
4
+ Requires-Python: >=3.12
5
+ Description-Content-Type: text/markdown
6
+ Provides-Extra: cache-redis
7
+ Requires-Dist: redis>=5.2.1; extra == "cache-redis"
8
+ Provides-Extra: serialization-pydantic
9
+ Requires-Dist: pydantic>=2.10.6; extra == "serialization-pydantic"
10
+ Provides-Extra: serialization-msgspec
11
+ Requires-Dist: msgspec>=0.19.0; extra == "serialization-msgspec"
@@ -0,0 +1,21 @@
1
+ README.md
2
+ pyproject.toml
3
+ dynamic_expressions/__init__.py
4
+ dynamic_expressions/dispatcher.py
5
+ dynamic_expressions/extensions.py
6
+ dynamic_expressions/nodes.py
7
+ dynamic_expressions/py.typed
8
+ dynamic_expressions/types.py
9
+ dynamic_expressions/visitors.py
10
+ dynamic_expressions.egg-info/PKG-INFO
11
+ dynamic_expressions.egg-info/SOURCES.txt
12
+ dynamic_expressions.egg-info/dependency_links.txt
13
+ dynamic_expressions.egg-info/requires.txt
14
+ dynamic_expressions.egg-info/top_level.txt
15
+ dynamic_expressions/cache/__init__.py
16
+ dynamic_expressions/cache/base.py
17
+ dynamic_expressions/cache/redis.py
18
+ dynamic_expressions/serialization/__init__.py
19
+ dynamic_expressions/serialization/_serialization.py
20
+ dynamic_expressions/serialization/msgspec.py
21
+ dynamic_expressions/serialization/pydantic.py
@@ -0,0 +1,9 @@
1
+
2
+ [cache-redis]
3
+ redis>=5.2.1
4
+
5
+ [serialization-msgspec]
6
+ msgspec>=0.19.0
7
+
8
+ [serialization-pydantic]
9
+ pydantic>=2.10.6
@@ -0,0 +1 @@
1
+ dynamic_expressions
@@ -0,0 +1,144 @@
1
+ [project]
2
+ name = "dynamic-expressions"
3
+ version = "0.1.1"
4
+ description = ""
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = []
8
+
9
+ [project.optional-dependencies]
10
+ cache-redis = [
11
+ "redis>=5.2.1",
12
+ ]
13
+ serialization-pydantic = [
14
+ "pydantic>=2.10.6",
15
+ ]
16
+ serialization-msgspec = [
17
+ "msgspec>=0.19.0",
18
+ ]
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "anyio>=4.8.0",
23
+ "commitizen",
24
+ "coverage>=7.6.10",
25
+ "deptry>=0.23.0",
26
+ "mypy>=1.14.1",
27
+ "pytest>=8.3.4",
28
+ "redis>=5.2.1",
29
+ "ruff>=0.9.4",
30
+ "testcontainers>=4.9.1",
31
+ "types-redis>=4.6.0.20241004",
32
+ ]
33
+
34
+
35
+ [tool.pytest.ini_options]
36
+ pythonpath = "."
37
+ markers = ["redis"]
38
+
39
+ [tool.coverage.run]
40
+ source = ["dynamic_expressions"]
41
+ omit = []
42
+ command_line = "-m pytest -v"
43
+ concurrency = []
44
+ branch = true
45
+
46
+ [tool.coverage.report]
47
+ exclude_lines = [
48
+ "class .*\\(.*\\bProtocol\\b.*\\):",
49
+ "@(?:typing\\.)?overload",
50
+ "if TYPE_CHECKING:",
51
+ ]
52
+
53
+ [tool.deptry]
54
+ [tool.deptry.per_rule_ignores]
55
+ DEP002 = []
56
+
57
+ [tool.mypy]
58
+ plugins = [
59
+ "pydantic.mypy",
60
+ ]
61
+
62
+ strict = true
63
+ follow_imports = "normal"
64
+ ignore_missing_imports = false
65
+
66
+ allow_redefinition = false
67
+ disallow_any_explicit = false
68
+ ignore_errors = false
69
+ local_partial_types = true
70
+ no_implicit_optional = true
71
+ strict_optional = true
72
+ warn_no_return = true
73
+ warn_return_any = true
74
+ warn_unreachable = true
75
+
76
+ pretty = true
77
+ show_column_numbers = true
78
+ show_error_codes = true
79
+
80
+ [tool.pydantic-mypy]
81
+ init_forbid_extra = true
82
+ init_typed = true
83
+
84
+ [tool.ruff]
85
+ src = ["src", "tests"]
86
+ [tool.ruff.lint]
87
+ fixable = [
88
+ "F",
89
+ "E",
90
+ "W",
91
+ "I",
92
+ "COM",
93
+ "UP",
94
+ "RUF",
95
+ ]
96
+ unfixable = [
97
+ "F841", # Variable is assigned to but never used
98
+ ]
99
+ select = ["ALL"]
100
+ ignore = [
101
+ "E501", # Line Length
102
+ "D10", # Disable mandatory docstrings
103
+ "D203", # one-blank-line-before-class
104
+ "D212", # multi-line-summary-first-line
105
+ "PD", # pandas-vet
106
+ "EXE",
107
+ "COM812", # ruff format conflict
108
+ "ISC001", # ruff format conflict
109
+ ]
110
+
111
+ [tool.ruff.lint.per-file-ignores]
112
+ "tests/*" = ["S101"]
113
+
114
+ [tool.ruff.lint.flake8-builtins]
115
+ builtins-allowed-modules = ["types"]
116
+
117
+ [tool.ruff.lint.flake8-pytest-style]
118
+ fixture-parentheses = false
119
+ mark-parentheses = false
120
+
121
+ [tool.ruff.lint.mccabe]
122
+ max-complexity = 6
123
+
124
+ [tool.ruff.lint.flake8-bugbear]
125
+ extend-immutable-calls = []
126
+
127
+ [tool.ruff.lint.pep8-naming]
128
+ classmethod-decorators = ["classmethod"]
129
+ staticmethod-decorators = ["staticmethod"]
130
+
131
+ [tool.ruff.lint.flake8-tidy-imports]
132
+ ban-relative-imports = "parents"
133
+
134
+ [tool.commitizen]
135
+ name = "cz_conventional_commits"
136
+ version = "0.1.1"
137
+ tag_format = "$version"
138
+ version_scheme = "pep440"
139
+ version_provider = "pep621"
140
+ major_version_zero = true
141
+ update_changelog_on_bump = true
142
+ version_files = [
143
+ "pyproject.toml:version",
144
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+