anydi 0.28.0__tar.gz → 0.29.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.28.0 → anydi-0.29.0}/PKG-INFO +1 -1
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/_utils.py +48 -35
- anydi-0.29.0/anydi/ext/pydantic_settings.py +48 -0
- {anydi-0.28.0 → anydi-0.29.0}/pyproject.toml +4 -3
- {anydi-0.28.0 → anydi-0.29.0}/LICENSE +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/README.md +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/__init__.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_container.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_context.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_logger.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_module.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_scanner.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_types.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/_utils.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/_utils.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/_container.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/_settings.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/apps.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/middleware.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/ninja/__init__.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/ninja/_operation.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/django/ninja/_signature.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/fastapi.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/faststream.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.28.0 → anydi-0.29.0}/anydi/py.typed +0 -0
|
@@ -11,16 +11,17 @@ from django.db.backends.base.base import BaseDatabaseWrapper
|
|
|
11
11
|
from django.urls import URLPattern, URLResolver, get_resolver
|
|
12
12
|
from typing_extensions import Annotated, get_origin
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
from anydi import Container
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def register_settings(
|
|
18
|
-
container:
|
|
18
|
+
container: Container, prefix: str = "django.conf.setting."
|
|
19
19
|
) -> None:
|
|
20
20
|
"""Register Django settings into the container."""
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
# Ensure prefix ends with a dot
|
|
23
|
+
if prefix[-1] != ".":
|
|
24
|
+
prefix += "."
|
|
24
25
|
|
|
25
26
|
for setting_name in dir(settings):
|
|
26
27
|
setting_value = getattr(settings, setting_name)
|
|
@@ -33,38 +34,11 @@ def register_settings(
|
|
|
33
34
|
scope="singleton",
|
|
34
35
|
)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
37
|
+
# Patch AnyDI to resolve Any types for annotated settings
|
|
38
|
+
_patch_any_typed_annotated(container, prefix=prefix)
|
|
49
39
|
|
|
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
40
|
|
|
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:
|
|
41
|
+
def register_components(container: Container) -> None:
|
|
68
42
|
"""Register Django components into the container."""
|
|
69
43
|
|
|
70
44
|
# Register caches
|
|
@@ -90,7 +64,7 @@ def register_components(container: anydi.Container) -> None:
|
|
|
90
64
|
)
|
|
91
65
|
|
|
92
66
|
|
|
93
|
-
def inject_urlpatterns(container:
|
|
67
|
+
def inject_urlpatterns(container: Container, *, urlconf: str) -> None:
|
|
94
68
|
"""Auto-inject the container into views."""
|
|
95
69
|
resolver = get_resolver(urlconf)
|
|
96
70
|
for pattern in iter_urlpatterns(resolver.url_patterns):
|
|
@@ -113,3 +87,42 @@ def iter_urlpatterns(
|
|
|
113
87
|
yield from iter_urlpatterns(url_pattern.url_patterns)
|
|
114
88
|
else:
|
|
115
89
|
yield url_pattern
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _get_setting_value(value: Any) -> Any:
|
|
93
|
+
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[method-assign]
|
|
124
|
+
container.resolve
|
|
125
|
+
)
|
|
126
|
+
container.aresolve = _patch_aresolve( # type: ignore[method-assign]
|
|
127
|
+
container.aresolve
|
|
128
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Iterable
|
|
4
|
+
|
|
5
|
+
from pydantic.fields import ComputedFieldInfo, FieldInfo # noqa
|
|
6
|
+
from pydantic_settings import BaseSettings
|
|
7
|
+
from typing_extensions import Annotated
|
|
8
|
+
|
|
9
|
+
from anydi import Container
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def install(
|
|
13
|
+
settings: BaseSettings | Iterable[BaseSettings],
|
|
14
|
+
container: Container,
|
|
15
|
+
*,
|
|
16
|
+
prefix: str = ".settings",
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Install Pydantic settings into an AnyDI container."""
|
|
19
|
+
|
|
20
|
+
# Ensure prefix ends with a dot
|
|
21
|
+
if prefix[-1] != ".":
|
|
22
|
+
prefix += "."
|
|
23
|
+
|
|
24
|
+
def _register_settings(_settings: BaseSettings) -> None:
|
|
25
|
+
all_fields = {**_settings.model_fields, **_settings.model_computed_fields}
|
|
26
|
+
for setting_name, field_info in all_fields.items():
|
|
27
|
+
if isinstance(field_info, ComputedFieldInfo):
|
|
28
|
+
interface = field_info.return_type
|
|
29
|
+
elif isinstance(field_info, FieldInfo):
|
|
30
|
+
interface = field_info.annotation
|
|
31
|
+
else:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
container.register(
|
|
35
|
+
Annotated[interface, f"{prefix}{setting_name}"],
|
|
36
|
+
_get_setting_value(getattr(_settings, setting_name)),
|
|
37
|
+
scope="singleton",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if isinstance(settings, BaseSettings):
|
|
41
|
+
_register_settings(settings)
|
|
42
|
+
else:
|
|
43
|
+
for _settings in settings:
|
|
44
|
+
_register_settings(_settings)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_setting_value(setting_value: Any) -> Callable[[], Any]:
|
|
48
|
+
return lambda: setting_value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.29.0"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -45,8 +45,8 @@ docs = ["mkdocs", "mkdocs-material"]
|
|
|
45
45
|
async = ["anyio"]
|
|
46
46
|
|
|
47
47
|
[tool.poetry.group.dev.dependencies]
|
|
48
|
-
mypy = "^1.11.
|
|
49
|
-
ruff = "^0.6.
|
|
48
|
+
mypy = "^1.11.2"
|
|
49
|
+
ruff = "^0.6.2"
|
|
50
50
|
pytest = "^8.3.1"
|
|
51
51
|
pytest-cov = "^5.0.0"
|
|
52
52
|
fastapi = "^0.100.0"
|
|
@@ -56,6 +56,7 @@ django-ninja = "^1.1.0"
|
|
|
56
56
|
pytest-django = "^4.8.0"
|
|
57
57
|
faststream = "^0.5.10"
|
|
58
58
|
redis = "^5.0.4"
|
|
59
|
+
pydantic-settings = "^2.4.0"
|
|
59
60
|
|
|
60
61
|
[tool.poetry.plugins.pytest11]
|
|
61
62
|
anydi = "anydi.ext.pytest_plugin"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|