anydi 0.61.0__tar.gz → 0.63.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.61.0 → anydi-0.63.0}/PKG-INFO +1 -1
- {anydi-0.61.0 → anydi-0.63.0}/anydi/__init__.py +2 -1
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_container.py +41 -3
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_injector.py +9 -8
- anydi-0.63.0/anydi/_marker.py +116 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_resolver.py +9 -5
- anydi-0.63.0/anydi/_types.py +45 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/fastapi.py +16 -8
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/faststream.py +20 -8
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/typer.py +25 -4
- {anydi-0.61.0 → anydi-0.63.0}/pyproject.toml +1 -1
- anydi-0.61.0/anydi/_types.py +0 -113
- {anydi-0.61.0 → anydi-0.63.0}/README.md +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_async_lock.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_context.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_decorators.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_module.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_provider.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/_scanner.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/py.typed +0 -0
- {anydi-0.61.0 → anydi-0.63.0}/anydi/testing.py +0 -0
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from ._container import Container, import_container
|
|
4
4
|
from ._decorators import injectable, provided, provider, request, singleton, transient
|
|
5
|
+
from ._marker import Inject, Provide
|
|
5
6
|
from ._module import Module
|
|
6
7
|
from ._provider import ProviderDef as Provider
|
|
7
|
-
from ._types import
|
|
8
|
+
from ._types import Scope
|
|
8
9
|
|
|
9
10
|
# Alias for dependency auto marker
|
|
10
11
|
# TODO: deprecate it
|
|
@@ -18,11 +18,19 @@ from typing_extensions import ParamSpec, Self, type_repr
|
|
|
18
18
|
from ._context import InstanceContext
|
|
19
19
|
from ._decorators import is_provided
|
|
20
20
|
from ._injector import Injector
|
|
21
|
+
from ._marker import Marker
|
|
21
22
|
from ._module import ModuleDef, ModuleRegistrar
|
|
22
23
|
from ._provider import Provider, ProviderDef, ProviderKind, ProviderParameter
|
|
23
24
|
from ._resolver import Resolver
|
|
24
25
|
from ._scanner import PackageOrIterable, Scanner
|
|
25
|
-
from ._types import
|
|
26
|
+
from ._types import (
|
|
27
|
+
NOT_SET,
|
|
28
|
+
Event,
|
|
29
|
+
Scope,
|
|
30
|
+
is_event_type,
|
|
31
|
+
is_iterator_type,
|
|
32
|
+
is_none_type,
|
|
33
|
+
)
|
|
26
34
|
|
|
27
35
|
T = TypeVar("T", bound=Any)
|
|
28
36
|
P = ParamSpec("P")
|
|
@@ -423,6 +431,10 @@ class Container:
|
|
|
423
431
|
parameters: list[ProviderParameter] = []
|
|
424
432
|
scope_provider: dict[Scope, Provider] = {}
|
|
425
433
|
|
|
434
|
+
# Precompute constant checks
|
|
435
|
+
is_scoped = scope not in ("singleton", "transient")
|
|
436
|
+
has_defaults = defaults is not None
|
|
437
|
+
|
|
426
438
|
for parameter in signature.parameters.values():
|
|
427
439
|
if parameter.annotation is inspect.Parameter.empty:
|
|
428
440
|
raise TypeError(
|
|
@@ -442,10 +454,32 @@ class Container:
|
|
|
442
454
|
)
|
|
443
455
|
has_default = default is not NOT_SET
|
|
444
456
|
|
|
457
|
+
# Check if provider exists before attempting to register (for scoped only)
|
|
458
|
+
was_auto_registered = (
|
|
459
|
+
is_scoped and parameter.annotation not in self._providers
|
|
460
|
+
)
|
|
461
|
+
|
|
445
462
|
try:
|
|
446
463
|
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
447
464
|
except LookupError as exc:
|
|
448
|
-
if parameter.name in defaults
|
|
465
|
+
if (has_defaults and parameter.name in defaults) or has_default:
|
|
466
|
+
continue
|
|
467
|
+
# For request/custom scopes, allow unregistered dependencies
|
|
468
|
+
# They might be provided via context.set()
|
|
469
|
+
if is_scoped:
|
|
470
|
+
# Add to unresolved list to provide better error messages
|
|
471
|
+
# and prevent infinite recursion
|
|
472
|
+
self._resolver.add_unresolved(parameter.annotation)
|
|
473
|
+
parameters.append(
|
|
474
|
+
ProviderParameter(
|
|
475
|
+
name=parameter.name,
|
|
476
|
+
annotation=parameter.annotation,
|
|
477
|
+
default=default,
|
|
478
|
+
has_default=has_default,
|
|
479
|
+
provider=None, # Will check context at runtime
|
|
480
|
+
shared_scope=True, # Same scope, check context
|
|
481
|
+
)
|
|
482
|
+
)
|
|
449
483
|
continue
|
|
450
484
|
unresolved_parameter = parameter
|
|
451
485
|
unresolved_exc = exc
|
|
@@ -455,6 +489,10 @@ class Container:
|
|
|
455
489
|
if sub_provider.scope not in scope_provider:
|
|
456
490
|
scope_provider[sub_provider.scope] = sub_provider
|
|
457
491
|
|
|
492
|
+
# If provider was auto-registered and has same scope, mark as unresolved
|
|
493
|
+
if was_auto_registered and sub_provider.scope == scope:
|
|
494
|
+
self._resolver.add_unresolved(parameter.annotation)
|
|
495
|
+
|
|
458
496
|
parameters.append(
|
|
459
497
|
ProviderParameter(
|
|
460
498
|
name=parameter.name,
|
|
@@ -687,7 +725,7 @@ class Container:
|
|
|
687
725
|
|
|
688
726
|
def validate_injected_parameter(
|
|
689
727
|
self, parameter: inspect.Parameter, *, call: Callable[..., Any]
|
|
690
|
-
) -> tuple[Any, bool]:
|
|
728
|
+
) -> tuple[Any, bool, Marker | None]:
|
|
691
729
|
"""Validate an injected parameter."""
|
|
692
730
|
return self._injector.validate_parameter(parameter, call=call)
|
|
693
731
|
|
|
@@ -17,7 +17,7 @@ from typing import (
|
|
|
17
17
|
|
|
18
18
|
from typing_extensions import ParamSpec, type_repr
|
|
19
19
|
|
|
20
|
-
from .
|
|
20
|
+
from ._marker import Marker, is_marker
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from ._container import Container
|
|
@@ -69,20 +69,21 @@ class Injector:
|
|
|
69
69
|
"""Get the injected parameters of a callable object."""
|
|
70
70
|
injected_params: dict[str, Any] = {}
|
|
71
71
|
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
72
|
-
interface, should_inject = self.validate_parameter(parameter, call=call)
|
|
72
|
+
interface, should_inject, _ = self.validate_parameter(parameter, call=call)
|
|
73
73
|
if should_inject:
|
|
74
74
|
injected_params[parameter.name] = interface
|
|
75
75
|
return injected_params
|
|
76
76
|
|
|
77
77
|
def validate_parameter(
|
|
78
78
|
self, parameter: inspect.Parameter, *, call: Callable[..., Any]
|
|
79
|
-
) -> tuple[Any, bool]:
|
|
79
|
+
) -> tuple[Any, bool, Marker | None]:
|
|
80
80
|
"""Validate an injected parameter."""
|
|
81
81
|
parameter = self.unwrap_parameter(parameter)
|
|
82
82
|
interface = parameter.annotation
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
marker = parameter.default
|
|
85
|
+
if not is_marker(marker):
|
|
86
|
+
return interface, False, None
|
|
86
87
|
|
|
87
88
|
if interface is inspect.Parameter.empty:
|
|
88
89
|
raise TypeError(
|
|
@@ -99,7 +100,7 @@ class Injector:
|
|
|
99
100
|
f"`{type_repr(interface)}`."
|
|
100
101
|
)
|
|
101
102
|
|
|
102
|
-
return interface, True
|
|
103
|
+
return interface, True, marker
|
|
103
104
|
|
|
104
105
|
@staticmethod
|
|
105
106
|
def unwrap_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
|
|
@@ -108,10 +109,10 @@ class Injector:
|
|
|
108
109
|
|
|
109
110
|
origin, *metadata = get_args(parameter.annotation)
|
|
110
111
|
|
|
111
|
-
if not metadata or not
|
|
112
|
+
if not metadata or not is_marker(metadata[-1]):
|
|
112
113
|
return parameter
|
|
113
114
|
|
|
114
|
-
if
|
|
115
|
+
if is_marker(parameter.default):
|
|
115
116
|
raise TypeError(
|
|
116
117
|
"Cannot specify `Inject` in `Annotated` and "
|
|
117
118
|
f"default value together for '{parameter.name}'"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Provide marker implementation and utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Annotated, Any, TypeVar
|
|
6
|
+
|
|
7
|
+
from ._types import NOT_SET
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Marker:
|
|
13
|
+
"""Marker stored in annotations or defaults to request injection."""
|
|
14
|
+
|
|
15
|
+
__slots__ = ("_interface", "_attrs", "_preferred_owner", "_current_owner")
|
|
16
|
+
|
|
17
|
+
_FRAMEWORK_ATTRS = frozenset({"dependency", "use_cache", "cast", "cast_result"})
|
|
18
|
+
|
|
19
|
+
def __init__(self, interface: Any = NOT_SET) -> None:
|
|
20
|
+
# Avoid reinitializing attributes when mixins call __init__ multiple times
|
|
21
|
+
if not hasattr(self, "_attrs"):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self._attrs: dict[str, dict[str, Any]] = {}
|
|
24
|
+
self._preferred_owner = "fastapi"
|
|
25
|
+
self._current_owner: str | None = None
|
|
26
|
+
self._interface = interface
|
|
27
|
+
|
|
28
|
+
def set_owner(self, owner: str) -> None:
|
|
29
|
+
self._preferred_owner = owner
|
|
30
|
+
|
|
31
|
+
def _store_attr(self, name: str, value: Any) -> None:
|
|
32
|
+
owner = self._current_owner or self._preferred_owner
|
|
33
|
+
self._attrs.setdefault(owner, {})[name] = value
|
|
34
|
+
|
|
35
|
+
def _get_attr(self, name: str) -> Any:
|
|
36
|
+
owner = self._preferred_owner
|
|
37
|
+
if owner in self._attrs and name in self._attrs[owner]:
|
|
38
|
+
return self._attrs[owner][name]
|
|
39
|
+
for attrs in self._attrs.values():
|
|
40
|
+
if name in attrs:
|
|
41
|
+
return attrs[name]
|
|
42
|
+
raise AttributeError(name)
|
|
43
|
+
|
|
44
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
45
|
+
if name in self._FRAMEWORK_ATTRS and hasattr(self, "_attrs"):
|
|
46
|
+
self._store_attr(name, value)
|
|
47
|
+
else:
|
|
48
|
+
super().__setattr__(name, value)
|
|
49
|
+
|
|
50
|
+
def __getattr__(self, name: str) -> Any:
|
|
51
|
+
if name in self._FRAMEWORK_ATTRS and hasattr(self, "_attrs"):
|
|
52
|
+
return self._get_attr(name)
|
|
53
|
+
raise AttributeError(name)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def interface(self) -> Any:
|
|
57
|
+
if self._interface is NOT_SET:
|
|
58
|
+
raise TypeError("Interface is not set.")
|
|
59
|
+
return self._interface
|
|
60
|
+
|
|
61
|
+
@interface.setter
|
|
62
|
+
def interface(self, interface: Any) -> None:
|
|
63
|
+
self._interface = interface
|
|
64
|
+
|
|
65
|
+
def __class_getitem__(cls, item: Any) -> Any:
|
|
66
|
+
return Annotated[item, cls()]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_marker_cls: type[Marker] = Marker
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def extend_marker(marker_cls: type[Marker]) -> None:
|
|
73
|
+
"""Register an additional framework-specific provide marker."""
|
|
74
|
+
|
|
75
|
+
global _marker_cls
|
|
76
|
+
previous = _marker_cls
|
|
77
|
+
|
|
78
|
+
if previous is Marker:
|
|
79
|
+
_marker_cls = marker_cls
|
|
80
|
+
else:
|
|
81
|
+
name = f"Marker_{marker_cls.__name__}_{previous.__name__}"
|
|
82
|
+
|
|
83
|
+
def __init__(self: Marker) -> None:
|
|
84
|
+
marker_cls.__init__(self)
|
|
85
|
+
previous.__init__(self)
|
|
86
|
+
|
|
87
|
+
combined: type[Marker] = type(
|
|
88
|
+
name, (marker_cls, previous), {"__init__": __init__}
|
|
89
|
+
)
|
|
90
|
+
_marker_cls = combined
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def is_marker(obj: Any) -> bool:
|
|
94
|
+
return isinstance(obj, Marker)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _ProvideMeta(type):
|
|
98
|
+
"""Metaclass for Provide that delegates __class_getitem__ to the active marker."""
|
|
99
|
+
|
|
100
|
+
def __getitem__(cls, item: Any) -> Any:
|
|
101
|
+
if hasattr(_marker_cls, "__class_getitem__"):
|
|
102
|
+
return _marker_cls.__class_getitem__(item) # type: ignore
|
|
103
|
+
return Annotated[item, _marker_cls()]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if TYPE_CHECKING:
|
|
107
|
+
Provide = Annotated[T, Marker()]
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
|
|
111
|
+
class Provide(metaclass=_ProvideMeta):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def Inject() -> Any:
|
|
116
|
+
return _marker_cls()
|
|
@@ -228,11 +228,15 @@ class Resolver:
|
|
|
228
228
|
)
|
|
229
229
|
create_lines.append(f" arg_{idx} = defaults['{name}']")
|
|
230
230
|
create_lines.append(" else:")
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
# Direct dict access for shared scope params (avoids method call)
|
|
232
|
+
if param_shared_scopes[idx]:
|
|
233
|
+
create_lines.append(
|
|
234
|
+
f" cached = (context._instances.get("
|
|
235
|
+
f"_param_annotations[{idx}], NOT_SET_) "
|
|
236
|
+
f"if context is not None else NOT_SET_)"
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
create_lines.append(" cached = NOT_SET_")
|
|
236
240
|
create_lines.append(" if cached is NOT_SET_:")
|
|
237
241
|
create_lines.append(
|
|
238
242
|
f" if _param_annotations[{idx}] in "
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Shared AnyDI utils module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import AsyncIterator, Iterator
|
|
7
|
+
from types import NoneType
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
from typing_extensions import Sentinel
|
|
11
|
+
|
|
12
|
+
Scope = Literal["transient", "singleton", "request"] | str
|
|
13
|
+
|
|
14
|
+
NOT_SET = Sentinel("NOT_SET")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Event:
|
|
18
|
+
"""Represents an event object."""
|
|
19
|
+
|
|
20
|
+
__slots__ = ()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_event_type(obj: Any) -> bool:
|
|
24
|
+
"""Checks if an object is an event type."""
|
|
25
|
+
return inspect.isclass(obj) and issubclass(obj, Event)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_context_manager(obj: Any) -> bool:
|
|
29
|
+
"""Check if the given object is a context manager."""
|
|
30
|
+
return hasattr(obj, "__enter__") and hasattr(obj, "__exit__")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_async_context_manager(obj: Any) -> bool:
|
|
34
|
+
"""Check if the given object is an async context manager."""
|
|
35
|
+
return hasattr(obj, "__aenter__") and hasattr(obj, "__aexit__")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_none_type(tp: Any) -> bool:
|
|
39
|
+
"""Check if the given object is a None type."""
|
|
40
|
+
return tp in (None, NoneType)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_iterator_type(tp: Any) -> bool:
|
|
44
|
+
"""Check if the given object is an iterator type."""
|
|
45
|
+
return tp in (Iterator, AsyncIterator)
|
|
@@ -12,7 +12,7 @@ from fastapi.routing import APIRoute
|
|
|
12
12
|
from starlette.requests import Request
|
|
13
13
|
|
|
14
14
|
from anydi import Container, Inject
|
|
15
|
-
from anydi.
|
|
15
|
+
from anydi._marker import Marker, extend_marker
|
|
16
16
|
|
|
17
17
|
from .starlette.middleware import RequestScopedMiddleware
|
|
18
18
|
|
|
@@ -24,19 +24,23 @@ def get_container(request: Request) -> Container:
|
|
|
24
24
|
return cast(Container, request.app.state.container)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class
|
|
27
|
+
class FastAPIMarker(params.Depends, Marker):
|
|
28
28
|
def __init__(self) -> None:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
Marker.__init__(self)
|
|
30
|
+
self._current_owner = "fastapi"
|
|
31
|
+
params.Depends.__init__(
|
|
32
|
+
self, dependency=self._fastapi_dependency, use_cache=True
|
|
33
|
+
)
|
|
34
|
+
self._current_owner = None
|
|
35
|
+
|
|
36
|
+
async def _fastapi_dependency(
|
|
33
37
|
self, container: Annotated[Container, Depends(get_container)]
|
|
34
38
|
) -> Any:
|
|
35
39
|
return await container.aresolve(self.interface)
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
# Configure Inject() and Provide[T] to use FastAPI-specific marker
|
|
39
|
-
|
|
43
|
+
extend_marker(FastAPIMarker)
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
|
|
@@ -57,7 +61,11 @@ def _validate_route_dependencies(
|
|
|
57
61
|
if not call:
|
|
58
62
|
continue # pragma: no cover
|
|
59
63
|
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
60
|
-
container.validate_injected_parameter(
|
|
64
|
+
_, should_inject, marker = container.validate_injected_parameter(
|
|
65
|
+
parameter, call=call
|
|
66
|
+
)
|
|
67
|
+
if should_inject and marker:
|
|
68
|
+
marker.set_owner("fastapi")
|
|
61
69
|
|
|
62
70
|
|
|
63
71
|
def install(app: FastAPI, container: Container) -> None:
|
|
@@ -10,7 +10,7 @@ from fast_depends.dependencies import Dependant
|
|
|
10
10
|
from faststream import BaseMiddleware, ContextRepo, StreamMessage
|
|
11
11
|
|
|
12
12
|
from anydi import Container
|
|
13
|
-
from anydi.
|
|
13
|
+
from anydi._marker import Inject, Marker, extend_marker
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from faststream._internal.basic_types import AsyncFuncAny
|
|
@@ -47,18 +47,26 @@ class RequestScopedMiddleware(BaseMiddleware):
|
|
|
47
47
|
return await call_next(msg)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class
|
|
50
|
+
class FastStreamMarker(Dependant, Marker):
|
|
51
51
|
def __init__(self) -> None:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
Marker.__init__(self)
|
|
53
|
+
self._current_owner = "faststream"
|
|
54
|
+
Dependant.__init__(
|
|
55
|
+
self,
|
|
56
|
+
self._faststream_dependency,
|
|
57
|
+
use_cache=True,
|
|
58
|
+
cast=True,
|
|
59
|
+
cast_result=True,
|
|
60
|
+
)
|
|
61
|
+
self._current_owner = None
|
|
62
|
+
|
|
63
|
+
async def _faststream_dependency(self, context: ContextRepo) -> Any:
|
|
56
64
|
container = get_container_from_context(context)
|
|
57
65
|
return await container.aresolve(self.interface)
|
|
58
66
|
|
|
59
67
|
|
|
60
68
|
# Configure Inject() and Provide[T] to use FastStream-specific marker
|
|
61
|
-
|
|
69
|
+
extend_marker(FastStreamMarker)
|
|
62
70
|
|
|
63
71
|
|
|
64
72
|
def _get_broker_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
|
|
@@ -71,4 +79,8 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
|
|
|
71
79
|
for handler in _get_broker_handlers(broker):
|
|
72
80
|
call = handler._original_call # noqa
|
|
73
81
|
for parameter in inspect.signature(call, eval_str=True).parameters.values():
|
|
74
|
-
container.validate_injected_parameter(
|
|
82
|
+
_, should_inject, marker = container.validate_injected_parameter(
|
|
83
|
+
parameter, call=call
|
|
84
|
+
)
|
|
85
|
+
if should_inject and marker:
|
|
86
|
+
marker.set_owner("faststream")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import concurrent.futures
|
|
5
6
|
import contextlib
|
|
6
7
|
import functools
|
|
7
8
|
import inspect
|
|
@@ -9,6 +10,7 @@ from collections.abc import Awaitable, Callable
|
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
import anyio
|
|
13
|
+
import sniffio
|
|
12
14
|
from typer import Typer
|
|
13
15
|
|
|
14
16
|
from anydi import Container, Scope
|
|
@@ -22,7 +24,16 @@ def _wrap_async_callback_no_injection(callback: Callable[..., Any]) -> Any:
|
|
|
22
24
|
|
|
23
25
|
@functools.wraps(callback)
|
|
24
26
|
def async_no_injection_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
25
|
-
|
|
27
|
+
# Check if we're already in an async context
|
|
28
|
+
try:
|
|
29
|
+
sniffio.current_async_library()
|
|
30
|
+
# We're in an async context, run anyio.run() in a separate thread
|
|
31
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
32
|
+
future = executor.submit(anyio.run, callback, *args, **kwargs)
|
|
33
|
+
return future.result()
|
|
34
|
+
except sniffio.AsyncLibraryNotFoundError:
|
|
35
|
+
# Not in an async context, can use anyio.run directly
|
|
36
|
+
return anyio.run(callback, *args, **kwargs)
|
|
26
37
|
|
|
27
38
|
return async_no_injection_wrapper
|
|
28
39
|
|
|
@@ -54,7 +65,16 @@ def _wrap_async_callback_with_injection(
|
|
|
54
65
|
|
|
55
66
|
return await container.run(callback, *args, **kwargs)
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
# Check if we're already in an async context
|
|
69
|
+
try:
|
|
70
|
+
sniffio.current_async_library()
|
|
71
|
+
# We're in an async context, run anyio.run() in a separate thread
|
|
72
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
73
|
+
future = executor.submit(anyio.run, _run)
|
|
74
|
+
return future.result()
|
|
75
|
+
except sniffio.AsyncLibraryNotFoundError:
|
|
76
|
+
# Not in an async context, can use anyio.run directly
|
|
77
|
+
return anyio.run(_run)
|
|
58
78
|
|
|
59
79
|
# Update the wrapper's signature to only show non-injected parameters to Typer
|
|
60
80
|
async_wrapper.__signature__ = sig.replace(parameters=non_injected_params) # type: ignore
|
|
@@ -71,9 +91,10 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
|
|
|
71
91
|
|
|
72
92
|
# Validate parameters and collect which ones need injection
|
|
73
93
|
for parameter in sig.parameters.values():
|
|
74
|
-
interface, should_inject = container.validate_injected_parameter(
|
|
94
|
+
interface, should_inject, _ = container.validate_injected_parameter(
|
|
75
95
|
parameter, call=callback
|
|
76
96
|
)
|
|
97
|
+
processed_parameter = container._injector.unwrap_parameter(parameter)
|
|
77
98
|
if should_inject:
|
|
78
99
|
injected_param_names.add(parameter.name)
|
|
79
100
|
try:
|
|
@@ -82,7 +103,7 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
|
|
|
82
103
|
if inspect.isclass(interface) and is_provided(interface):
|
|
83
104
|
scopes.add(interface.__provided__["scope"])
|
|
84
105
|
else:
|
|
85
|
-
non_injected_params.add(
|
|
106
|
+
non_injected_params.add(processed_parameter)
|
|
86
107
|
|
|
87
108
|
# If no parameters need injection and callback is not async, return original
|
|
88
109
|
if not injected_param_names and not inspect.iscoroutinefunction(callback):
|
anydi-0.61.0/anydi/_types.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"""Shared AnyDI utils module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import inspect
|
|
6
|
-
from collections.abc import AsyncIterator, Callable, Iterator
|
|
7
|
-
from types import NoneType
|
|
8
|
-
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar
|
|
9
|
-
|
|
10
|
-
from typing_extensions import Sentinel
|
|
11
|
-
|
|
12
|
-
T = TypeVar("T")
|
|
13
|
-
|
|
14
|
-
Scope = Literal["transient", "singleton", "request"] | str
|
|
15
|
-
|
|
16
|
-
NOT_SET = Sentinel("NOT_SET")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ProvideMarker:
|
|
20
|
-
"""A marker object for declaring dependency."""
|
|
21
|
-
|
|
22
|
-
__slots__ = ("_interface",)
|
|
23
|
-
|
|
24
|
-
def __init__(self, interface: Any = NOT_SET) -> None:
|
|
25
|
-
self._interface = interface
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def interface(self) -> Any:
|
|
29
|
-
if self._interface is NOT_SET:
|
|
30
|
-
raise TypeError("Interface is not set.")
|
|
31
|
-
return self._interface
|
|
32
|
-
|
|
33
|
-
@interface.setter
|
|
34
|
-
def interface(self, interface: Any) -> None:
|
|
35
|
-
self._interface = interface
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def Inject() -> Any:
|
|
78
|
-
return _provide_factory()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# Alias from backward compatibility
|
|
82
|
-
is_inject_marker = is_provide_marker
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class Event:
|
|
86
|
-
"""Represents an event object."""
|
|
87
|
-
|
|
88
|
-
__slots__ = ()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def is_event_type(obj: Any) -> bool:
|
|
92
|
-
"""Checks if an object is an event type."""
|
|
93
|
-
return inspect.isclass(obj) and issubclass(obj, Event)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def is_context_manager(obj: Any) -> bool:
|
|
97
|
-
"""Check if the given object is a context manager."""
|
|
98
|
-
return hasattr(obj, "__enter__") and hasattr(obj, "__exit__")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def is_async_context_manager(obj: Any) -> bool:
|
|
102
|
-
"""Check if the given object is an async context manager."""
|
|
103
|
-
return hasattr(obj, "__aenter__") and hasattr(obj, "__aexit__")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def is_none_type(tp: Any) -> bool:
|
|
107
|
-
"""Check if the given object is a None type."""
|
|
108
|
-
return tp in (None, NoneType)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def is_iterator_type(tp: Any) -> bool:
|
|
112
|
-
"""Check if the given object is an iterator type."""
|
|
113
|
-
return tp in (Iterator, AsyncIterator)
|
|
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
|