anydi 0.42.0__py3-none-any.whl → 0.43.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/_container.py +7 -10
- anydi/_decorators.py +49 -7
- anydi/_module.py +4 -3
- anydi/_scan.py +25 -25
- anydi/ext/django/_utils.py +1 -41
- {anydi-0.42.0.dist-info → anydi-0.43.0.dist-info}/METADATA +1 -1
- {anydi-0.42.0.dist-info → anydi-0.43.0.dist-info}/RECORD +10 -10
- {anydi-0.42.0.dist-info → anydi-0.43.0.dist-info}/WHEEL +0 -0
- {anydi-0.42.0.dist-info → anydi-0.43.0.dist-info}/entry_points.txt +0 -0
- {anydi-0.42.0.dist-info → anydi-0.43.0.dist-info}/licenses/LICENSE +0 -0
anydi/_container.py
CHANGED
|
@@ -11,12 +11,13 @@ 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
|
|
14
|
+
from typing import Any, Callable, TypeVar, cast, overload
|
|
15
15
|
|
|
16
16
|
from typing_extensions import ParamSpec, Self, get_args, get_origin
|
|
17
17
|
|
|
18
18
|
from ._async import run_sync
|
|
19
19
|
from ._context import InstanceContext
|
|
20
|
+
from ._decorators import is_provided
|
|
20
21
|
from ._module import ModuleDef, ModuleRegistrar
|
|
21
22
|
from ._provider import (
|
|
22
23
|
Provider,
|
|
@@ -441,17 +442,13 @@ class Container:
|
|
|
441
442
|
except LookupError:
|
|
442
443
|
if self.strict or interface is inspect.Parameter.empty:
|
|
443
444
|
raise
|
|
444
|
-
if
|
|
445
|
-
call = args[0]
|
|
446
|
-
else:
|
|
447
|
-
call = interface
|
|
448
|
-
if inspect.isclass(call) and not is_builtin_type(call):
|
|
445
|
+
if inspect.isclass(interface) and not is_builtin_type(interface):
|
|
449
446
|
# Try to get defined scope
|
|
450
|
-
if
|
|
451
|
-
scope =
|
|
447
|
+
if is_provided(interface):
|
|
448
|
+
scope = interface.__provided__["scope"]
|
|
452
449
|
else:
|
|
453
450
|
scope = parent_scope
|
|
454
|
-
return self._register_provider(
|
|
451
|
+
return self._register_provider(interface, scope, interface, **defaults)
|
|
455
452
|
raise
|
|
456
453
|
|
|
457
454
|
def _set_provider(self, provider: Provider) -> None:
|
|
@@ -793,8 +790,8 @@ class Container:
|
|
|
793
790
|
return cast(Callable[P, T], self._inject_cache[call])
|
|
794
791
|
|
|
795
792
|
injected_params = self._get_injected_params(call)
|
|
796
|
-
|
|
797
793
|
if not injected_params:
|
|
794
|
+
self._inject_cache[call] = call
|
|
798
795
|
return call
|
|
799
796
|
|
|
800
797
|
if inspect.iscoroutinefunction(call):
|
anydi/_decorators.py
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import (
|
|
3
|
+
TYPE_CHECKING,
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
Concatenate,
|
|
7
|
+
ParamSpec,
|
|
8
|
+
Protocol,
|
|
9
|
+
TypedDict,
|
|
10
|
+
TypeGuard,
|
|
11
|
+
TypeVar,
|
|
12
|
+
overload,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ._module import Module
|
|
17
|
+
|
|
3
18
|
|
|
4
|
-
from ._module import Module
|
|
5
19
|
from ._scope import Scope
|
|
6
20
|
|
|
7
21
|
T = TypeVar("T")
|
|
8
22
|
P = ParamSpec("P")
|
|
9
23
|
|
|
10
24
|
ClassT = TypeVar("ClassT", bound=type)
|
|
11
|
-
ModuleT = TypeVar("ModuleT", bound=Module)
|
|
25
|
+
ModuleT = TypeVar("ModuleT", bound="Module")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProvidedMetadata(TypedDict):
|
|
29
|
+
"""Metadata for classes marked as provided by AnyDI."""
|
|
30
|
+
|
|
31
|
+
scope: Scope
|
|
12
32
|
|
|
13
33
|
|
|
14
34
|
def provided(*, scope: Scope) -> Callable[[ClassT], ClassT]:
|
|
15
35
|
"""Decorator for marking a class as provided by AnyDI with a specific scope."""
|
|
16
36
|
|
|
17
37
|
def decorator(cls: ClassT) -> ClassT:
|
|
18
|
-
cls.__provided__ =
|
|
19
|
-
cls.__scope__ = scope
|
|
38
|
+
cls.__provided__ = ProvidedMetadata(scope=scope)
|
|
20
39
|
return cls
|
|
21
40
|
|
|
22
41
|
return decorator
|
|
@@ -28,6 +47,14 @@ request = provided(scope="request")
|
|
|
28
47
|
singleton = provided(scope="singleton")
|
|
29
48
|
|
|
30
49
|
|
|
50
|
+
class Provided(Protocol):
|
|
51
|
+
__provided__: ProvidedMetadata
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_provided(cls: Any) -> TypeGuard[type[Provided]]:
|
|
55
|
+
return hasattr(cls, "__provided__")
|
|
56
|
+
|
|
57
|
+
|
|
31
58
|
class ProviderMetadata(TypedDict):
|
|
32
59
|
scope: Scope
|
|
33
60
|
override: bool
|
|
@@ -49,8 +76,15 @@ def provider(
|
|
|
49
76
|
return decorator
|
|
50
77
|
|
|
51
78
|
|
|
79
|
+
class Provider(Protocol):
|
|
80
|
+
__provider__: ProviderMetadata
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def is_provider(obj: Callable[..., Any]) -> TypeGuard[Provider]:
|
|
84
|
+
return hasattr(obj, "__provider__")
|
|
85
|
+
|
|
86
|
+
|
|
52
87
|
class InjectableMetadata(TypedDict):
|
|
53
|
-
wrapped: bool
|
|
54
88
|
tags: Iterable[str] | None
|
|
55
89
|
|
|
56
90
|
|
|
@@ -71,10 +105,18 @@ def injectable(
|
|
|
71
105
|
"""Decorator for marking a function or method as requiring dependency injection."""
|
|
72
106
|
|
|
73
107
|
def decorator(inner: Callable[P, T]) -> Callable[P, T]:
|
|
74
|
-
inner.__injectable__ = InjectableMetadata(
|
|
108
|
+
inner.__injectable__ = InjectableMetadata(tags=tags) # type: ignore
|
|
75
109
|
return inner
|
|
76
110
|
|
|
77
111
|
if func is None:
|
|
78
112
|
return decorator
|
|
79
113
|
|
|
80
114
|
return decorator(func)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Injectable(Protocol):
|
|
118
|
+
__injectable__: InjectableMetadata
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_injectable(obj: Callable[..., Any]) -> TypeGuard[Injectable]:
|
|
122
|
+
return hasattr(obj, "__injectable__")
|
anydi/_module.py
CHANGED
|
@@ -4,9 +4,10 @@ import importlib
|
|
|
4
4
|
import inspect
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Callable
|
|
6
6
|
|
|
7
|
+
from ._decorators import ProviderMetadata, is_provider
|
|
8
|
+
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from ._container import Container
|
|
9
|
-
from ._decorators import ProviderMetadata
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class ModuleMeta(type):
|
|
@@ -14,9 +15,9 @@ class ModuleMeta(type):
|
|
|
14
15
|
|
|
15
16
|
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> Any:
|
|
16
17
|
attrs["providers"] = [
|
|
17
|
-
(name,
|
|
18
|
+
(name, value.__provider__)
|
|
18
19
|
for name, value in attrs.items()
|
|
19
|
-
if
|
|
20
|
+
if is_provider(value)
|
|
20
21
|
]
|
|
21
22
|
return super().__new__(cls, name, bases, attrs)
|
|
22
23
|
|
anydi/_scan.py
CHANGED
|
@@ -6,9 +6,9 @@ import pkgutil
|
|
|
6
6
|
from collections.abc import Iterable
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from types import ModuleType
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Union
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, Union
|
|
10
10
|
|
|
11
|
-
from ._decorators import
|
|
11
|
+
from ._decorators import is_injectable
|
|
12
12
|
from ._typing import get_typed_parameters, is_marker
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -73,9 +73,8 @@ class Scanner:
|
|
|
73
73
|
|
|
74
74
|
return dependencies
|
|
75
75
|
|
|
76
|
-
@staticmethod
|
|
77
76
|
def _scan_module(
|
|
78
|
-
module: ModuleType, *, tags: Iterable[str]
|
|
77
|
+
self, module: ModuleType, *, tags: Iterable[str]
|
|
79
78
|
) -> list[ScannedDependency]:
|
|
80
79
|
"""Scan a module for decorated members."""
|
|
81
80
|
dependencies: list[ScannedDependency] = []
|
|
@@ -84,27 +83,28 @@ class Scanner:
|
|
|
84
83
|
if getattr(member, "__module__", None) != module.__name__:
|
|
85
84
|
continue
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
member,
|
|
89
|
-
"__injectable__",
|
|
90
|
-
InjectableMetadata(wrapped=False, tags=[]),
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
should_include = False
|
|
94
|
-
if metadata["wrapped"]:
|
|
95
|
-
should_include = True
|
|
96
|
-
elif tags and metadata["tags"]:
|
|
97
|
-
should_include = bool(set(metadata["tags"]) & set(tags))
|
|
98
|
-
elif tags and not metadata["tags"]:
|
|
99
|
-
continue # tags are provided but member has none
|
|
100
|
-
|
|
101
|
-
if not should_include:
|
|
102
|
-
for param in get_typed_parameters(member):
|
|
103
|
-
if is_marker(param.default):
|
|
104
|
-
should_include = True
|
|
105
|
-
break
|
|
106
|
-
|
|
107
|
-
if should_include:
|
|
86
|
+
if self._should_include_member(member, tags=tags):
|
|
108
87
|
dependencies.append(ScannedDependency(member=member, module=module))
|
|
109
88
|
|
|
110
89
|
return dependencies
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _should_include_member(
|
|
93
|
+
member: Callable[..., Any], *, tags: Iterable[str]
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Determine if a member should be included based on tags or marker defaults."""
|
|
96
|
+
|
|
97
|
+
if is_injectable(member):
|
|
98
|
+
member_tags = set(member.__injectable__["tags"] or [])
|
|
99
|
+
if tags:
|
|
100
|
+
return bool(set(tags) & member_tags)
|
|
101
|
+
return True # No tags passed → include all injectables
|
|
102
|
+
|
|
103
|
+
# If no tags are passed and not explicitly injectable,
|
|
104
|
+
# check for parameter markers
|
|
105
|
+
if not tags:
|
|
106
|
+
for param in get_typed_parameters(member):
|
|
107
|
+
if is_marker(param.default):
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
anydi/ext/django/_utils.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterator
|
|
4
|
-
from functools import wraps
|
|
5
4
|
from typing import Annotated, Any
|
|
6
5
|
|
|
7
6
|
from django.conf import settings
|
|
@@ -9,7 +8,6 @@ from django.core.cache import BaseCache, caches
|
|
|
9
8
|
from django.db import connections
|
|
10
9
|
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
11
10
|
from django.urls import URLPattern, URLResolver, get_resolver
|
|
12
|
-
from typing_extensions import get_origin
|
|
13
11
|
|
|
14
12
|
from anydi import Container
|
|
15
13
|
|
|
@@ -29,14 +27,11 @@ def register_settings(
|
|
|
29
27
|
continue
|
|
30
28
|
|
|
31
29
|
container.register(
|
|
32
|
-
Annotated[
|
|
30
|
+
Annotated[type(setting_value), f"{prefix}{setting_name}"],
|
|
33
31
|
_get_setting_value(setting_value),
|
|
34
32
|
scope="singleton",
|
|
35
33
|
)
|
|
36
34
|
|
|
37
|
-
# Patch AnyDI to resolve Any types for annotated settings
|
|
38
|
-
_patch_any_typed_annotated(container, prefix=prefix)
|
|
39
|
-
|
|
40
35
|
|
|
41
36
|
def register_components(container: Container) -> None:
|
|
42
37
|
"""Register Django components into the container."""
|
|
@@ -91,38 +86,3 @@ def iter_urlpatterns(
|
|
|
91
86
|
|
|
92
87
|
def _get_setting_value(value: Any) -> Any:
|
|
93
88
|
return lambda: value
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _any_typed_interface(interface: Any, prefix: str) -> Any:
|
|
97
|
-
origin = get_origin(interface)
|
|
98
|
-
if origin is not Annotated:
|
|
99
|
-
return interface # pragma: no cover
|
|
100
|
-
named = interface.__metadata__[-1]
|
|
101
|
-
|
|
102
|
-
if isinstance(named, str) and named.startswith(prefix):
|
|
103
|
-
_, setting_name = named.rsplit(prefix, maxsplit=1)
|
|
104
|
-
return Annotated[Any, f"{prefix}{setting_name}"]
|
|
105
|
-
return interface
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _patch_any_typed_annotated(container: Container, *, prefix: str) -> None:
|
|
109
|
-
def _patch_resolve(resolve: Any) -> Any:
|
|
110
|
-
@wraps(resolve)
|
|
111
|
-
def wrapper(interface: Any) -> Any:
|
|
112
|
-
return resolve(_any_typed_interface(interface, prefix))
|
|
113
|
-
|
|
114
|
-
return wrapper
|
|
115
|
-
|
|
116
|
-
def _patch_aresolve(resolve: Any) -> Any:
|
|
117
|
-
@wraps(resolve)
|
|
118
|
-
async def wrapper(interface: Any) -> Any:
|
|
119
|
-
return await resolve(_any_typed_interface(interface, prefix))
|
|
120
|
-
|
|
121
|
-
return wrapper
|
|
122
|
-
|
|
123
|
-
container.resolve = _patch_resolve( # type: ignore
|
|
124
|
-
container.resolve
|
|
125
|
-
)
|
|
126
|
-
container.aresolve = _patch_aresolve( # type: ignore
|
|
127
|
-
container.aresolve
|
|
128
|
-
)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
anydi/__init__.py,sha256=UH3N0LIw4ysoV0_EFgtDNioxEQxOmeytYbJnS8MXIXM,532
|
|
2
2
|
anydi/_async.py,sha256=KhRd3RmZFcwNDzrMm8ctA1gwrg-6m_7laECTYsZdF5k,1445
|
|
3
|
-
anydi/_container.py,sha256=
|
|
3
|
+
anydi/_container.py,sha256=sftWiHUTJBMQelglSbThn-Y0IeYw15vZnmC7sFqDpxQ,31529
|
|
4
4
|
anydi/_context.py,sha256=_Xy8cTpRskb4cxTd-Fe-5NnIZyBe1DnovkofhdeUfmw,3020
|
|
5
|
-
anydi/_decorators.py,sha256=
|
|
6
|
-
anydi/_module.py,sha256=
|
|
5
|
+
anydi/_decorators.py,sha256=F3yBeGQSz1EsulZaEvYn3cd6FEjJRMoyA6u1QCbEwcs,2813
|
|
6
|
+
anydi/_module.py,sha256=QPvP27JndZkwl-FYUZWscJm6yfkNzjwoFGURyDhb6Pc,2582
|
|
7
7
|
anydi/_provider.py,sha256=ig2ecn-STmFGcpkLE5A5OM35XHtU2NsxFVrGp2CvuvM,2123
|
|
8
|
-
anydi/_scan.py,sha256=
|
|
8
|
+
anydi/_scan.py,sha256=WOdsPaoRZPYw4kBzoBXyWhkiwuxY8_UjHfa0c9QFyLc,3639
|
|
9
9
|
anydi/_scope.py,sha256=PFHjPb2-n0vhRo9mvD_craTFfoJBzR3y-N3_0apL5Q0,258
|
|
10
10
|
anydi/_typing.py,sha256=m7KnVenDE1E420IeYz2Ocw6dhddmSFbuBBgkOXtl9pA,3709
|
|
11
11
|
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -19,7 +19,7 @@ anydi/ext/pytest_plugin.py,sha256=57Y1G4MOpwV43Fib88JGeRI8jACcj5Jy8R-7ZT0SZJs,47
|
|
|
19
19
|
anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
|
|
20
20
|
anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
|
|
21
21
|
anydi/ext/django/_settings.py,sha256=Z0RlAuXoO73oahWeMkK10w8c-4uCBde-DBpeKTV5USY,853
|
|
22
|
-
anydi/ext/django/_utils.py,sha256=
|
|
22
|
+
anydi/ext/django/_utils.py,sha256=N1DZlY5RHxbb7TUWH-BWWC78GzUX_a1oYEZ8hT8WgaE,2663
|
|
23
23
|
anydi/ext/django/apps.py,sha256=mfOLUQ1i-B4e3f5NPitbRVuVUlm4QoFzuJVXsI8zGWA,2791
|
|
24
24
|
anydi/ext/django/middleware.py,sha256=5OUdp0OWRozyW338Sq04BDhacaFlyUTTOduS_7EwCTA,854
|
|
25
25
|
anydi/ext/django/ninja/__init__.py,sha256=4J0zoHPK9itbTVrjjvLX6Ftrsb2ND8bITqNDIJzEhks,520
|
|
@@ -27,8 +27,8 @@ anydi/ext/django/ninja/_operation.py,sha256=wk5EOjLY3KVIHk9jMCGsFsja9-dQmMOpLpHX
|
|
|
27
27
|
anydi/ext/django/ninja/_signature.py,sha256=UVLmKpYvH1fNb9C7ffP50KCmh0BE6unHegfss5dvpVU,2261
|
|
28
28
|
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
anydi/ext/starlette/middleware.py,sha256=MxnzshAs-CMvjJp0r457k52MzBL8O4KAuClnF6exBdU,803
|
|
30
|
-
anydi-0.
|
|
31
|
-
anydi-0.
|
|
32
|
-
anydi-0.
|
|
33
|
-
anydi-0.
|
|
34
|
-
anydi-0.
|
|
30
|
+
anydi-0.43.0.dist-info/METADATA,sha256=QwACa2m9ugixipKP81nR-jASIqmk9c2W_Y3LNPYvW58,4957
|
|
31
|
+
anydi-0.43.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
32
|
+
anydi-0.43.0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
|
|
33
|
+
anydi-0.43.0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
|
|
34
|
+
anydi-0.43.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|