anydi 0.26.8__tar.gz → 0.27.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.26.8 → anydi-0.27.0}/PKG-INFO +3 -3
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_context.py +1 -1
- anydi-0.27.0/anydi/ext/_utils.py +89 -0
- anydi-0.27.0/anydi/ext/fastapi.py +92 -0
- anydi-0.27.0/anydi/ext/faststream.py +65 -0
- {anydi-0.26.8 → anydi-0.27.0}/pyproject.toml +8 -6
- anydi-0.26.8/anydi/ext/fastapi.py +0 -165
- {anydi-0.26.8 → anydi-0.27.0}/LICENSE +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/README.md +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/__init__.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_container.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_logger.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_module.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_scanner.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_types.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/_utils.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/_container.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/_settings.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/_utils.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/apps.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/middleware.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/ninja/__init__.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/ninja/_operation.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/django/ninja/_signature.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.26.8 → anydi-0.27.0}/anydi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anydi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Home-page: https://github.com/antonrh/anydi
|
|
6
6
|
License: MIT
|
|
@@ -32,8 +32,8 @@ Provides-Extra: async
|
|
|
32
32
|
Provides-Extra: docs
|
|
33
33
|
Requires-Dist: anyio (>=3.6.2,<4.0.0) ; extra == "async"
|
|
34
34
|
Requires-Dist: mkdocs (>=1.4.2,<2.0.0) ; extra == "docs"
|
|
35
|
-
Requires-Dist: mkdocs-material (>=9.
|
|
36
|
-
Requires-Dist: typing-extensions (>=4.
|
|
35
|
+
Requires-Dist: mkdocs-material (>=9.5.21,<10.0.0) ; extra == "docs"
|
|
36
|
+
Requires-Dist: typing-extensions (>=4.12.1,<5.0.0)
|
|
37
37
|
Project-URL: Repository, https://github.com/antonrh/anydi
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
@@ -251,7 +251,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
251
251
|
exc_val: The exception instance, if any.
|
|
252
252
|
exc_tb: The traceback, if any.
|
|
253
253
|
"""
|
|
254
|
-
return self._stack.__exit__(exc_type, exc_val, exc_tb)
|
|
254
|
+
return self._stack.__exit__(exc_type, exc_val, exc_tb) # type: ignore[return-value]
|
|
255
255
|
|
|
256
256
|
@abc.abstractmethod
|
|
257
257
|
def start(self) -> None:
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""AnyDI FastAPI extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Annotated, get_args, get_origin
|
|
10
|
+
|
|
11
|
+
from anydi import Container
|
|
12
|
+
from anydi._utils import get_full_qualname
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HasInterface:
|
|
18
|
+
_interface: Any = None
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def interface(self) -> Any:
|
|
22
|
+
if self._interface is None:
|
|
23
|
+
raise TypeError("Interface is not set.")
|
|
24
|
+
return self._interface
|
|
25
|
+
|
|
26
|
+
@interface.setter
|
|
27
|
+
def interface(self, interface: Any) -> None:
|
|
28
|
+
self._interface = interface
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
|
|
32
|
+
"""Patch an annotated parameter to resolve the default value."""
|
|
33
|
+
if not (
|
|
34
|
+
get_origin(parameter.annotation) is Annotated
|
|
35
|
+
and parameter.default is parameter.empty
|
|
36
|
+
):
|
|
37
|
+
return parameter
|
|
38
|
+
|
|
39
|
+
tp_origin, *tp_metadata = get_args(parameter.annotation)
|
|
40
|
+
default = tp_metadata[-1]
|
|
41
|
+
|
|
42
|
+
if not isinstance(default, HasInterface):
|
|
43
|
+
return parameter
|
|
44
|
+
|
|
45
|
+
if (num := len(tp_metadata[:-1])) == 0:
|
|
46
|
+
interface = tp_origin
|
|
47
|
+
elif num == 1:
|
|
48
|
+
interface = Annotated[tp_origin, tp_metadata[0]]
|
|
49
|
+
elif num == 2:
|
|
50
|
+
interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
|
|
51
|
+
elif num == 3:
|
|
52
|
+
interface = Annotated[
|
|
53
|
+
tp_origin,
|
|
54
|
+
tp_metadata[0],
|
|
55
|
+
tp_metadata[1],
|
|
56
|
+
tp_metadata[2],
|
|
57
|
+
]
|
|
58
|
+
else:
|
|
59
|
+
raise TypeError("Too many annotated arguments.") # pragma: no cover
|
|
60
|
+
return parameter.replace(annotation=interface, default=default)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def patch_call_parameter(
|
|
64
|
+
call: Callable[..., Any], parameter: inspect.Parameter, container: Container
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Patch a parameter to inject dependencies using AnyDI.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
call: The call function.
|
|
70
|
+
parameter: The parameter to patch.
|
|
71
|
+
container: The AnyDI container.
|
|
72
|
+
"""
|
|
73
|
+
parameter = patch_annotated_parameter(parameter)
|
|
74
|
+
|
|
75
|
+
if not isinstance(parameter.default, HasInterface):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
if not container.strict and not container.is_registered(parameter.annotation):
|
|
79
|
+
logger.debug(
|
|
80
|
+
f"Callable `{get_full_qualname(call)}` injected parameter "
|
|
81
|
+
f"`{parameter.name}` with an annotation of "
|
|
82
|
+
f"`{get_full_qualname(parameter.annotation)}` "
|
|
83
|
+
"is not registered. It will be registered at runtime with the "
|
|
84
|
+
"first call because it is running in non-strict mode."
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
container._validate_injected_parameter(call, parameter) # noqa
|
|
88
|
+
|
|
89
|
+
parameter.default.interface = parameter.annotation
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""AnyDI FastAPI extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Iterator, cast
|
|
7
|
+
|
|
8
|
+
from fastapi import Depends, FastAPI, params
|
|
9
|
+
from fastapi.dependencies.models import Dependant
|
|
10
|
+
from fastapi.routing import APIRoute
|
|
11
|
+
from starlette.requests import Request
|
|
12
|
+
|
|
13
|
+
from anydi import Container
|
|
14
|
+
from anydi._utils import get_typed_parameters
|
|
15
|
+
|
|
16
|
+
from ._utils import HasInterface, patch_call_parameter
|
|
17
|
+
from .starlette.middleware import RequestScopedMiddleware
|
|
18
|
+
|
|
19
|
+
__all__ = ["RequestScopedMiddleware", "install", "get_container", "Inject"]
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def install(app: FastAPI, container: Container) -> None:
|
|
25
|
+
"""Install AnyDI into a FastAPI application.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
app: The FastAPI application instance.
|
|
29
|
+
container: The container.
|
|
30
|
+
|
|
31
|
+
This function installs the AnyDI container into a FastAPI application by attaching
|
|
32
|
+
it to the application state. It also patches the route dependencies to inject the
|
|
33
|
+
required dependencies using AnyDI.
|
|
34
|
+
"""
|
|
35
|
+
app.state.container = container # noqa
|
|
36
|
+
|
|
37
|
+
patched = []
|
|
38
|
+
|
|
39
|
+
for route in app.routes:
|
|
40
|
+
if not isinstance(route, APIRoute):
|
|
41
|
+
continue
|
|
42
|
+
for dependant in _iter_dependencies(route.dependant):
|
|
43
|
+
if dependant.cache_key in patched:
|
|
44
|
+
continue
|
|
45
|
+
patched.append(dependant.cache_key)
|
|
46
|
+
call, *params = dependant.cache_key
|
|
47
|
+
if not call:
|
|
48
|
+
continue # pragma: no cover
|
|
49
|
+
for parameter in get_typed_parameters(call):
|
|
50
|
+
patch_call_parameter(call, parameter, container)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_container(request: Request) -> Container:
|
|
54
|
+
"""Get the AnyDI container from a FastAPI request.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
request: The FastAPI request.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The AnyDI container associated with the request.
|
|
61
|
+
"""
|
|
62
|
+
return cast(Container, request.app.state.container)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Resolver(HasInterface, params.Depends):
|
|
66
|
+
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
67
|
+
|
|
68
|
+
def __init__(self) -> None:
|
|
69
|
+
super().__init__(dependency=self._dependency, use_cache=True)
|
|
70
|
+
|
|
71
|
+
async def _dependency(self, container: Container = Depends(get_container)) -> Any:
|
|
72
|
+
return await container.aresolve(self.interface)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def Inject() -> Any: # noqa
|
|
76
|
+
"""Decorator for marking a function parameter as requiring injection.
|
|
77
|
+
|
|
78
|
+
The `Inject` decorator is used to mark a function parameter as requiring injection
|
|
79
|
+
of a dependency resolved by AnyDI.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The `Resolver` instance representing the parameter dependency.
|
|
83
|
+
"""
|
|
84
|
+
return Resolver()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
|
|
88
|
+
"""Iterate over the dependencies of a dependant."""
|
|
89
|
+
yield dependant
|
|
90
|
+
if dependant.dependencies:
|
|
91
|
+
for sub_dependant in dependant.dependencies:
|
|
92
|
+
yield from _iter_dependencies(sub_dependant)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""AnyDI FastStream extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from fast_depends.dependencies import Depends
|
|
9
|
+
from faststream import ContextRepo
|
|
10
|
+
from faststream.broker.core.usecase import BrokerUsecase
|
|
11
|
+
|
|
12
|
+
from anydi import Container
|
|
13
|
+
from anydi._utils import get_typed_parameters
|
|
14
|
+
|
|
15
|
+
from ._utils import HasInterface, patch_call_parameter
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
21
|
+
"""Install AnyDI into a FastStream broker.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
broker: The broker.
|
|
25
|
+
container: The container.
|
|
26
|
+
|
|
27
|
+
This function installs the AnyDI container into a FastStream broker by attaching
|
|
28
|
+
it to the broker. It also patches the broker handlers to inject the required
|
|
29
|
+
dependencies using AnyDI.
|
|
30
|
+
"""
|
|
31
|
+
broker._container = container # type: ignore[attr-defined]
|
|
32
|
+
|
|
33
|
+
for handler in _get_broken_handlers(broker):
|
|
34
|
+
call = handler._original_call # noqa
|
|
35
|
+
for parameter in get_typed_parameters(call):
|
|
36
|
+
patch_call_parameter(call, parameter, container)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
|
40
|
+
if hasattr(broker, "handlers"):
|
|
41
|
+
return [handler.calls[0][0] for handler in broker.handlers.values()]
|
|
42
|
+
# faststream > 0.5.0
|
|
43
|
+
return [
|
|
44
|
+
subscriber.calls[0].handler
|
|
45
|
+
for subscriber in broker._subscribers.values() # noqa
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_container(broker: BrokerUsecase[Any, Any]) -> Container:
|
|
50
|
+
return cast(Container, getattr(broker, "_container")) # noqa
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Resolver(HasInterface, Depends):
|
|
54
|
+
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
55
|
+
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
super().__init__(dependency=self._dependency, use_cache=True, cast=True)
|
|
58
|
+
|
|
59
|
+
async def _dependency(self, context: ContextRepo) -> Any:
|
|
60
|
+
container = get_container(context.get("broker"))
|
|
61
|
+
return await container.aresolve(self.interface)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def Inject() -> Any: # noqa
|
|
65
|
+
return Resolver()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -35,25 +35,27 @@ packages = [
|
|
|
35
35
|
|
|
36
36
|
[tool.poetry.dependencies]
|
|
37
37
|
python = "^3.8"
|
|
38
|
-
typing-extensions = "^4.
|
|
38
|
+
typing-extensions = "^4.12.1"
|
|
39
39
|
anyio = { version = "^3.6.2", optional = true }
|
|
40
40
|
mkdocs = { version = "^1.4.2", optional = true }
|
|
41
|
-
mkdocs-material = { version = "^9.
|
|
41
|
+
mkdocs-material = { version = "^9.5.21", optional = true }
|
|
42
42
|
|
|
43
43
|
[tool.poetry.extras]
|
|
44
44
|
docs = ["mkdocs", "mkdocs-material"]
|
|
45
45
|
async = ["anyio"]
|
|
46
46
|
|
|
47
47
|
[tool.poetry.group.dev.dependencies]
|
|
48
|
-
mypy = "^1.
|
|
49
|
-
ruff = "^0.4
|
|
50
|
-
pytest = "^8.1
|
|
48
|
+
mypy = "^1.11.0"
|
|
49
|
+
ruff = "^0.5.4"
|
|
50
|
+
pytest = "^8.3.1"
|
|
51
51
|
pytest-cov = "^5.0.0"
|
|
52
52
|
fastapi = "^0.100.0"
|
|
53
53
|
httpx = "^0.26.0"
|
|
54
54
|
django = "^4.2"
|
|
55
55
|
django-ninja = "^1.1.0"
|
|
56
56
|
pytest-django = "^4.8.0"
|
|
57
|
+
faststream = "^0.5.10"
|
|
58
|
+
redis = "^5.0.4"
|
|
57
59
|
|
|
58
60
|
[tool.poetry.plugins.pytest11]
|
|
59
61
|
anydi = "anydi.ext.pytest_plugin"
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
"""AnyDI FastAPI extension."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import inspect
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Any, Callable, Iterator, cast
|
|
8
|
-
|
|
9
|
-
from fastapi import Depends, FastAPI, params
|
|
10
|
-
from fastapi.dependencies.models import Dependant
|
|
11
|
-
from fastapi.routing import APIRoute
|
|
12
|
-
from starlette.requests import Request
|
|
13
|
-
from typing_extensions import Annotated, get_args, get_origin
|
|
14
|
-
|
|
15
|
-
from anydi import Container
|
|
16
|
-
from anydi._utils import get_full_qualname, get_typed_parameters
|
|
17
|
-
|
|
18
|
-
from .starlette.middleware import RequestScopedMiddleware
|
|
19
|
-
|
|
20
|
-
__all__ = ["RequestScopedMiddleware", "install", "get_container", "Inject"]
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def install(app: FastAPI, container: Container) -> None:
|
|
26
|
-
"""Install AnyDI into a FastAPI application.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
app: The FastAPI application instance.
|
|
30
|
-
container: The container.
|
|
31
|
-
|
|
32
|
-
This function installs the AnyDI container into a FastAPI application by attaching
|
|
33
|
-
it to the application state. It also patches the route dependencies to inject the
|
|
34
|
-
required dependencies using AnyDI.
|
|
35
|
-
"""
|
|
36
|
-
app.state.container = container # noqa
|
|
37
|
-
|
|
38
|
-
patched = []
|
|
39
|
-
|
|
40
|
-
for route in app.routes:
|
|
41
|
-
if not isinstance(route, APIRoute):
|
|
42
|
-
continue
|
|
43
|
-
for dependant in _iter_dependencies(route.dependant):
|
|
44
|
-
if dependant.cache_key in patched:
|
|
45
|
-
continue
|
|
46
|
-
patched.append(dependant.cache_key)
|
|
47
|
-
call, *params = dependant.cache_key
|
|
48
|
-
if not call:
|
|
49
|
-
continue # pragma: no cover
|
|
50
|
-
for parameter in get_typed_parameters(call):
|
|
51
|
-
_patch_route_parameter(call, parameter, container)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_container(request: Request) -> Container:
|
|
55
|
-
"""Get the AnyDI container from a FastAPI request.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
request: The FastAPI request.
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
The AnyDI container associated with the request.
|
|
62
|
-
"""
|
|
63
|
-
return cast(Container, request.app.state.container)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class Resolver(params.Depends):
|
|
67
|
-
"""Parameter dependency class for injecting dependencies using AnyDI."""
|
|
68
|
-
|
|
69
|
-
def __init__(self) -> None:
|
|
70
|
-
super().__init__(dependency=self._dependency, use_cache=True)
|
|
71
|
-
self._interface: Any = None
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def interface(self) -> Any:
|
|
75
|
-
if self._interface is None:
|
|
76
|
-
raise TypeError("Interface is not set.")
|
|
77
|
-
return self._interface
|
|
78
|
-
|
|
79
|
-
@interface.setter
|
|
80
|
-
def interface(self, interface: Any) -> None:
|
|
81
|
-
self._interface = interface
|
|
82
|
-
|
|
83
|
-
async def _dependency(self, container: Container = Depends(get_container)) -> Any:
|
|
84
|
-
return await container.aresolve(self.interface)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def Inject() -> Any: # noqa
|
|
88
|
-
"""Decorator for marking a function parameter as requiring injection.
|
|
89
|
-
|
|
90
|
-
The `Inject` decorator is used to mark a function parameter as requiring injection
|
|
91
|
-
of a dependency resolved by AnyDI.
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
The `Resolver` instance representing the parameter dependency.
|
|
95
|
-
"""
|
|
96
|
-
return Resolver()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
|
|
100
|
-
"""Iterate over the dependencies of a dependant."""
|
|
101
|
-
yield dependant
|
|
102
|
-
if dependant.dependencies:
|
|
103
|
-
for sub_dependant in dependant.dependencies:
|
|
104
|
-
yield from _iter_dependencies(sub_dependant)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _patch_route_parameter(
|
|
108
|
-
call: Callable[..., Any], parameter: inspect.Parameter, container: Container
|
|
109
|
-
) -> None:
|
|
110
|
-
"""Patch a parameter to inject dependencies using AnyDI.
|
|
111
|
-
|
|
112
|
-
Args:
|
|
113
|
-
call: The call function.
|
|
114
|
-
parameter: The parameter to patch.
|
|
115
|
-
container: The AnyDI container.
|
|
116
|
-
"""
|
|
117
|
-
parameter = _patch_annotated_parameter(parameter)
|
|
118
|
-
|
|
119
|
-
if not isinstance(parameter.default, Resolver):
|
|
120
|
-
return None
|
|
121
|
-
|
|
122
|
-
if not container.strict and not container.is_registered(parameter.annotation):
|
|
123
|
-
logger.debug(
|
|
124
|
-
f"Callable `{get_full_qualname(call)}` injected parameter "
|
|
125
|
-
f"`{parameter.name}` with an annotation of "
|
|
126
|
-
f"`{get_full_qualname(parameter.annotation)}` "
|
|
127
|
-
"is not registered. It will be registered at runtime with the "
|
|
128
|
-
"first call because it is running in non-strict mode."
|
|
129
|
-
)
|
|
130
|
-
else:
|
|
131
|
-
container._validate_injected_parameter(call, parameter) # noqa
|
|
132
|
-
|
|
133
|
-
parameter.default.interface = parameter.annotation
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
|
|
137
|
-
"""Patch an annotated parameter to resolve the default value."""
|
|
138
|
-
if not (
|
|
139
|
-
get_origin(parameter.annotation) is Annotated
|
|
140
|
-
and parameter.default is parameter.empty
|
|
141
|
-
):
|
|
142
|
-
return parameter
|
|
143
|
-
|
|
144
|
-
tp_origin, *tp_metadata = get_args(parameter.annotation)
|
|
145
|
-
default = tp_metadata[-1]
|
|
146
|
-
|
|
147
|
-
if not isinstance(default, Resolver):
|
|
148
|
-
return parameter
|
|
149
|
-
|
|
150
|
-
if (num := len(tp_metadata[:-1])) == 0:
|
|
151
|
-
interface = tp_origin
|
|
152
|
-
elif num == 1:
|
|
153
|
-
interface = Annotated[tp_origin, tp_metadata[0]]
|
|
154
|
-
elif num == 2:
|
|
155
|
-
interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
|
|
156
|
-
elif num == 3:
|
|
157
|
-
interface = Annotated[
|
|
158
|
-
tp_origin,
|
|
159
|
-
tp_metadata[0],
|
|
160
|
-
tp_metadata[1],
|
|
161
|
-
tp_metadata[2],
|
|
162
|
-
]
|
|
163
|
-
else:
|
|
164
|
-
raise TypeError("Too many annotated arguments.") # pragma: no cover
|
|
165
|
-
return parameter.replace(annotation=interface, default=default)
|
|
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
|