anydi 0.24.2__tar.gz → 0.25.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.24.2 → anydi-0.25.0}/PKG-INFO +57 -1
- {anydi-0.24.2 → anydi-0.25.0}/README.md +56 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_container.py +15 -7
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_context.py +2 -2
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_scanner.py +4 -4
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_types.py +6 -4
- anydi-0.25.0/anydi/_utils.py +122 -0
- anydi-0.25.0/anydi/ext/django/__init__.py +9 -0
- anydi-0.25.0/anydi/ext/django/_container.py +18 -0
- anydi-0.25.0/anydi/ext/django/_settings.py +39 -0
- anydi-0.25.0/anydi/ext/django/_utils.py +111 -0
- anydi-0.25.0/anydi/ext/django/apps.py +82 -0
- anydi-0.25.0/anydi/ext/django/middleware.py +26 -0
- anydi-0.25.0/anydi/ext/django/ninja/__init__.py +16 -0
- anydi-0.25.0/anydi/ext/django/ninja/_operation.py +75 -0
- anydi-0.25.0/anydi/ext/django/ninja/_signature.py +64 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/ext/fastapi.py +2 -2
- {anydi-0.24.2 → anydi-0.25.0}/pyproject.toml +18 -2
- anydi-0.24.2/anydi/_utils.py +0 -113
- {anydi-0.24.2 → anydi-0.25.0}/LICENSE +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/__init__.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_logger.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/_module.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.24.2 → anydi-0.25.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.24.2 → anydi-0.25.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.25.0
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Home-page: https://github.com/antonrh/anydi
|
|
6
6
|
License: MIT
|
|
@@ -139,3 +139,59 @@ def say_hello(message: str = Inject()) -> dict[str, str]:
|
|
|
139
139
|
anydi.ext.fastapi.install(app, container)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## Django Ninja Example
|
|
145
|
+
|
|
146
|
+
*container.py*
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from anydi import Container
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_container() -> Container:
|
|
153
|
+
container = Container()
|
|
154
|
+
|
|
155
|
+
@container.provider(scope="singleton")
|
|
156
|
+
def message() -> str:
|
|
157
|
+
return "Hello, World!"
|
|
158
|
+
|
|
159
|
+
return container
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
*settings.py*
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
INSTALLED_APPS = [
|
|
166
|
+
...
|
|
167
|
+
"anydi.ext.django",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
ANYDI = {
|
|
171
|
+
"CONTAINER_FACTORY": "myapp.container.get_container",
|
|
172
|
+
"PATCH_NINJA": True,
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
*urls.py*
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from django.http import HttpRequest
|
|
180
|
+
from django.urls import path
|
|
181
|
+
from ninja import NinjaAPI
|
|
182
|
+
|
|
183
|
+
from anydi import auto
|
|
184
|
+
|
|
185
|
+
api = NinjaAPI()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@api.get("/hello")
|
|
189
|
+
def say_hello(request: HttpRequest, message: str = auto) -> dict[str, str]:
|
|
190
|
+
return {"message": message}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
urlpatterns = [
|
|
194
|
+
path("api/", api.urls),
|
|
195
|
+
]
|
|
196
|
+
```
|
|
197
|
+
|
|
@@ -98,3 +98,59 @@ def say_hello(message: str = Inject()) -> dict[str, str]:
|
|
|
98
98
|
# Install the container into the FastAPI app
|
|
99
99
|
anydi.ext.fastapi.install(app, container)
|
|
100
100
|
```
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Django Ninja Example
|
|
105
|
+
|
|
106
|
+
*container.py*
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from anydi import Container
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_container() -> Container:
|
|
113
|
+
container = Container()
|
|
114
|
+
|
|
115
|
+
@container.provider(scope="singleton")
|
|
116
|
+
def message() -> str:
|
|
117
|
+
return "Hello, World!"
|
|
118
|
+
|
|
119
|
+
return container
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
*settings.py*
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
INSTALLED_APPS = [
|
|
126
|
+
...
|
|
127
|
+
"anydi.ext.django",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
ANYDI = {
|
|
131
|
+
"CONTAINER_FACTORY": "myapp.container.get_container",
|
|
132
|
+
"PATCH_NINJA": True,
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
*urls.py*
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from django.http import HttpRequest
|
|
140
|
+
from django.urls import path
|
|
141
|
+
from ninja import NinjaAPI
|
|
142
|
+
|
|
143
|
+
from anydi import auto
|
|
144
|
+
|
|
145
|
+
api = NinjaAPI()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@api.get("/hello")
|
|
149
|
+
def say_hello(request: HttpRequest, message: str = auto) -> dict[str, str]:
|
|
150
|
+
return {"message": message}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
urlpatterns = [
|
|
154
|
+
path("api/", api.urls),
|
|
155
|
+
]
|
|
156
|
+
```
|
|
@@ -43,7 +43,13 @@ from ._logger import logger
|
|
|
43
43
|
from ._module import Module, ModuleRegistry
|
|
44
44
|
from ._scanner import Scanner
|
|
45
45
|
from ._types import AnyInterface, Interface, Provider, Scope, is_marker
|
|
46
|
-
from ._utils import
|
|
46
|
+
from ._utils import (
|
|
47
|
+
get_full_qualname,
|
|
48
|
+
get_typed_parameters,
|
|
49
|
+
get_typed_return_annotation,
|
|
50
|
+
has_resource_origin,
|
|
51
|
+
is_builtin_type,
|
|
52
|
+
)
|
|
47
53
|
|
|
48
54
|
T = TypeVar("T", bound=Any)
|
|
49
55
|
P = ParamSpec("P")
|
|
@@ -327,7 +333,7 @@ class Container:
|
|
|
327
333
|
"""
|
|
328
334
|
related_providers = []
|
|
329
335
|
|
|
330
|
-
for parameter in provider.parameters
|
|
336
|
+
for parameter in provider.parameters:
|
|
331
337
|
if parameter.annotation is inspect._empty: # noqa
|
|
332
338
|
raise TypeError(
|
|
333
339
|
f"Missing provider `{provider}` "
|
|
@@ -366,7 +372,7 @@ class Container:
|
|
|
366
372
|
The auto scope, or None if the auto scope cannot be detected.
|
|
367
373
|
"""
|
|
368
374
|
has_transient, has_request, has_singleton = False, False, False
|
|
369
|
-
for parameter in
|
|
375
|
+
for parameter in get_typed_parameters(obj):
|
|
370
376
|
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
371
377
|
if not has_transient and sub_provider.scope == "transient":
|
|
372
378
|
has_transient = True
|
|
@@ -711,14 +717,16 @@ class Container:
|
|
|
711
717
|
Raises:
|
|
712
718
|
TypeError: If the provider return annotation is missing or invalid.
|
|
713
719
|
"""
|
|
714
|
-
annotation =
|
|
720
|
+
annotation = get_typed_return_annotation(obj)
|
|
715
721
|
|
|
716
|
-
if annotation is
|
|
722
|
+
if annotation is None:
|
|
717
723
|
raise TypeError(
|
|
718
724
|
f"Missing `{get_full_qualname(obj)}` provider return annotation."
|
|
719
725
|
)
|
|
720
726
|
|
|
721
|
-
|
|
727
|
+
origin = get_origin(annotation)
|
|
728
|
+
|
|
729
|
+
if has_resource_origin(origin):
|
|
722
730
|
args = get_args(annotation)
|
|
723
731
|
if args:
|
|
724
732
|
return args[0]
|
|
@@ -741,7 +749,7 @@ class Container:
|
|
|
741
749
|
of the injected parameters.
|
|
742
750
|
"""
|
|
743
751
|
injected_params = {}
|
|
744
|
-
for parameter in
|
|
752
|
+
for parameter in get_typed_parameters(obj):
|
|
745
753
|
if not is_marker(parameter.default):
|
|
746
754
|
continue
|
|
747
755
|
try:
|
|
@@ -97,7 +97,7 @@ class ScopedContext(abc.ABC):
|
|
|
97
97
|
The arguments for the provider.
|
|
98
98
|
"""
|
|
99
99
|
args, kwargs = [], {}
|
|
100
|
-
for parameter in provider.parameters
|
|
100
|
+
for parameter in provider.parameters:
|
|
101
101
|
instance = self.container.resolve(parameter.annotation)
|
|
102
102
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
103
103
|
args.append(instance)
|
|
@@ -117,7 +117,7 @@ class ScopedContext(abc.ABC):
|
|
|
117
117
|
The arguments for the provider.
|
|
118
118
|
"""
|
|
119
119
|
args, kwargs = [], {}
|
|
120
|
-
for parameter in provider.parameters
|
|
120
|
+
for parameter in provider.parameters:
|
|
121
121
|
instance = await self.container.aresolve(parameter.annotation)
|
|
122
122
|
if parameter.kind == parameter.POSITIONAL_ONLY:
|
|
123
123
|
args.append(instance)
|
|
@@ -20,7 +20,7 @@ from typing import (
|
|
|
20
20
|
from typing_extensions import NamedTuple, ParamSpec
|
|
21
21
|
|
|
22
22
|
from ._types import is_marker
|
|
23
|
-
from ._utils import
|
|
23
|
+
from ._utils import get_typed_parameters
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from ._container import Container
|
|
@@ -157,10 +157,10 @@ class Scanner:
|
|
|
157
157
|
|
|
158
158
|
# Get by Marker
|
|
159
159
|
if inspect.isclass(member):
|
|
160
|
-
|
|
160
|
+
parameters = get_typed_parameters(member.__init__)
|
|
161
161
|
else:
|
|
162
|
-
|
|
163
|
-
for parameter in
|
|
162
|
+
parameters = get_typed_parameters(member)
|
|
163
|
+
for parameter in parameters:
|
|
164
164
|
if is_marker(parameter.default):
|
|
165
165
|
dependencies.append(
|
|
166
166
|
self._create_dependency(member=member, module=module)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import inspect
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from functools import cached_property
|
|
4
|
-
from typing import Any, Callable,
|
|
6
|
+
from typing import Any, Callable, Type, TypeVar, Union
|
|
5
7
|
|
|
6
8
|
from typing_extensions import Annotated, Literal, Self, TypeAlias
|
|
7
9
|
|
|
8
|
-
from ._utils import get_full_qualname,
|
|
10
|
+
from ._utils import get_full_qualname, get_typed_parameters
|
|
9
11
|
|
|
10
12
|
Scope = Literal["transient", "singleton", "request"]
|
|
11
13
|
|
|
@@ -58,13 +60,13 @@ class Provider:
|
|
|
58
60
|
return get_full_qualname(self.obj)
|
|
59
61
|
|
|
60
62
|
@cached_property
|
|
61
|
-
def parameters(self) ->
|
|
63
|
+
def parameters(self) -> list[inspect.Parameter]:
|
|
62
64
|
"""Returns the parameters of the provider as a mapping.
|
|
63
65
|
|
|
64
66
|
Returns:
|
|
65
67
|
The parameters of the provider.
|
|
66
68
|
"""
|
|
67
|
-
return
|
|
69
|
+
return get_typed_parameters(self.obj)
|
|
68
70
|
|
|
69
71
|
@cached_property
|
|
70
72
|
def is_class(self) -> bool:
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Shared AnyDI utils module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
import functools
|
|
7
|
+
import inspect
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any, AsyncIterator, Callable, ForwardRef, Iterator, TypeVar, cast
|
|
10
|
+
|
|
11
|
+
from typing_extensions import Annotated, ParamSpec, get_origin
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import anyio # noqa
|
|
15
|
+
except ImportError:
|
|
16
|
+
anyio = None # type: ignore[assignment]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if sys.version_info < (3, 9): # pragma: nocover
|
|
20
|
+
|
|
21
|
+
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
|
22
|
+
return type_._evaluate(globalns, localns) # noqa
|
|
23
|
+
|
|
24
|
+
else:
|
|
25
|
+
|
|
26
|
+
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
|
27
|
+
return cast(Any, type_)._evaluate(globalns, localns, set()) # noqa
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
P = ParamSpec("P")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_full_qualname(obj: Any) -> str:
|
|
35
|
+
"""Get the fully qualified name of an object."""
|
|
36
|
+
origin = get_origin(obj)
|
|
37
|
+
if origin is Annotated:
|
|
38
|
+
metadata = ", ".join(
|
|
39
|
+
[
|
|
40
|
+
f'"{arg}"' if isinstance(arg, str) else str(arg)
|
|
41
|
+
for arg in obj.__metadata__
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
return f"Annotated[{get_full_qualname(obj.__args__[0])}, {metadata}]]"
|
|
45
|
+
|
|
46
|
+
qualname = getattr(obj, "__qualname__", None)
|
|
47
|
+
module_name = getattr(obj, "__module__", None)
|
|
48
|
+
if qualname is None:
|
|
49
|
+
qualname = type(obj).__qualname__
|
|
50
|
+
|
|
51
|
+
if module_name is None:
|
|
52
|
+
module_name = type(obj).__module__
|
|
53
|
+
|
|
54
|
+
if module_name == builtins.__name__:
|
|
55
|
+
return qualname
|
|
56
|
+
return f"{module_name}.{qualname}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_builtin_type(tp: type[Any]) -> bool:
|
|
60
|
+
"""Check if the given type is a built-in type."""
|
|
61
|
+
return tp.__module__ == builtins.__name__
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def make_forwardref(annotation: str, globalns: dict[str, Any]) -> Any:
|
|
65
|
+
"""Create a forward reference from a string annotation."""
|
|
66
|
+
forward_ref = ForwardRef(annotation)
|
|
67
|
+
return evaluate_forwardref(forward_ref, globalns, globalns)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
|
|
71
|
+
"""Get the typed annotation of a parameter."""
|
|
72
|
+
if isinstance(annotation, str):
|
|
73
|
+
annotation = ForwardRef(annotation)
|
|
74
|
+
annotation = evaluate_forwardref(annotation, globalns, globalns)
|
|
75
|
+
return annotation
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_typed_return_annotation(obj: Callable[..., Any]) -> Any:
|
|
79
|
+
"""Get the typed return annotation of a callable object."""
|
|
80
|
+
signature = inspect.signature(obj)
|
|
81
|
+
annotation = signature.return_annotation
|
|
82
|
+
if annotation is inspect.Signature.empty:
|
|
83
|
+
return None
|
|
84
|
+
globalns = getattr(obj, "__globals__", {})
|
|
85
|
+
return get_typed_annotation(annotation, globalns)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_typed_parameters(obj: Callable[..., Any]) -> list[inspect.Parameter]:
|
|
89
|
+
"""Get the typed parameters of a callable object."""
|
|
90
|
+
globalns = getattr(obj, "__globals__", {})
|
|
91
|
+
return [
|
|
92
|
+
parameter.replace(
|
|
93
|
+
annotation=get_typed_annotation(parameter.annotation, globalns)
|
|
94
|
+
)
|
|
95
|
+
for name, parameter in inspect.signature(obj).parameters.items()
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_resource_origins = (
|
|
100
|
+
get_origin(Iterator),
|
|
101
|
+
get_origin(AsyncIterator),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def has_resource_origin(origin: Any) -> bool:
|
|
106
|
+
"""Check if the given origin is a resource origin."""
|
|
107
|
+
return origin in _resource_origins
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def run_async(
|
|
111
|
+
func: Callable[P, T],
|
|
112
|
+
/,
|
|
113
|
+
*args: P.args,
|
|
114
|
+
**kwargs: P.kwargs,
|
|
115
|
+
) -> T:
|
|
116
|
+
"""Runs the given function asynchronously using the `anyio` library."""
|
|
117
|
+
if not anyio:
|
|
118
|
+
raise ImportError(
|
|
119
|
+
"`anyio` library is not currently installed. Please make sure to install "
|
|
120
|
+
"it first, or consider using `anydi[full]` instead."
|
|
121
|
+
)
|
|
122
|
+
return await anyio.to_thread.run_sync(functools.partial(func, *args, **kwargs))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
from django.apps.registry import apps
|
|
4
|
+
from django.utils.functional import SimpleLazyObject
|
|
5
|
+
|
|
6
|
+
import anydi
|
|
7
|
+
|
|
8
|
+
from .apps import ContainerConfig
|
|
9
|
+
|
|
10
|
+
__all__ = ["container"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_container() -> anydi.Container:
|
|
14
|
+
app_config = cast(ContainerConfig, apps.get_app_config(ContainerConfig.label))
|
|
15
|
+
return app_config.container
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
container = cast(anydi.Container, SimpleLazyObject(_get_container))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from typing_extensions import TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Settings(TypedDict):
|
|
10
|
+
CONTAINER_FACTORY: str | None
|
|
11
|
+
STRICT_MODE: bool
|
|
12
|
+
REGISTER_SETTINGS: bool
|
|
13
|
+
REGISTER_COMPONENTS: bool
|
|
14
|
+
INJECT_URLCONF: str | None
|
|
15
|
+
MODULES: Sequence[str]
|
|
16
|
+
SCAN_PACKAGES: Sequence[str]
|
|
17
|
+
PATCH_NINJA: bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DEFAULTS = Settings(
|
|
21
|
+
CONTAINER_FACTORY=None,
|
|
22
|
+
STRICT_MODE=False,
|
|
23
|
+
REGISTER_SETTINGS=False,
|
|
24
|
+
REGISTER_COMPONENTS=False,
|
|
25
|
+
MODULES=[],
|
|
26
|
+
PATCH_NINJA=False,
|
|
27
|
+
INJECT_URLCONF=None,
|
|
28
|
+
SCAN_PACKAGES=[],
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_settings() -> Settings:
|
|
33
|
+
"""Get the AnyDI settings from the Django settings."""
|
|
34
|
+
return Settings(
|
|
35
|
+
**{
|
|
36
|
+
**DEFAULTS,
|
|
37
|
+
**getattr(settings, "ANYDI", {}),
|
|
38
|
+
}
|
|
39
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.core.cache import BaseCache, caches
|
|
9
|
+
from django.db import connections
|
|
10
|
+
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
11
|
+
from django.urls import URLPattern, URLResolver, get_resolver
|
|
12
|
+
from typing_extensions import Annotated, get_origin
|
|
13
|
+
|
|
14
|
+
import anydi
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_settings(
|
|
18
|
+
container: anydi.Container, prefix: str = "django.conf.setting."
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Register Django settings into the container."""
|
|
21
|
+
|
|
22
|
+
def _get_setting_value(value: Any) -> Any:
|
|
23
|
+
return lambda: value
|
|
24
|
+
|
|
25
|
+
for setting_name in dir(settings):
|
|
26
|
+
setting_value = getattr(settings, setting_name)
|
|
27
|
+
if not setting_name.isupper():
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
container.register(
|
|
31
|
+
Annotated[Any, f"{prefix}{setting_name}"],
|
|
32
|
+
_get_setting_value(setting_value),
|
|
33
|
+
scope="singleton",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def _resolve(resolve: Any) -> Any:
|
|
37
|
+
@wraps(resolve)
|
|
38
|
+
def wrapper(interface: Any) -> Any:
|
|
39
|
+
return resolve(_aware_settings(interface, prefix))
|
|
40
|
+
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
def _aresolve(resolve: Any) -> Any:
|
|
44
|
+
@wraps(resolve)
|
|
45
|
+
async def wrapper(interface: Any) -> Any:
|
|
46
|
+
return await resolve(_aware_settings(interface, prefix))
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
|
|
50
|
+
# Patch resolvers
|
|
51
|
+
container.resolve = _resolve(container.resolve) # type: ignore[method-assign] # noqa
|
|
52
|
+
container.aresolve = _aresolve(container.aresolve) # type: ignore[method-assign] # noqa
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _aware_settings(interface: Any, prefix: str) -> Any:
|
|
56
|
+
origin = get_origin(interface)
|
|
57
|
+
if origin is not Annotated:
|
|
58
|
+
return interface # pragma: no cover
|
|
59
|
+
named = interface.__metadata__[-1]
|
|
60
|
+
|
|
61
|
+
if isinstance(named, str) and named.startswith(prefix):
|
|
62
|
+
_, setting_name = named.rsplit(prefix, maxsplit=1)
|
|
63
|
+
return Annotated[Any, f"{prefix}{setting_name}"]
|
|
64
|
+
return interface
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def register_components(container: anydi.Container) -> None:
|
|
68
|
+
"""Register Django components into the container."""
|
|
69
|
+
|
|
70
|
+
# Register caches
|
|
71
|
+
def _get_cache(cache_name: str) -> Any:
|
|
72
|
+
return lambda: caches[cache_name]
|
|
73
|
+
|
|
74
|
+
for cache_name in caches:
|
|
75
|
+
container.register(
|
|
76
|
+
Annotated[BaseCache, cache_name],
|
|
77
|
+
_get_cache(cache_name),
|
|
78
|
+
scope="singleton",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Register database connections
|
|
82
|
+
def _get_connection(alias: str) -> Any:
|
|
83
|
+
return lambda: connections[alias]
|
|
84
|
+
|
|
85
|
+
for alias in connections:
|
|
86
|
+
container.register(
|
|
87
|
+
Annotated[BaseDatabaseWrapper, alias],
|
|
88
|
+
_get_connection(alias),
|
|
89
|
+
scope="singleton",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def inject_urlpatterns(container: anydi.Container, *, urlconf: str) -> None:
|
|
94
|
+
"""Auto-inject the container into views."""
|
|
95
|
+
resolver = get_resolver(urlconf)
|
|
96
|
+
for pattern in iter_urlpatterns(resolver.url_patterns):
|
|
97
|
+
# Skip django-ninja views
|
|
98
|
+
if pattern.lookup_str.startswith("ninja."):
|
|
99
|
+
continue # pragma: no cover
|
|
100
|
+
pattern.callback = container.inject(pattern.callback)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def iter_urlpatterns(
|
|
104
|
+
urlpatterns: list[URLPattern | URLResolver],
|
|
105
|
+
) -> Iterator[URLPattern]:
|
|
106
|
+
"""Iterate over all views in urlpatterns."""
|
|
107
|
+
for url_pattern in urlpatterns:
|
|
108
|
+
if isinstance(url_pattern, URLResolver):
|
|
109
|
+
yield from iter_urlpatterns(url_pattern.url_patterns)
|
|
110
|
+
else:
|
|
111
|
+
yield url_pattern
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import types
|
|
5
|
+
from typing import Callable, cast
|
|
6
|
+
|
|
7
|
+
from django.apps import AppConfig
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
10
|
+
from django.utils.module_loading import import_string
|
|
11
|
+
|
|
12
|
+
import anydi
|
|
13
|
+
|
|
14
|
+
from ._settings import get_settings
|
|
15
|
+
from ._utils import inject_urlpatterns, register_components, register_settings
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ContainerConfig(AppConfig): # type: ignore[misc]
|
|
21
|
+
name = "anydi.ext.django"
|
|
22
|
+
label = "anydi_django"
|
|
23
|
+
|
|
24
|
+
def __init__(self, app_name: str, app_module: types.ModuleType | None) -> None:
|
|
25
|
+
super().__init__(app_name, app_module)
|
|
26
|
+
self.settings = get_settings()
|
|
27
|
+
# Create a container
|
|
28
|
+
container_factory_path = self.settings["CONTAINER_FACTORY"]
|
|
29
|
+
if container_factory_path:
|
|
30
|
+
try:
|
|
31
|
+
container_factory = cast(
|
|
32
|
+
Callable[[], anydi.Container], import_string(container_factory_path)
|
|
33
|
+
)
|
|
34
|
+
except ImportError as exc:
|
|
35
|
+
raise ImproperlyConfigured(
|
|
36
|
+
f"Cannot import container factory '{container_factory_path}'."
|
|
37
|
+
) from exc
|
|
38
|
+
self.container = container_factory()
|
|
39
|
+
else:
|
|
40
|
+
self.container = anydi.Container(
|
|
41
|
+
strict=self.settings["STRICT_MODE"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def ready(self) -> None: # noqa: C901
|
|
45
|
+
# Register Django settings
|
|
46
|
+
if self.settings["REGISTER_SETTINGS"]:
|
|
47
|
+
register_settings(
|
|
48
|
+
self.container,
|
|
49
|
+
prefix=getattr(
|
|
50
|
+
settings,
|
|
51
|
+
"ANYDI_SETTINGS_PREFIX",
|
|
52
|
+
"django.conf.settings.",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Register Django components
|
|
57
|
+
if self.settings["REGISTER_COMPONENTS"]:
|
|
58
|
+
register_components(self.container)
|
|
59
|
+
|
|
60
|
+
# Register modules
|
|
61
|
+
for module_path in self.settings["MODULES"]:
|
|
62
|
+
try:
|
|
63
|
+
module_cls = import_string(module_path)
|
|
64
|
+
except ImportError as exc:
|
|
65
|
+
raise ImproperlyConfigured(
|
|
66
|
+
f"Cannot import module '{module_path}'."
|
|
67
|
+
) from exc
|
|
68
|
+
self.container.register_module(module_cls)
|
|
69
|
+
|
|
70
|
+
# Patching the django-ninja framework if it installed
|
|
71
|
+
if self.settings["PATCH_NINJA"]:
|
|
72
|
+
from .ninja import patch_ninja
|
|
73
|
+
|
|
74
|
+
patch_ninja()
|
|
75
|
+
|
|
76
|
+
# Auto-injecting the container into views
|
|
77
|
+
if urlconf := self.settings["INJECT_URLCONF"]:
|
|
78
|
+
inject_urlpatterns(self.container, urlconf=urlconf)
|
|
79
|
+
|
|
80
|
+
# Scan packages
|
|
81
|
+
for scan_package in self.settings["SCAN_PACKAGES"]:
|
|
82
|
+
self.container.scan(scan_package)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from asgiref.sync import iscoroutinefunction
|
|
4
|
+
from django.http import HttpRequest, HttpResponse
|
|
5
|
+
from django.utils.decorators import sync_and_async_middleware
|
|
6
|
+
|
|
7
|
+
from ._container import container
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@sync_and_async_middleware # type: ignore[misc]
|
|
11
|
+
def request_scoped_middleware(
|
|
12
|
+
get_response: Callable[[HttpRequest], HttpResponse],
|
|
13
|
+
) -> Callable[[HttpRequest], HttpResponse]:
|
|
14
|
+
if iscoroutinefunction(get_response):
|
|
15
|
+
|
|
16
|
+
async def async_middleware(request: HttpRequest) -> HttpResponse:
|
|
17
|
+
async with container.arequest_context():
|
|
18
|
+
return await get_response(request)
|
|
19
|
+
|
|
20
|
+
return async_middleware
|
|
21
|
+
|
|
22
|
+
def middleware(request: HttpRequest) -> HttpResponse:
|
|
23
|
+
with container.request_context():
|
|
24
|
+
return get_response(request)
|
|
25
|
+
|
|
26
|
+
return middleware
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from ninja import operation
|
|
3
|
+
except ImportError as exc: # pragma: no cover
|
|
4
|
+
raise ImportError(
|
|
5
|
+
"'django-ninja' is not installed. "
|
|
6
|
+
"Please install it using 'pip install django-ninja'."
|
|
7
|
+
) from exc
|
|
8
|
+
|
|
9
|
+
from ._operation import AsyncOperation, Operation
|
|
10
|
+
from ._signature import ViewSignature
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def patch_ninja() -> None:
|
|
14
|
+
operation.ViewSignature = ViewSignature # type: ignore[attr-defined]
|
|
15
|
+
operation.Operation = Operation # type: ignore[misc]
|
|
16
|
+
operation.AsyncOperation = AsyncOperation # type: ignore[misc]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from django.http import HttpRequest, HttpResponseBase
|
|
6
|
+
from ninja.operation import (
|
|
7
|
+
AsyncOperation as BaseAsyncOperation, # noqa
|
|
8
|
+
Operation as BaseOperation,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from anydi.ext.django import container
|
|
12
|
+
|
|
13
|
+
from ._signature import ViewSignature
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _update_exc_args(exc: Exception) -> None:
|
|
17
|
+
if isinstance(exc, TypeError) and "required positional argument" in str(exc):
|
|
18
|
+
msg = "Did you fail to use functools.wraps() in a decorator?"
|
|
19
|
+
msg = f"{exc.args[0]}: {msg}" if exc.args else msg
|
|
20
|
+
exc.args = (msg,) + exc.args[1:]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Operation(BaseOperation):
|
|
24
|
+
signature: ViewSignature
|
|
25
|
+
|
|
26
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
self.dependencies = self.signature.dependencies
|
|
29
|
+
|
|
30
|
+
def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase:
|
|
31
|
+
error = self._run_checks(request)
|
|
32
|
+
if error:
|
|
33
|
+
return error
|
|
34
|
+
try:
|
|
35
|
+
temporal_response = self.api.create_temporal_response(request)
|
|
36
|
+
values = self._get_values(request, kw, temporal_response)
|
|
37
|
+
values.update(self._get_dependencies())
|
|
38
|
+
result = self.view_func(request, **values)
|
|
39
|
+
return self._result_to_response(request, result, temporal_response)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
_update_exc_args(e)
|
|
42
|
+
return self.api.on_exception(request, e)
|
|
43
|
+
|
|
44
|
+
def _get_dependencies(self) -> dict[str, Any]:
|
|
45
|
+
return {
|
|
46
|
+
name: container.resolve(interface) for name, interface in self.dependencies
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AsyncOperation(BaseAsyncOperation):
|
|
51
|
+
signature: ViewSignature
|
|
52
|
+
|
|
53
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
54
|
+
super().__init__(*args, **kwargs)
|
|
55
|
+
self.dependencies = self.signature.dependencies
|
|
56
|
+
|
|
57
|
+
async def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase: # type: ignore
|
|
58
|
+
error = await self._run_checks(request)
|
|
59
|
+
if error:
|
|
60
|
+
return error
|
|
61
|
+
try:
|
|
62
|
+
temporal_response = self.api.create_temporal_response(request)
|
|
63
|
+
values = self._get_values(request, kw, temporal_response)
|
|
64
|
+
values.update(await self._get_dependencies())
|
|
65
|
+
result = await self.view_func(request, **values)
|
|
66
|
+
return self._result_to_response(request, result, temporal_response)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
_update_exc_args(e)
|
|
69
|
+
return self.api.on_exception(request, e)
|
|
70
|
+
|
|
71
|
+
async def _get_dependencies(self) -> dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
name: await container.aresolve(interface)
|
|
74
|
+
for name, interface in self.dependencies
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from django.http import HttpResponse
|
|
8
|
+
from ninja.signature.details import (
|
|
9
|
+
FuncParam, # noqa
|
|
10
|
+
ViewSignature as BaseViewSignature,
|
|
11
|
+
)
|
|
12
|
+
from ninja.signature.utils import get_path_param_names, get_typed_signature
|
|
13
|
+
|
|
14
|
+
from anydi._types import Marker # noqa
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ViewSignature(BaseViewSignature):
|
|
18
|
+
def __init__(self, path: str, view_func: Callable[..., Any]) -> None:
|
|
19
|
+
self.view_func = view_func
|
|
20
|
+
self.signature = get_typed_signature(self.view_func)
|
|
21
|
+
self.path = path
|
|
22
|
+
self.path_params_names = get_path_param_names(path)
|
|
23
|
+
self.docstring = inspect.cleandoc(view_func.__doc__ or "")
|
|
24
|
+
self.has_kwargs = False
|
|
25
|
+
self.dependencies = []
|
|
26
|
+
|
|
27
|
+
self.params = []
|
|
28
|
+
for name, arg in self.signature.parameters.items():
|
|
29
|
+
if name == "request":
|
|
30
|
+
# TODO: maybe better assert that 1st param is request or check by type?
|
|
31
|
+
# maybe even have attribute like `has_request`
|
|
32
|
+
# so that users can ignore passing request if not needed
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
if arg.kind == arg.VAR_KEYWORD:
|
|
36
|
+
# Skipping **kwargs
|
|
37
|
+
self.has_kwargs = True
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if arg.kind == arg.VAR_POSITIONAL:
|
|
41
|
+
# Skipping *args
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if arg.annotation is HttpResponse:
|
|
45
|
+
self.response_arg = name
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Skip default values that are anydi dependency markers
|
|
49
|
+
if isinstance(arg.default, Marker):
|
|
50
|
+
self.dependencies.append((name, arg.annotation))
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
func_param = self._get_param_type(name, arg)
|
|
54
|
+
self.params.append(func_param)
|
|
55
|
+
|
|
56
|
+
if hasattr(view_func, "_ninja_contribute_args"):
|
|
57
|
+
for p_name, p_type, p_source in view_func._ninja_contribute_args: # noqa
|
|
58
|
+
self.params.append(
|
|
59
|
+
FuncParam(p_name, p_source.alias or p_name, p_source, p_type, False)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self.models = self._create_models()
|
|
63
|
+
|
|
64
|
+
self._validate_view_path_params()
|
|
@@ -13,7 +13,7 @@ from starlette.requests import Request
|
|
|
13
13
|
from typing_extensions import Annotated, get_args, get_origin
|
|
14
14
|
|
|
15
15
|
from anydi import Container
|
|
16
|
-
from anydi._utils import get_full_qualname,
|
|
16
|
+
from anydi._utils import get_full_qualname, get_typed_parameters
|
|
17
17
|
|
|
18
18
|
from .starlette.middleware import RequestScopedMiddleware
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@ def install(app: FastAPI, container: Container) -> None:
|
|
|
47
47
|
call, *params = dependant.cache_key
|
|
48
48
|
if not call:
|
|
49
49
|
continue # pragma: no cover
|
|
50
|
-
for parameter in
|
|
50
|
+
for parameter in get_typed_parameters(call):
|
|
51
51
|
_patch_route_parameter(call, parameter, container)
|
|
52
52
|
|
|
53
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.25.0"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -50,8 +50,11 @@ mypy = "^1.10.0"
|
|
|
50
50
|
ruff = "^0.4.3"
|
|
51
51
|
pytest = "^8.1.0"
|
|
52
52
|
pytest-cov = "^5.0.0"
|
|
53
|
-
fastapi = "^0.
|
|
53
|
+
fastapi = "^0.100.0"
|
|
54
54
|
httpx = "^0.26.0"
|
|
55
|
+
django = "^4.2"
|
|
56
|
+
django-ninja = "^1.1.0"
|
|
57
|
+
pytest-django = "^4.8.0"
|
|
55
58
|
|
|
56
59
|
[tool.poetry.plugins.pytest11]
|
|
57
60
|
anydi = "anydi.ext.pytest_plugin"
|
|
@@ -74,6 +77,12 @@ convention = "google"
|
|
|
74
77
|
python_version = "3.10"
|
|
75
78
|
strict = true
|
|
76
79
|
|
|
80
|
+
[[tool.mypy.overrides]]
|
|
81
|
+
module = [
|
|
82
|
+
"django.*",
|
|
83
|
+
]
|
|
84
|
+
ignore_missing_imports = true
|
|
85
|
+
|
|
77
86
|
[tool.pytest.ini_options]
|
|
78
87
|
addopts = [
|
|
79
88
|
"--strict-config",
|
|
@@ -81,6 +90,7 @@ addopts = [
|
|
|
81
90
|
]
|
|
82
91
|
xfail_strict = true
|
|
83
92
|
junit_family = "xunit2"
|
|
93
|
+
DJANGO_SETTINGS_MODULE = "tests.ext.django.settings"
|
|
84
94
|
|
|
85
95
|
[tool.coverage.report]
|
|
86
96
|
exclude_also = [
|
|
@@ -97,6 +107,12 @@ exclude_also = [
|
|
|
97
107
|
"if not anyio:",
|
|
98
108
|
]
|
|
99
109
|
|
|
110
|
+
[tool.coverage.run]
|
|
111
|
+
omit = [
|
|
112
|
+
"anydi/ext/django/ninja/_operation.py",
|
|
113
|
+
"anydi/ext/django/ninja/_signature.py",
|
|
114
|
+
]
|
|
115
|
+
|
|
100
116
|
[build-system]
|
|
101
117
|
requires = ["poetry-core>=1.0.0"]
|
|
102
118
|
build-backend = "poetry.core.masonry.api"
|
anydi-0.24.2/anydi/_utils.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"""Shared AnyDI utils module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import builtins
|
|
6
|
-
import functools
|
|
7
|
-
import inspect
|
|
8
|
-
import sys
|
|
9
|
-
from typing import Any, Callable, TypeVar
|
|
10
|
-
|
|
11
|
-
from typing_extensions import Annotated, ParamSpec, get_origin
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
import anyio # noqa
|
|
15
|
-
except ImportError:
|
|
16
|
-
anyio = None # type: ignore[assignment]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
T = TypeVar("T")
|
|
20
|
-
P = ParamSpec("P")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def get_full_qualname(obj: Any) -> str:
|
|
24
|
-
"""Get the fully qualified name of an object.
|
|
25
|
-
|
|
26
|
-
This function returns the fully qualified name of the given object,
|
|
27
|
-
which includes both the module name and the object's qualname.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
obj: The object for which to retrieve the fully qualified name.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
The fully qualified name of the object.
|
|
34
|
-
"""
|
|
35
|
-
origin = get_origin(obj)
|
|
36
|
-
if origin is Annotated:
|
|
37
|
-
metadata = ", ".join(
|
|
38
|
-
[
|
|
39
|
-
f'"{arg}"' if isinstance(arg, str) else str(arg)
|
|
40
|
-
for arg in obj.__metadata__
|
|
41
|
-
]
|
|
42
|
-
)
|
|
43
|
-
return f"Annotated[{get_full_qualname(obj.__args__[0])}, {metadata}]]"
|
|
44
|
-
|
|
45
|
-
qualname = getattr(obj, "__qualname__", None)
|
|
46
|
-
module_name = getattr(obj, "__module__", None)
|
|
47
|
-
if qualname is None:
|
|
48
|
-
qualname = type(obj).__qualname__
|
|
49
|
-
|
|
50
|
-
if module_name is None:
|
|
51
|
-
module_name = type(obj).__module__
|
|
52
|
-
|
|
53
|
-
if module_name == builtins.__name__:
|
|
54
|
-
return qualname
|
|
55
|
-
return f"{module_name}.{qualname}"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def is_builtin_type(tp: type[Any]) -> bool:
|
|
59
|
-
"""
|
|
60
|
-
Check if the given type is a built-in type.
|
|
61
|
-
Args:
|
|
62
|
-
tp (type): The type to check.
|
|
63
|
-
Returns:
|
|
64
|
-
bool: True if the type is a built-in type, False otherwise.
|
|
65
|
-
"""
|
|
66
|
-
return tp.__module__ == builtins.__name__
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@functools.lru_cache(maxsize=None)
|
|
70
|
-
def get_signature(obj: Callable[..., Any]) -> inspect.Signature:
|
|
71
|
-
"""Get the signature of a callable object.
|
|
72
|
-
|
|
73
|
-
This function uses the `inspect.signature` function to retrieve the signature
|
|
74
|
-
of the given callable object. It applies an LRU cache decorator to improve
|
|
75
|
-
performance by caching the signatures of previously inspected objects.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
obj: The callable object to inspect.
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
The signature of the callable object.
|
|
82
|
-
"""
|
|
83
|
-
signature_kwargs: dict[str, Any] = {}
|
|
84
|
-
if sys.version_info >= (3, 10):
|
|
85
|
-
signature_kwargs["eval_str"] = True
|
|
86
|
-
return inspect.signature(obj, **signature_kwargs)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
async def run_async(
|
|
90
|
-
func: Callable[P, T],
|
|
91
|
-
/,
|
|
92
|
-
*args: P.args,
|
|
93
|
-
**kwargs: P.kwargs,
|
|
94
|
-
) -> T:
|
|
95
|
-
"""Runs the given function asynchronously using the `anyio` library.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
func: The function to run asynchronously.
|
|
99
|
-
args: The positional arguments to pass to the function.
|
|
100
|
-
kwargs: The keyword arguments to pass to the function.
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
The result of the function.
|
|
104
|
-
|
|
105
|
-
Raises:
|
|
106
|
-
ImportError: If the `anyio` library is not installed.
|
|
107
|
-
"""
|
|
108
|
-
if not anyio:
|
|
109
|
-
raise ImportError(
|
|
110
|
-
"`anyio` library is not currently installed. Please make sure to install "
|
|
111
|
-
"it first, or consider using `anydi[full]` instead."
|
|
112
|
-
)
|
|
113
|
-
return await anyio.to_thread.run_sync(functools.partial(func, *args, **kwargs))
|
|
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
|