anydi 0.46.0rc1__tar.gz → 0.47.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.
- {anydi-0.46.0rc1 → anydi-0.47.0}/PKG-INFO +1 -1
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/__init__.py +5 -3
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_container.py +46 -14
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_typing.py +41 -28
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/ninja/_signature.py +2 -2
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/fastapi.py +10 -12
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/faststream.py +6 -8
- {anydi-0.46.0rc1 → anydi-0.47.0}/pyproject.toml +2 -2
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/api/router.py +4 -4
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/starlette/app.py +3 -3
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_container.py +45 -43
- {anydi-0.46.0rc1 → anydi-0.47.0}/uv.lock +1 -1
- anydi-0.46.0rc1/anydi/ext/_utils.py +0 -77
- {anydi-0.46.0rc1 → anydi-0.47.0}/.editorconfig +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/.github/workflows/ci.yml +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/.gitignore +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/.readthedocs.yaml +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/LICENSE +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/Makefile +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/README.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_async.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_context.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_decorators.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_module.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_provider.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_scan.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/_scope.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/_container.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/_settings.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/_utils.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/apps.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/middleware.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/ninja/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/django/ninja/_operation.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/py.typed +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/anydi/testing.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/examples/basic.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/extensions/django.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/extensions/fastapi.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/extensions/faststream.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/extensions/pydantic_settings.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/index.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/docs/usage.md +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/mkdocs.yml +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/conftest.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/api/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/api/test_router.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/api/urls.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/conftest.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/container.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/scan/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/services.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/settings.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/test_views.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/urls.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/django/views.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fastapi/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fastapi/app.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fastapi/conftest.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fastapi/test_ext.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fastapi/test_routes.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/faststream/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/faststream/test_ext.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/faststream/test_subscribers.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/fixtures.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/starlette/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/starlette/conftest.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/starlette/test_routes.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/test_pydantic.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/ext/test_pytest_plugin.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/fixtures.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a1/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a1/handlers.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a2/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a2/a21/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a2/a21/handlers.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a3/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/a/a3/handlers.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/b/__init__.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/scan_app/b/handlers.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_decorators.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_module.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_scan.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_testing.py +0 -0
- {anydi-0.46.0rc1 → anydi-0.47.0}/tests/test_utils.py +0 -0
|
@@ -5,22 +5,24 @@ from ._decorators import injectable, provided, provider, request, singleton, tra
|
|
|
5
5
|
from ._module import Module
|
|
6
6
|
from ._provider import ProviderDef as Provider
|
|
7
7
|
from ._scope import Scope
|
|
8
|
-
from ._typing import
|
|
8
|
+
from ._typing import Inject
|
|
9
9
|
|
|
10
10
|
# Alias for dependency auto marker
|
|
11
|
-
|
|
11
|
+
# TODO: deprecate it
|
|
12
|
+
auto = Inject()
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
15
16
|
"Container",
|
|
17
|
+
"Inject",
|
|
16
18
|
"Module",
|
|
17
19
|
"Provider",
|
|
18
20
|
"Scope",
|
|
19
21
|
"auto",
|
|
20
22
|
"injectable",
|
|
23
|
+
"provided",
|
|
21
24
|
"provider",
|
|
22
25
|
"request",
|
|
23
26
|
"singleton",
|
|
24
27
|
"transient",
|
|
25
|
-
"provided",
|
|
26
28
|
]
|
|
@@ -11,7 +11,7 @@ import uuid
|
|
|
11
11
|
from collections import defaultdict
|
|
12
12
|
from collections.abc import AsyncIterator, Iterable, Iterator
|
|
13
13
|
from contextvars import ContextVar
|
|
14
|
-
from typing import Any, Callable, TypeVar, cast, overload
|
|
14
|
+
from typing import Annotated, Any, Callable, TypeVar, cast, overload
|
|
15
15
|
|
|
16
16
|
from typing_extensions import ParamSpec, Self, get_args, get_origin
|
|
17
17
|
|
|
@@ -806,36 +806,68 @@ class Container:
|
|
|
806
806
|
"""Get the injected parameters of a callable object."""
|
|
807
807
|
injected_params: dict[str, Any] = {}
|
|
808
808
|
for parameter in get_typed_parameters(call):
|
|
809
|
-
interface, should_inject = self.
|
|
809
|
+
interface, should_inject = self.validate_injected_parameter(
|
|
810
810
|
parameter, call=call
|
|
811
811
|
)
|
|
812
812
|
if should_inject:
|
|
813
813
|
injected_params[parameter.name] = interface
|
|
814
814
|
return injected_params
|
|
815
815
|
|
|
816
|
-
|
|
816
|
+
@staticmethod
|
|
817
|
+
def _unwrap_injected_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
|
|
818
|
+
if get_origin(parameter.annotation) is not Annotated:
|
|
819
|
+
return parameter
|
|
820
|
+
|
|
821
|
+
origin, *metadata = get_args(parameter.annotation)
|
|
822
|
+
|
|
823
|
+
if not metadata or not is_inject_marker(metadata[-1]):
|
|
824
|
+
return parameter
|
|
825
|
+
|
|
826
|
+
if is_inject_marker(parameter.default):
|
|
827
|
+
raise TypeError(
|
|
828
|
+
"Cannot specify `Inject` in `Annotated` and "
|
|
829
|
+
f"default value together for '{parameter.name}'"
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
if parameter.default is not inspect.Parameter.empty:
|
|
833
|
+
return parameter
|
|
834
|
+
|
|
835
|
+
marker = metadata[-1]
|
|
836
|
+
new_metadata = metadata[:-1]
|
|
837
|
+
if new_metadata:
|
|
838
|
+
new_annotation = Annotated.__class_getitem__((origin, *new_metadata)) # type: ignore
|
|
839
|
+
else:
|
|
840
|
+
new_annotation = origin
|
|
841
|
+
return parameter.replace(annotation=new_annotation, default=marker)
|
|
842
|
+
|
|
843
|
+
def validate_injected_parameter(
|
|
817
844
|
self, parameter: inspect.Parameter, *, call: Callable[..., Any]
|
|
818
845
|
) -> tuple[Any, bool]:
|
|
819
846
|
"""Validate an injected parameter."""
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if parameter.annotation is inspect.Parameter.empty:
|
|
823
|
-
raise TypeError(
|
|
824
|
-
f"Missing `{type_repr(call)}` "
|
|
825
|
-
f"parameter `{parameter.name}` annotation."
|
|
826
|
-
)
|
|
827
|
-
should_inject = True
|
|
847
|
+
parameter = self._unwrap_injected_parameter(parameter)
|
|
848
|
+
interface = parameter.annotation
|
|
828
849
|
|
|
829
|
-
|
|
850
|
+
if not is_inject_marker(parameter.default):
|
|
851
|
+
return interface, False
|
|
830
852
|
|
|
831
|
-
|
|
832
|
-
|
|
853
|
+
if interface is inspect.Parameter.empty:
|
|
854
|
+
raise TypeError(
|
|
855
|
+
f"Missing `{type_repr(call)}` parameter `{parameter.name}` annotation."
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
# Set inject marker interface
|
|
859
|
+
parameter.default.interface = interface
|
|
860
|
+
|
|
861
|
+
# TODO: temporary disable until strict is enforced (remove False)
|
|
862
|
+
if False and not self.has_provider_for(interface):
|
|
833
863
|
raise LookupError(
|
|
834
864
|
f"`{type_repr(call)}` has an unknown dependency parameter "
|
|
835
865
|
f"`{parameter.name}` with an annotation of "
|
|
836
866
|
f"`{type_repr(interface)}`."
|
|
837
867
|
)
|
|
838
868
|
|
|
869
|
+
return interface, True
|
|
870
|
+
|
|
839
871
|
############################
|
|
840
872
|
# Module Methods
|
|
841
873
|
############################
|
|
@@ -7,9 +7,9 @@ import inspect
|
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
9
|
from collections.abc import AsyncIterator, Iterator
|
|
10
|
-
from typing import Any, Callable, ForwardRef
|
|
10
|
+
from typing import Any, Callable, ForwardRef, TypeVar
|
|
11
11
|
|
|
12
|
-
from typing_extensions import
|
|
12
|
+
from typing_extensions import get_args, get_origin
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from types import NoneType
|
|
@@ -17,6 +17,9 @@ except ImportError:
|
|
|
17
17
|
NoneType = type(None)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
20
23
|
def type_repr(obj: Any) -> str:
|
|
21
24
|
"""Get a string representation of a type or object."""
|
|
22
25
|
if isinstance(obj, str):
|
|
@@ -93,48 +96,58 @@ def get_typed_parameters(obj: Callable[..., Any]) -> list[inspect.Parameter]:
|
|
|
93
96
|
]
|
|
94
97
|
|
|
95
98
|
|
|
96
|
-
class
|
|
97
|
-
|
|
99
|
+
class _Sentinel:
|
|
100
|
+
__slots__ = ("_name",)
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
def __init__(self, name: str) -> None:
|
|
103
|
+
self._name = name
|
|
100
104
|
|
|
101
|
-
def
|
|
102
|
-
return self
|
|
105
|
+
def __repr__(self) -> str:
|
|
106
|
+
return f"<{self._name}>"
|
|
103
107
|
|
|
108
|
+
def __eq__(self, other: object) -> bool:
|
|
109
|
+
return self is other
|
|
104
110
|
|
|
105
|
-
def
|
|
106
|
-
|
|
111
|
+
def __hash__(self) -> int:
|
|
112
|
+
return id(self)
|
|
107
113
|
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
return isinstance(obj, _InjectMarker)
|
|
115
|
+
NOT_SET = _Sentinel("NOT_SET")
|
|
111
116
|
|
|
112
117
|
|
|
113
|
-
class
|
|
114
|
-
"""
|
|
118
|
+
class InjectMarker:
|
|
119
|
+
"""A marker object for declaring injectable dependencies."""
|
|
115
120
|
|
|
116
|
-
__slots__ = ()
|
|
121
|
+
__slots__ = ("_interface",)
|
|
117
122
|
|
|
123
|
+
def __init__(self, interface: Any = NOT_SET) -> None:
|
|
124
|
+
self._interface = interface
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
@property
|
|
127
|
+
def interface(self) -> Any:
|
|
128
|
+
if self._interface is NOT_SET:
|
|
129
|
+
raise TypeError("Interface is not set.")
|
|
130
|
+
return self._interface
|
|
122
131
|
|
|
132
|
+
@interface.setter
|
|
133
|
+
def interface(self, interface: Any) -> None:
|
|
134
|
+
self._interface = interface
|
|
123
135
|
|
|
124
|
-
class _Sentinel:
|
|
125
|
-
__slots__ = ("_name",)
|
|
126
136
|
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
def is_inject_marker(obj: Any) -> bool:
|
|
138
|
+
return isinstance(obj, InjectMarker)
|
|
129
139
|
|
|
130
|
-
def __repr__(self) -> str:
|
|
131
|
-
return f"<{self._name}>"
|
|
132
140
|
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
def Inject() -> Any:
|
|
142
|
+
return InjectMarker()
|
|
135
143
|
|
|
136
|
-
def __hash__(self) -> int:
|
|
137
|
-
return id(self)
|
|
138
144
|
|
|
145
|
+
class Event:
|
|
146
|
+
"""Represents an event object."""
|
|
139
147
|
|
|
140
|
-
|
|
148
|
+
__slots__ = ()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def is_event_type(obj: Any) -> bool:
|
|
152
|
+
"""Checks if an object is an event type."""
|
|
153
|
+
return inspect.isclass(obj) and issubclass(obj, Event)
|
|
@@ -46,11 +46,11 @@ class ViewSignature(BaseViewSignature):
|
|
|
46
46
|
self.response_arg = name
|
|
47
47
|
continue
|
|
48
48
|
|
|
49
|
-
interface, should_inject = container.
|
|
49
|
+
interface, should_inject = container.validate_injected_parameter(
|
|
50
50
|
arg, call=self.view_func
|
|
51
51
|
)
|
|
52
52
|
if should_inject:
|
|
53
|
-
self.dependencies.append((name,
|
|
53
|
+
self.dependencies.append((name, interface))
|
|
54
54
|
continue
|
|
55
55
|
|
|
56
56
|
func_param = self._get_param_type(name, arg)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Iterator
|
|
6
|
-
from typing import Any, cast
|
|
6
|
+
from typing import Annotated, Any, cast
|
|
7
7
|
|
|
8
8
|
from fastapi import Depends, FastAPI, params
|
|
9
9
|
from fastapi.dependencies.models import Dependant
|
|
@@ -11,9 +11,8 @@ from fastapi.routing import APIRoute
|
|
|
11
11
|
from starlette.requests import Request
|
|
12
12
|
|
|
13
13
|
from anydi._container import Container
|
|
14
|
-
from anydi._typing import get_typed_parameters
|
|
14
|
+
from anydi._typing import InjectMarker, get_typed_parameters
|
|
15
15
|
|
|
16
|
-
from ._utils import HasInterface, patch_call_parameter
|
|
17
16
|
from .starlette.middleware import RequestScopedMiddleware
|
|
18
17
|
|
|
19
18
|
__all__ = ["RequestScopedMiddleware", "install", "get_container", "Inject"]
|
|
@@ -41,7 +40,7 @@ def install(app: FastAPI, container: Container) -> None:
|
|
|
41
40
|
if not call:
|
|
42
41
|
continue # pragma: no cover
|
|
43
42
|
for parameter in get_typed_parameters(call):
|
|
44
|
-
|
|
43
|
+
container.validate_injected_parameter(parameter, call=call)
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def get_container(request: Request) -> Container:
|
|
@@ -49,20 +48,19 @@ def get_container(request: Request) -> Container:
|
|
|
49
48
|
return cast(Container, request.app.state.container)
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
class
|
|
53
|
-
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
54
|
-
|
|
51
|
+
class _Inject(params.Depends, InjectMarker):
|
|
55
52
|
def __init__(self) -> None:
|
|
56
53
|
super().__init__(dependency=self._dependency, use_cache=True)
|
|
57
|
-
|
|
54
|
+
InjectMarker.__init__(self)
|
|
58
55
|
|
|
59
|
-
async def _dependency(
|
|
56
|
+
async def _dependency(
|
|
57
|
+
self, container: Annotated[Container, Depends(get_container)]
|
|
58
|
+
) -> Any:
|
|
60
59
|
return await container.aresolve(self.interface)
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
def Inject() -> Any:
|
|
64
|
-
|
|
65
|
-
return Resolver()
|
|
62
|
+
def Inject() -> Any:
|
|
63
|
+
return _Inject()
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
|
|
@@ -9,9 +9,7 @@ from faststream import ContextRepo
|
|
|
9
9
|
from faststream.broker.core.usecase import BrokerUsecase
|
|
10
10
|
|
|
11
11
|
from anydi import Container
|
|
12
|
-
from anydi._typing import get_typed_parameters
|
|
13
|
-
|
|
14
|
-
from ._utils import HasInterface, patch_call_parameter
|
|
12
|
+
from anydi._typing import InjectMarker, get_typed_parameters
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
@@ -26,7 +24,7 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
|
26
24
|
for handler in _get_broken_handlers(broker):
|
|
27
25
|
call = handler._original_call # noqa
|
|
28
26
|
for parameter in get_typed_parameters(call):
|
|
29
|
-
|
|
27
|
+
container.validate_injected_parameter(parameter, call=call)
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
|
@@ -43,17 +41,17 @@ def get_container(broker: BrokerUsecase[Any, Any]) -> Container:
|
|
|
43
41
|
return cast(Container, getattr(broker, "_container")) # noqa
|
|
44
42
|
|
|
45
43
|
|
|
46
|
-
class
|
|
44
|
+
class _Inject(Depends, InjectMarker):
|
|
47
45
|
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
48
46
|
|
|
49
47
|
def __init__(self) -> None:
|
|
50
48
|
super().__init__(dependency=self._dependency, use_cache=True, cast=True)
|
|
51
|
-
|
|
49
|
+
InjectMarker.__init__(self)
|
|
52
50
|
|
|
53
51
|
async def _dependency(self, context: ContextRepo) -> Any:
|
|
54
52
|
container = get_container(context.get("broker"))
|
|
55
53
|
return await container.aresolve(self.interface)
|
|
56
54
|
|
|
57
55
|
|
|
58
|
-
def Inject() -> Any:
|
|
59
|
-
return
|
|
56
|
+
def Inject() -> Any:
|
|
57
|
+
return _Inject()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.47.0"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = [{ name = "Anton Ruhlov", email = "antonruhlov@gmail.com" }]
|
|
6
6
|
requires-python = "~=3.9"
|
|
@@ -137,7 +137,7 @@ omit = [
|
|
|
137
137
|
]
|
|
138
138
|
|
|
139
139
|
[tool.bumpversion]
|
|
140
|
-
current_version = "0.
|
|
140
|
+
current_version = "0.47.0"
|
|
141
141
|
parse = """(?x)
|
|
142
142
|
(?P<major>0|[1-9]\\d*)\\.
|
|
143
143
|
(?P<minor>0|[1-9]\\d*)\\.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Annotated, Any
|
|
2
2
|
|
|
3
3
|
from django.http import HttpRequest
|
|
4
4
|
from ninja import Router
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
from anydi import Inject
|
|
7
7
|
|
|
8
8
|
from tests.ext.django.services import HelloService
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ router = Router()
|
|
|
14
14
|
def say_hello(
|
|
15
15
|
request: HttpRequest,
|
|
16
16
|
name: str,
|
|
17
|
-
hello_service: HelloService
|
|
17
|
+
hello_service: Annotated[HelloService, Inject()],
|
|
18
18
|
) -> Any:
|
|
19
19
|
assert hello_service.started
|
|
20
20
|
return hello_service.say_hello(name)
|
|
@@ -24,7 +24,7 @@ def say_hello(
|
|
|
24
24
|
async def say_hello_async(
|
|
25
25
|
request: HttpRequest,
|
|
26
26
|
name: str,
|
|
27
|
-
hello_service: HelloService =
|
|
27
|
+
hello_service: HelloService = Inject(),
|
|
28
28
|
) -> Any:
|
|
29
29
|
assert hello_service.started
|
|
30
30
|
return await hello_service.say_hello_async(name)
|
|
@@ -6,7 +6,7 @@ from starlette.requests import Request
|
|
|
6
6
|
from starlette.responses import JSONResponse
|
|
7
7
|
from starlette.routing import Route
|
|
8
8
|
|
|
9
|
-
from anydi import
|
|
9
|
+
from anydi import Inject
|
|
10
10
|
from anydi.ext.starlette.middleware import RequestScopedMiddleware
|
|
11
11
|
from anydi.testing import TestContainer
|
|
12
12
|
|
|
@@ -28,8 +28,8 @@ def mail_service() -> MailService:
|
|
|
28
28
|
@container.inject
|
|
29
29
|
async def send_email(
|
|
30
30
|
request: Request,
|
|
31
|
-
user_service: UserService =
|
|
32
|
-
mail_service: MailService =
|
|
31
|
+
user_service: UserService = Inject(),
|
|
32
|
+
mail_service: MailService = Inject(),
|
|
33
33
|
) -> JSONResponse:
|
|
34
34
|
data = await request.json()
|
|
35
35
|
message = cast(str, data.get("message"))
|
|
@@ -9,7 +9,7 @@ from typing import Annotated, Any, Callable, Union
|
|
|
9
9
|
import pytest
|
|
10
10
|
from typing_extensions import Self
|
|
11
11
|
|
|
12
|
-
from anydi import Container, Provider, Scope,
|
|
12
|
+
from anydi import Container, Inject, Provider, Scope, request, singleton, transient
|
|
13
13
|
from anydi._provider import ProviderKind
|
|
14
14
|
from anydi._typing import Event
|
|
15
15
|
|
|
@@ -1403,25 +1403,53 @@ class TestContainer:
|
|
|
1403
1403
|
|
|
1404
1404
|
|
|
1405
1405
|
class TestContainerInjector:
|
|
1406
|
-
def
|
|
1406
|
+
def test_inject_using_inject_marker(self, container: Container) -> None:
|
|
1407
1407
|
@container.provider(scope="singleton")
|
|
1408
|
-
def
|
|
1409
|
-
return "
|
|
1408
|
+
def message() -> str:
|
|
1409
|
+
return "test"
|
|
1410
|
+
|
|
1411
|
+
@container.inject
|
|
1412
|
+
def func(message: str = Inject()) -> str:
|
|
1413
|
+
return message
|
|
1410
1414
|
|
|
1415
|
+
result = func()
|
|
1416
|
+
|
|
1417
|
+
assert result == "test"
|
|
1418
|
+
|
|
1419
|
+
def test_inject_using_inject_annotated_marker(self, container: Container) -> None:
|
|
1411
1420
|
@container.provider(scope="singleton")
|
|
1412
|
-
def
|
|
1413
|
-
return
|
|
1421
|
+
def message() -> str:
|
|
1422
|
+
return "test"
|
|
1414
1423
|
|
|
1415
1424
|
@container.inject
|
|
1416
|
-
def func(
|
|
1417
|
-
return
|
|
1425
|
+
def func(message: Annotated[str, Inject()]) -> str:
|
|
1426
|
+
return message
|
|
1418
1427
|
|
|
1419
|
-
result = func(
|
|
1428
|
+
result = func() # type: ignore
|
|
1420
1429
|
|
|
1421
|
-
assert result == "
|
|
1430
|
+
assert result == "test"
|
|
1431
|
+
|
|
1432
|
+
def test_inject_using_inject_annotated_with_marker(
|
|
1433
|
+
self, container: Container
|
|
1434
|
+
) -> None:
|
|
1435
|
+
@container.provider(scope="singleton")
|
|
1436
|
+
def message() -> str:
|
|
1437
|
+
return "test"
|
|
1438
|
+
|
|
1439
|
+
with pytest.raises(
|
|
1440
|
+
TypeError,
|
|
1441
|
+
match=(
|
|
1442
|
+
"Cannot specify `Inject` in `Annotated` and default value "
|
|
1443
|
+
"together for 'message'"
|
|
1444
|
+
),
|
|
1445
|
+
):
|
|
1446
|
+
|
|
1447
|
+
@container.inject
|
|
1448
|
+
def func(message: Annotated[str, Inject()] = Inject()) -> str:
|
|
1449
|
+
return message
|
|
1422
1450
|
|
|
1423
1451
|
def test_inject_missing_annotation(self, container: Container) -> None:
|
|
1424
|
-
def handler(name=
|
|
1452
|
+
def handler(name=Inject()) -> str: # type: ignore
|
|
1425
1453
|
return name # type: ignore
|
|
1426
1454
|
|
|
1427
1455
|
with pytest.raises(
|
|
@@ -1433,7 +1461,7 @@ class TestContainerInjector:
|
|
|
1433
1461
|
def test_inject_unknown_dependency(self) -> None:
|
|
1434
1462
|
container = Container()
|
|
1435
1463
|
|
|
1436
|
-
def handler(message: str =
|
|
1464
|
+
def handler(message: str = Inject()) -> None:
|
|
1437
1465
|
pass
|
|
1438
1466
|
|
|
1439
1467
|
with pytest.raises(
|
|
@@ -1445,32 +1473,6 @@ class TestContainerInjector:
|
|
|
1445
1473
|
):
|
|
1446
1474
|
container.inject(handler)
|
|
1447
1475
|
|
|
1448
|
-
def test_inject_auto_marker(self, container: Container) -> None:
|
|
1449
|
-
@container.provider(scope="singleton")
|
|
1450
|
-
def message() -> str:
|
|
1451
|
-
return "test"
|
|
1452
|
-
|
|
1453
|
-
@container.inject
|
|
1454
|
-
def func(message: str = auto) -> str:
|
|
1455
|
-
return message
|
|
1456
|
-
|
|
1457
|
-
result = func()
|
|
1458
|
-
|
|
1459
|
-
assert result == "test"
|
|
1460
|
-
|
|
1461
|
-
def test_inject_auto_marker_call(self, container: Container) -> None:
|
|
1462
|
-
@container.provider(scope="singleton")
|
|
1463
|
-
def message() -> str:
|
|
1464
|
-
return "test"
|
|
1465
|
-
|
|
1466
|
-
@container.inject
|
|
1467
|
-
def func(message: str = auto()) -> str:
|
|
1468
|
-
return message
|
|
1469
|
-
|
|
1470
|
-
result = func()
|
|
1471
|
-
|
|
1472
|
-
assert result == "test"
|
|
1473
|
-
|
|
1474
1476
|
def test_inject_class(self, container: Container) -> None:
|
|
1475
1477
|
@container.provider(scope="singleton")
|
|
1476
1478
|
def ident_provider() -> str:
|
|
@@ -1482,7 +1484,7 @@ class TestContainerInjector:
|
|
|
1482
1484
|
|
|
1483
1485
|
@container.inject
|
|
1484
1486
|
class Handler:
|
|
1485
|
-
def __init__(self, name: str, service: Service =
|
|
1487
|
+
def __init__(self, name: str, service: Service = Inject()) -> None:
|
|
1486
1488
|
self.name = name
|
|
1487
1489
|
self.service = service
|
|
1488
1490
|
|
|
@@ -1510,7 +1512,7 @@ class TestContainerInjector:
|
|
|
1510
1512
|
await container.astart()
|
|
1511
1513
|
|
|
1512
1514
|
@container.inject
|
|
1513
|
-
async def func(name: str, service: Service =
|
|
1515
|
+
async def func(name: str, service: Service = Inject()) -> str:
|
|
1514
1516
|
return f"{name} = {service.ident}"
|
|
1515
1517
|
|
|
1516
1518
|
result = await func(name="service ident")
|
|
@@ -1528,8 +1530,8 @@ class TestContainerInjector:
|
|
|
1528
1530
|
|
|
1529
1531
|
def sum_handler(
|
|
1530
1532
|
value1: int,
|
|
1531
|
-
value2: Annotated[int, "value1"] =
|
|
1532
|
-
value3: Annotated[int, "value2"] =
|
|
1533
|
+
value2: Annotated[int, "value1"] = Inject(),
|
|
1534
|
+
value3: Annotated[int, "value2"] = Inject(),
|
|
1533
1535
|
) -> int:
|
|
1534
1536
|
return value1 + value2 + value3
|
|
1535
1537
|
|
|
@@ -1542,7 +1544,7 @@ class TestContainerInjector:
|
|
|
1542
1544
|
def message() -> str:
|
|
1543
1545
|
return "hello"
|
|
1544
1546
|
|
|
1545
|
-
def handler(message: str =
|
|
1547
|
+
def handler(message: str = Inject()) -> str:
|
|
1546
1548
|
return message
|
|
1547
1549
|
|
|
1548
1550
|
_ = container.run(handler)
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"""AnyDI FastAPI extension."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import inspect
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Annotated, Any, Callable
|
|
8
|
-
|
|
9
|
-
from typing_extensions import get_args, get_origin
|
|
10
|
-
|
|
11
|
-
from anydi import Container
|
|
12
|
-
from anydi._typing import _InjectMarker
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class HasInterface(_InjectMarker):
|
|
18
|
-
__slots__ = ("_interface",)
|
|
19
|
-
|
|
20
|
-
def __init__(self, interface: Any = None) -> None:
|
|
21
|
-
self._interface = interface
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def interface(self) -> Any:
|
|
25
|
-
if self._interface is None:
|
|
26
|
-
raise TypeError("Interface is not set.")
|
|
27
|
-
return self._interface
|
|
28
|
-
|
|
29
|
-
@interface.setter
|
|
30
|
-
def interface(self, interface: Any) -> None:
|
|
31
|
-
self._interface = interface
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
|
|
35
|
-
"""Patch an annotated parameter to resolve the default value."""
|
|
36
|
-
if not (
|
|
37
|
-
get_origin(parameter.annotation) is Annotated
|
|
38
|
-
and parameter.default is inspect.Parameter.empty
|
|
39
|
-
):
|
|
40
|
-
return parameter
|
|
41
|
-
|
|
42
|
-
tp_origin, *tp_metadata = get_args(parameter.annotation)
|
|
43
|
-
default = tp_metadata[-1]
|
|
44
|
-
|
|
45
|
-
if not isinstance(default, HasInterface):
|
|
46
|
-
return parameter
|
|
47
|
-
|
|
48
|
-
if (num := len(tp_metadata[:-1])) == 0:
|
|
49
|
-
interface = tp_origin
|
|
50
|
-
elif num == 1:
|
|
51
|
-
interface = Annotated[tp_origin, tp_metadata[0]]
|
|
52
|
-
elif num == 2:
|
|
53
|
-
interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
|
|
54
|
-
elif num == 3:
|
|
55
|
-
interface = Annotated[
|
|
56
|
-
tp_origin,
|
|
57
|
-
tp_metadata[0],
|
|
58
|
-
tp_metadata[1],
|
|
59
|
-
tp_metadata[2],
|
|
60
|
-
]
|
|
61
|
-
else:
|
|
62
|
-
raise TypeError("Too many annotated arguments.") # pragma: no cover
|
|
63
|
-
return parameter.replace(annotation=interface, default=default)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def patch_call_parameter(
|
|
67
|
-
container: Container, call: Callable[..., Any], parameter: inspect.Parameter
|
|
68
|
-
) -> None:
|
|
69
|
-
"""Patch a parameter to inject dependencies using AnyDI."""
|
|
70
|
-
parameter = patch_annotated_parameter(parameter)
|
|
71
|
-
|
|
72
|
-
interface, should_inject = container._validate_injected_parameter(
|
|
73
|
-
parameter, call=call
|
|
74
|
-
) # noqa
|
|
75
|
-
if should_inject:
|
|
76
|
-
parameter.default.interface = interface
|
|
77
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|