anydi 0.27.1__py3-none-any.whl → 0.29.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/_context.py CHANGED
@@ -195,6 +195,14 @@ class ResourceScopedContext(ScopedContext):
195
195
  """
196
196
  return interface in self._instances
197
197
 
198
+ def _create_instance(self, provider: Provider) -> Any:
199
+ """Create an instance using the provider."""
200
+ instance = super()._create_instance(provider)
201
+ # Enter the context manager if the instance is closable.
202
+ if hasattr(instance, "__enter__") and hasattr(instance, "__exit__"):
203
+ self._stack.enter_context(instance)
204
+ return instance
205
+
198
206
  def _create_resource(self, provider: Provider) -> Any:
199
207
  """Create a resource using the provider.
200
208
 
@@ -208,6 +216,14 @@ class ResourceScopedContext(ScopedContext):
208
216
  cm = contextlib.contextmanager(provider.obj)(*args, **kwargs)
209
217
  return self._stack.enter_context(cm)
210
218
 
219
+ async def _acreate_instance(self, provider: Provider) -> Any:
220
+ """Create an instance asynchronously using the provider."""
221
+ instance = await super()._acreate_instance(provider)
222
+ # Enter the context manager if the instance is closable.
223
+ if hasattr(instance, "__aenter__") and hasattr(instance, "__aexit__"):
224
+ await self._async_stack.enter_async_context(instance)
225
+ return instance
226
+
211
227
  async def _acreate_resource(self, provider: Provider) -> Any:
212
228
  """Create a resource asynchronously using the provider.
213
229
 
@@ -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 anydi
14
+ from anydi import Container
15
15
 
16
16
 
17
17
  def register_settings(
18
- container: anydi.Container, prefix: str = "django.conf.setting."
18
+ container: Container, prefix: str = "django.conf.setting."
19
19
  ) -> None:
20
20
  """Register Django settings into the container."""
21
21
 
22
- def _get_setting_value(value: Any) -> Any:
23
- return lambda: value
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
- 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
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: anydi.Container, *, urlconf: str) -> None:
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
  Metadata-Version: 2.1
2
2
  Name: anydi
3
- Version: 0.27.1
3
+ Version: 0.29.0
4
4
  Summary: Dependency Injection library
5
5
  Home-page: https://github.com/antonrh/anydi
6
6
  License: MIT
@@ -32,7 +32,7 @@ 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.5.21,<10.0.0) ; extra == "docs"
35
+ Requires-Dist: mkdocs-material (>=9.5.29,<10.0.0) ; extra == "docs"
36
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
@@ -1,6 +1,6 @@
1
1
  anydi/__init__.py,sha256=aeaBp5vq09sG-e9sqqs9qpUtUIDNfOdFPrlAfE5Ku9E,584
2
2
  anydi/_container.py,sha256=-vpiYl2Tx2QK9wl74DiF5qLFRudJ3e78TqEyuyOrthE,29055
3
- anydi/_context.py,sha256=_hJ1Cf2amgAr4uLvgk2KtGWMo0p7JFM_db1_YbWweHI,11895
3
+ anydi/_context.py,sha256=Wm4DT8Ie_TPchWmIBe8Q9f90dQrGd5lY8H5K85rStgY,12706
4
4
  anydi/_logger.py,sha256=UpubJUnW83kffFxkhUlObm2DmZX1Pjqoz9YFKS-JOPg,52
5
5
  anydi/_module.py,sha256=E1TfLud_Af-MPB83PxIzHVA1jlDW2FGaRP_il1a6y3Y,3675
6
6
  anydi/_scanner.py,sha256=cyEk-K2Q8ssZStq8GrxMeEcCuAZMw-RXrjlgWEevKCs,6667
@@ -11,7 +11,7 @@ anydi/ext/_utils.py,sha256=2kxLPTMM9Ro3s6-knbqYzONlqRB3hMcwZFFRQGHcFUg,2691
11
11
  anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
12
12
  anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
13
13
  anydi/ext/django/_settings.py,sha256=cKzFBGtPCsexZ2ZcInubBukIebhxzNfa3F0KuwoZYaA,844
14
- anydi/ext/django/_utils.py,sha256=76_T-gxP67qddlRyevAF2oe-FTv9NBJSBgOWxxs0qZQ,3673
14
+ anydi/ext/django/_utils.py,sha256=E0GGxsEJiSJsA4-8AcEwfdUi_iK7qGpKfGNRDKmDqEg,3976
15
15
  anydi/ext/django/apps.py,sha256=-gj_tqb6goeYMNItr6nwWHYXZwDOdiH8anby0YwnUmw,2866
16
16
  anydi/ext/django/middleware.py,sha256=iVHWtE829khMY-BXbNNt0g2FrIApKprna7dCG9ObEis,823
17
17
  anydi/ext/django/ninja/__init__.py,sha256=kW3grUgWp_nkWSG_-39ADHMrZLGNcj9TsJ9OW8iWWrk,546
@@ -19,12 +19,13 @@ anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI
19
19
  anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfyk_NVEWw,2207
20
20
  anydi/ext/fastapi.py,sha256=vhfSyovXuCjvSkx6AiLOTNU975i8wDg72C5fqXQiFLw,2896
21
21
  anydi/ext/faststream.py,sha256=L4rkWYIO4ZZuWH-8M8NT6_J0bT0Dz_EWO3B6Oj1iFBI,2024
22
+ anydi/ext/pydantic_settings.py,sha256=UwJWByusXLKvlIp5gBHslgUom2bK93QZwwFhJgzY1gg,1490
22
23
  anydi/ext/pytest_plugin.py,sha256=3OWphc4nEzla46_8KR7LXtwGns5eol_YlUWfTf4Cr2Q,3952
23
24
  anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
25
  anydi/ext/starlette/middleware.py,sha256=Ni0BQaPjs_Ha6zcLZYYJ3-XkslTCnL9aCSa06rnRDMI,1139
25
26
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- anydi-0.27.1.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
27
- anydi-0.27.1.dist-info/METADATA,sha256=YJpEkYC-TShdr7ntwZOW1JTbk1NJxJvz5xgXCWtROWw,5111
28
- anydi-0.27.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- anydi-0.27.1.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
30
- anydi-0.27.1.dist-info/RECORD,,
27
+ anydi-0.29.0.dist-info/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
+ anydi-0.29.0.dist-info/METADATA,sha256=HgsJLUAb-SP361yfEM5mI4Y7Q6MWNiA2sL1m1RIccl4,5111
29
+ anydi-0.29.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
30
+ anydi-0.29.0.dist-info/entry_points.txt,sha256=GmQblwzxFg42zva1HyBYJJ7TvrTIcSAGBHmyi3bvsi4,42
31
+ anydi-0.29.0.dist-info/RECORD,,
File without changes