anydi 0.55.1__py3-none-any.whl → 0.57.0__py3-none-any.whl
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.
- anydi/__init__.py +4 -2
- anydi/_container.py +67 -110
- anydi/_injector.py +132 -0
- anydi/_resolver.py +225 -122
- anydi/_scanner.py +52 -44
- anydi/_types.py +48 -7
- anydi/ext/fastapi.py +31 -33
- anydi/ext/faststream.py +25 -31
- anydi/ext/pydantic_settings.py +2 -1
- anydi/ext/pytest_plugin.py +380 -50
- anydi/testing.py +34 -113
- anydi-0.57.0.dist-info/METADATA +266 -0
- anydi-0.57.0.dist-info/RECORD +25 -0
- anydi-0.55.1.dist-info/METADATA +0 -193
- anydi-0.55.1.dist-info/RECORD +0 -24
- {anydi-0.55.1.dist-info → anydi-0.57.0.dist-info}/WHEEL +0 -0
- {anydi-0.55.1.dist-info → anydi-0.57.0.dist-info}/entry_points.txt +0 -0
anydi/_types.py
CHANGED
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
from collections.abc import AsyncIterator, Iterator
|
|
6
|
+
from collections.abc import AsyncIterator, Callable, Iterator
|
|
7
7
|
from types import NoneType
|
|
8
|
-
from typing import Any, Literal
|
|
8
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar
|
|
9
9
|
|
|
10
10
|
from typing_extensions import Sentinel
|
|
11
11
|
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
12
14
|
Scope = Literal["transient", "singleton", "request"]
|
|
13
15
|
|
|
14
16
|
NOT_SET = Sentinel("NOT_SET")
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class
|
|
18
|
-
"""A marker object for declaring
|
|
19
|
+
class ProvideMarker:
|
|
20
|
+
"""A marker object for declaring dependency."""
|
|
19
21
|
|
|
20
22
|
__slots__ = ("_interface",)
|
|
21
23
|
|
|
@@ -32,13 +34,52 @@ class InjectMarker:
|
|
|
32
34
|
def interface(self, interface: Any) -> None:
|
|
33
35
|
self._interface = interface
|
|
34
36
|
|
|
37
|
+
def __class_getitem__(cls, item: Any) -> Any:
|
|
38
|
+
return Annotated[item, cls()]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_provide_factory: Callable[[], Any] = ProvideMarker
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_provide_factory(factory: Callable[[], Any]) -> Callable[[], Any]:
|
|
45
|
+
"""Set the global factory used by Inject() and Provide."""
|
|
46
|
+
global _provide_factory
|
|
47
|
+
previous = _provide_factory
|
|
48
|
+
_provide_factory = factory
|
|
49
|
+
return previous
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_provide_marker(obj: Any) -> bool:
|
|
53
|
+
return isinstance(obj, ProvideMarker)
|
|
54
|
+
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
class _ProvideMeta(type):
|
|
57
|
+
"""Metaclass for Provide that delegates __class_getitem__ to the current factory."""
|
|
58
|
+
|
|
59
|
+
def __getitem__(cls, item: Any) -> Any:
|
|
60
|
+
# Use the current factory's __class_getitem__ if available
|
|
61
|
+
factory = _provide_factory
|
|
62
|
+
if hasattr(factory, "__class_getitem__"):
|
|
63
|
+
return factory.__class_getitem__(item) # type: ignore[attr-defined]
|
|
64
|
+
# Fallback to creating Annotated with factory instance
|
|
65
|
+
return Annotated[item, factory()]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if TYPE_CHECKING:
|
|
69
|
+
Provide = Annotated[T, ProvideMarker()]
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
|
|
73
|
+
class Provide(metaclass=_ProvideMeta):
|
|
74
|
+
pass
|
|
38
75
|
|
|
39
76
|
|
|
40
77
|
def Inject() -> Any:
|
|
41
|
-
return
|
|
78
|
+
return _provide_factory()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Alias from backward compatibility
|
|
82
|
+
is_inject_marker = is_provide_marker
|
|
42
83
|
|
|
43
84
|
|
|
44
85
|
class Event:
|
anydi/ext/fastapi.py
CHANGED
|
@@ -11,37 +11,12 @@ from fastapi.dependencies.models import Dependant
|
|
|
11
11
|
from fastapi.routing import APIRoute
|
|
12
12
|
from starlette.requests import Request
|
|
13
13
|
|
|
14
|
-
from anydi
|
|
15
|
-
from anydi._types import
|
|
14
|
+
from anydi import Container
|
|
15
|
+
from anydi._types import Inject, ProvideMarker, set_provide_factory
|
|
16
16
|
|
|
17
17
|
from .starlette.middleware import RequestScopedMiddleware
|
|
18
18
|
|
|
19
|
-
__all__ = ["
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def install(app: FastAPI, container: Container) -> None:
|
|
23
|
-
"""Install AnyDI into a FastAPI application.
|
|
24
|
-
|
|
25
|
-
This function installs the AnyDI container into a FastAPI application by attaching
|
|
26
|
-
it to the application state. It also patches the route dependencies to inject the
|
|
27
|
-
required dependencies using AnyDI.
|
|
28
|
-
"""
|
|
29
|
-
app.state.container = container # noqa
|
|
30
|
-
|
|
31
|
-
patched = []
|
|
32
|
-
|
|
33
|
-
for route in app.routes:
|
|
34
|
-
if not isinstance(route, APIRoute):
|
|
35
|
-
continue
|
|
36
|
-
for dependant in _iter_dependencies(route.dependant):
|
|
37
|
-
if dependant.cache_key in patched:
|
|
38
|
-
continue
|
|
39
|
-
patched.append(dependant.cache_key)
|
|
40
|
-
call, *params = dependant.cache_key
|
|
41
|
-
if not call:
|
|
42
|
-
continue # pragma: no cover
|
|
43
|
-
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
44
|
-
container.validate_injected_parameter(parameter, call=call)
|
|
19
|
+
__all__ = ["install", "get_container", "Inject", "RequestScopedMiddleware"]
|
|
45
20
|
|
|
46
21
|
|
|
47
22
|
def get_container(request: Request) -> Container:
|
|
@@ -49,10 +24,10 @@ def get_container(request: Request) -> Container:
|
|
|
49
24
|
return cast(Container, request.app.state.container)
|
|
50
25
|
|
|
51
26
|
|
|
52
|
-
class
|
|
27
|
+
class _ProvideMarker(params.Depends, ProvideMarker):
|
|
53
28
|
def __init__(self) -> None:
|
|
54
29
|
super().__init__(dependency=self._dependency, use_cache=True)
|
|
55
|
-
|
|
30
|
+
ProvideMarker.__init__(self)
|
|
56
31
|
|
|
57
32
|
async def _dependency(
|
|
58
33
|
self, container: Annotated[Container, Depends(get_container)]
|
|
@@ -60,13 +35,36 @@ class _Inject(params.Depends, InjectMarker):
|
|
|
60
35
|
return await container.aresolve(self.interface)
|
|
61
36
|
|
|
62
37
|
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
# Configure Inject() and Provide[T] to use FastAPI-specific marker
|
|
39
|
+
set_provide_factory(_ProvideMarker)
|
|
65
40
|
|
|
66
41
|
|
|
67
42
|
def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
|
|
68
|
-
"""Iterate over the dependencies of a dependant."""
|
|
69
43
|
yield dependant
|
|
70
44
|
if dependant.dependencies:
|
|
71
45
|
for sub_dependant in dependant.dependencies:
|
|
72
46
|
yield from _iter_dependencies(sub_dependant)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _validate_route_dependencies(
|
|
50
|
+
route: APIRoute, container: Container, patched: set[tuple[Any, ...]]
|
|
51
|
+
) -> None:
|
|
52
|
+
for dependant in _iter_dependencies(route.dependant):
|
|
53
|
+
if dependant.cache_key in patched:
|
|
54
|
+
continue
|
|
55
|
+
patched.add(dependant.cache_key)
|
|
56
|
+
call, *_ = dependant.cache_key
|
|
57
|
+
if not call:
|
|
58
|
+
continue # pragma: no cover
|
|
59
|
+
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
60
|
+
container.validate_injected_parameter(parameter, call=call)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def install(app: FastAPI, container: Container) -> None:
|
|
64
|
+
"""Install AnyDI into a FastAPI application."""
|
|
65
|
+
app.state.container = container # noqa
|
|
66
|
+
patched: set[tuple[Any, ...]] = set()
|
|
67
|
+
for route in app.routes:
|
|
68
|
+
if not isinstance(route, APIRoute):
|
|
69
|
+
continue
|
|
70
|
+
_validate_route_dependencies(route, container, patched)
|
anydi/ext/faststream.py
CHANGED
|
@@ -10,49 +10,43 @@ from faststream import ContextRepo
|
|
|
10
10
|
from faststream.broker.core.usecase import BrokerUsecase
|
|
11
11
|
|
|
12
12
|
from anydi import Container
|
|
13
|
-
from anydi._types import
|
|
13
|
+
from anydi._types import Inject, ProvideMarker, set_provide_factory
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
17
|
-
"""Install AnyDI into a FastStream broker.
|
|
18
|
-
|
|
19
|
-
This function installs the AnyDI container into a FastStream broker by attaching
|
|
20
|
-
it to the broker. It also patches the broker handlers to inject the required
|
|
21
|
-
dependencies using AnyDI.
|
|
22
|
-
"""
|
|
23
|
-
broker._container = container # type: ignore
|
|
24
|
-
|
|
25
|
-
for handler in _get_broken_handlers(broker):
|
|
26
|
-
call = handler._original_call # noqa
|
|
27
|
-
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
28
|
-
container.validate_injected_parameter(parameter, call=call)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
|
32
|
-
if (handlers := getattr(broker, "handlers", None)) is not None:
|
|
33
|
-
return [handler.calls[0][0] for handler in handlers.values()]
|
|
34
|
-
# faststream > 0.5.0
|
|
35
|
-
return [
|
|
36
|
-
subscriber.calls[0].handler
|
|
37
|
-
for subscriber in broker._subscribers.values() # noqa
|
|
38
|
-
]
|
|
15
|
+
__all__ = ["install", "get_container", "Inject"]
|
|
39
16
|
|
|
40
17
|
|
|
41
18
|
def get_container(broker: BrokerUsecase[Any, Any]) -> Container:
|
|
19
|
+
"""Get the AnyDI container from a FastStream broker."""
|
|
42
20
|
return cast(Container, getattr(broker, "_container")) # noqa
|
|
43
21
|
|
|
44
22
|
|
|
45
|
-
class
|
|
46
|
-
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
47
|
-
|
|
23
|
+
class _ProvideMarker(Depends, ProvideMarker):
|
|
48
24
|
def __init__(self) -> None:
|
|
49
25
|
super().__init__(dependency=self._dependency, use_cache=True, cast=True)
|
|
50
|
-
|
|
26
|
+
ProvideMarker.__init__(self)
|
|
51
27
|
|
|
52
28
|
async def _dependency(self, context: ContextRepo) -> Any:
|
|
53
29
|
container = get_container(context.get("broker"))
|
|
54
30
|
return await container.aresolve(self.interface)
|
|
55
31
|
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
# Configure Inject() and Provide[T] to use FastStream-specific marker
|
|
34
|
+
set_provide_factory(_ProvideMarker)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_broker_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
|
38
|
+
if (handlers := getattr(broker, "handlers", None)) is not None:
|
|
39
|
+
return [handler.calls[0][0] for handler in handlers.values()]
|
|
40
|
+
return [
|
|
41
|
+
subscriber.calls[0].handler
|
|
42
|
+
for subscriber in broker._subscribers.values() # noqa
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
47
|
+
"""Install AnyDI into a FastStream broker."""
|
|
48
|
+
broker._container = container # type: ignore
|
|
49
|
+
for handler in _get_broker_handlers(broker):
|
|
50
|
+
call = handler._original_call # noqa
|
|
51
|
+
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
52
|
+
container.validate_injected_parameter(parameter, call=call)
|
anydi/ext/pydantic_settings.py
CHANGED
|
@@ -22,7 +22,8 @@ def install(
|
|
|
22
22
|
prefix += "."
|
|
23
23
|
|
|
24
24
|
def _register_settings(_settings: BaseSettings) -> None:
|
|
25
|
-
|
|
25
|
+
settings_cls = type(_settings)
|
|
26
|
+
all_fields = {**settings_cls.model_fields, **settings_cls.model_computed_fields}
|
|
26
27
|
for setting_name, field_info in all_fields.items():
|
|
27
28
|
if isinstance(field_info, ComputedFieldInfo):
|
|
28
29
|
interface = field_info.return_type
|