modern-di 0.11.0__py3-none-any.whl → 0.12.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.

Potentially problematic release.


This version of modern-di might be problematic. Click here for more details.

File without changes
@@ -0,0 +1,7 @@
1
+ import typing
2
+ from operator import attrgetter
3
+
4
+
5
+ def get_value_from_object_by_dotted_path(obj: typing.Any, path: str) -> typing.Any: # noqa: ANN401
6
+ attribute_getter = attrgetter(path)
7
+ return attribute_getter(obj)
@@ -4,7 +4,10 @@ import itertools
4
4
  import typing
5
5
  import uuid
6
6
 
7
+ from typing_extensions import override
8
+
7
9
  from modern_di import Container
10
+ from modern_di.helpers.attr_getter_helpers import get_value_from_object_by_dotted_path
8
11
 
9
12
 
10
13
  T_co = typing.TypeVar("T_co", covariant=True)
@@ -36,6 +39,22 @@ class AbstractProvider(typing.Generic[T_co], abc.ABC):
36
39
  msg = "Scope of dependency cannot be more than scope of dependent"
37
40
  raise RuntimeError(msg)
38
41
 
42
+ def __getattr__(self, attr_name: str) -> typing.Any: # noqa: ANN401
43
+ """Get an attribute from the resolve object.
44
+
45
+ Args:
46
+ attr_name: name of attribute to get.
47
+
48
+ Returns:
49
+ An `AttrGetter` provider that will get the attribute after resolving the current provider.
50
+
51
+ """
52
+ if attr_name.startswith("_"):
53
+ msg = f"'{type(self)}' object has no attribute '{attr_name}'"
54
+ raise AttributeError(msg)
55
+
56
+ return AttrGetter(provider=self, attr_name=attr_name)
57
+
39
58
 
40
59
  class AbstractOverrideProvider(AbstractProvider[T_co], abc.ABC):
41
60
  def override(self, override_object: object, container: Container) -> None:
@@ -69,8 +88,8 @@ class AbstractCreatorProvider(AbstractOverrideProvider[T_co], abc.ABC):
69
88
 
70
89
  def _sync_build_creator(self, container: Container) -> typing.Any: # noqa: ANN401
71
90
  return self._creator(
72
- *typing.cast(P.args, self._sync_resolve_args(container)),
73
- **typing.cast(P.kwargs, self._sync_resolve_kwargs(container)),
91
+ *self._sync_resolve_args(container),
92
+ **self._sync_resolve_kwargs(container),
74
93
  )
75
94
 
76
95
  async def _async_resolve_args(self, container: Container) -> list[typing.Any]:
@@ -84,6 +103,43 @@ class AbstractCreatorProvider(AbstractOverrideProvider[T_co], abc.ABC):
84
103
 
85
104
  async def _async_build_creator(self, container: Container) -> typing.Any: # noqa: ANN401
86
105
  return self._creator(
87
- *typing.cast(P.args, await self._async_resolve_args(container)),
88
- **typing.cast(P.kwargs, await self._async_resolve_kwargs(container)),
106
+ *await self._async_resolve_args(container),
107
+ **await self._async_resolve_kwargs(container),
89
108
  )
109
+
110
+
111
+ class AttrGetter(AbstractProvider[T_co]):
112
+ """Provides an attribute after resolving the wrapped provider."""
113
+
114
+ __slots__ = [*AbstractProvider.BASE_SLOTS, "_attrs", "_provider"]
115
+
116
+ def __init__(self, provider: AbstractProvider[T_co], attr_name: str) -> None:
117
+ """Create a new AttrGetter instance.
118
+
119
+ Args:
120
+ provider: provider to wrap.
121
+ attr_name: attribute name to resolve when the provider is resolved.
122
+
123
+ """
124
+ super().__init__(scope=provider.scope)
125
+ self._provider = provider
126
+ self._attrs = [attr_name]
127
+
128
+ def __getattr__(self, attr: str) -> "AttrGetter[T_co]":
129
+ if attr.startswith("_"):
130
+ msg = f"'{type(self)}' object has no attribute '{attr}'"
131
+ raise AttributeError(msg)
132
+ self._attrs.append(attr)
133
+ return self
134
+
135
+ @override
136
+ async def async_resolve(self, container: Container) -> typing.Any:
137
+ resolved_provider_object = await self._provider.async_resolve(container)
138
+ attribute_path = ".".join(self._attrs)
139
+ return get_value_from_object_by_dotted_path(resolved_provider_object, attribute_path)
140
+
141
+ @override
142
+ def sync_resolve(self, container: Container) -> typing.Any:
143
+ resolved_provider_object = self._provider.sync_resolve(container)
144
+ attribute_path = ".".join(self._attrs)
145
+ return get_value_from_object_by_dotted_path(resolved_provider_object, attribute_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-di
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: Dependency Injection framework with IOC-container and scopes
5
5
  Project-URL: repository, https://github.com/modern-python/modern-di
6
6
  Project-URL: docs, https://modern-di.readthedocs.io
@@ -39,103 +39,3 @@ It is in development state yet and gives you the following:
39
39
  - Thread-safe and asyncio concurrency safe providers
40
40
 
41
41
  📚 [Documentation](https://modern-di.readthedocs.io)
42
-
43
- # Quickstart
44
-
45
- ## 1. Install `modern-di` using your favorite tool:
46
-
47
- ```shell
48
- pip install modern-di
49
- uv add modern-di
50
- poetry add modern-di
51
- ```
52
-
53
- ## 2. Describe resources and classes:
54
- ```python
55
- import dataclasses
56
- import logging
57
- import typing
58
-
59
-
60
- logger = logging.getLogger(__name__)
61
-
62
-
63
- # singleton provider with finalization
64
- def create_sync_resource() -> typing.Iterator[str]:
65
- logger.debug("Resource initiated")
66
- try:
67
- yield "sync resource"
68
- finally:
69
- logger.debug("Resource destructed")
70
-
71
-
72
- # same, but async
73
- async def create_async_resource() -> typing.AsyncIterator[str]:
74
- logger.debug("Async resource initiated")
75
- try:
76
- yield "async resource"
77
- finally:
78
- logger.debug("Async resource destructed")
79
-
80
-
81
- @dataclasses.dataclass(kw_only=True, slots=True)
82
- class DependentFactory:
83
- sync_resource: str
84
- async_resource: str
85
- ```
86
-
87
- ## 3. Describe dependencies graph
88
- ```python
89
- from modern_di import BaseGraph, Scope, providers
90
-
91
-
92
- class Dependencies(BaseGraph):
93
- sync_resource = providers.Resource(Scope.APP, create_sync_resource)
94
- async_resource = providers.Resource(Scope.APP, create_async_resource)
95
-
96
- simple_factory = providers.Factory(Scope.REQUEST, SimpleFactory, dep1="text", dep2=123)
97
- dependent_factory = providers.Factory(
98
- Scope.REQUEST,
99
- sync_resource=sync_resource,
100
- async_resource=async_resource,
101
- )
102
- ```
103
-
104
- ## 4.1. Integrate with your framework
105
-
106
- For now there are integration for the following frameworks:
107
- 1. [FastAPI](https://modern-di.readthedocs.io/en/latest/integrations/fastapi.html)
108
- 2. [LiteStar](https://modern-di.readthedocs.io/en/latest/integrations/litestar.html)
109
-
110
- ## 4.2. Or use `modern-di` without integrations
111
-
112
- Create container and resolve dependencies in your code
113
- ```python
114
- from modern_di import Container, Scope
115
-
116
-
117
- # init container of app scope in sync mode
118
- with Container(scope=Scope.APP) as app_container:
119
- # resolve sync resource
120
- Dependencies.sync_resource.sync_resolve(app_container)
121
-
122
-
123
- # init container of app scope in async mode
124
- async with Container(scope=Scope.APP) as app_container:
125
- # resolve async resource
126
- await Dependencies.async_resource.async_resolve(app_container)
127
-
128
- # resolve sync resource
129
- instance1 = await Dependencies.sync_resource.async_resolve(app_container)
130
- instance2 = Dependencies.sync_resource.sync_resolve(app_container)
131
- assert instance1 is instance2
132
-
133
- # create container of request scope
134
- async with app_container.build_child_container(scope=Scope.REQUEST) as request_container:
135
- # resolve factories of request scope
136
- Dependencies.simple_factory.sync_resolve(request_container)
137
- await Dependencies.dependent_factory.async_resolve(request_container)
138
-
139
- # resources of app-scope also can be resolved here
140
-
141
- ```
@@ -4,8 +4,10 @@ modern_di/graph.py,sha256=X60wtG3Mqus_5YZNiZlQuXoHODBp7rYl_IHJs7GzSQM,1356
4
4
  modern_di/provider_state.py,sha256=oU08QnMr0yhIZKkz0Pee8_RnWtETDE9ux4JB83qhwTI,1358
5
5
  modern_di/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  modern_di/scope.py,sha256=e6Olc-CF89clbYDNGciy-F8EqJt1Mw2703zfuJaEY94,113
7
+ modern_di/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ modern_di/helpers/attr_getter_helpers.py,sha256=HUjExWLRAz2h0YX2_h5xKPX9-_K3i-BZTeZb3u-Wq5k,221
7
9
  modern_di/providers/__init__.py,sha256=x4dzOpyyY6puc_ogtjch4xAdzz-8fN_dWefTjS1k2aI,709
8
- modern_di/providers/abstract.py,sha256=dgtAwzwtK-zyIHroxAT3QB5CV06sx3PUugYweZexkMc,3408
10
+ modern_di/providers/abstract.py,sha256=TZryLsWRXsOu0aApmY13iHTNHTl1yU7jHs6sPCJrz5k,5417
9
11
  modern_di/providers/container_provider.py,sha256=r5IEQXgKtPwvHvbqkbPnmGyDGGCCjokTtdard9Rvt40,548
10
12
  modern_di/providers/context_adapter.py,sha256=_b1x3ToQPWT-9KkDioFhw1W8Q1VXZYUnczfYzMTobVA,760
11
13
  modern_di/providers/dict.py,sha256=nCU9iaqteYHDbILAfhrdnbMgS9_emE4MS7Xn2VoUlPo,858
@@ -16,6 +18,6 @@ modern_di/providers/object.py,sha256=Sm0mb3Ua7cZ5Ay65fLvl7fnJDSQrQZwvzio3lkkXP2A
16
18
  modern_di/providers/resource.py,sha256=Vp8psY_svOQEJ_38DWhR8ASYpqhP9DoB7bC0jH0IlaU,4605
17
19
  modern_di/providers/selector.py,sha256=RQbHD2-Liw-TGqu6UELbfCzXYuqxiO_Mg1tLyF3mKQo,1419
18
20
  modern_di/providers/singleton.py,sha256=_hUpCmbHgLAigdhBiu0zypwWwrIGdB6_oZkGfuLxzNE,2372
19
- modern_di-0.11.0.dist-info/METADATA,sha256=kal_732xHN0mXUyxkNdv4gDCK_MlQM12xOHiHcClcJs,5950
20
- modern_di-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- modern_di-0.11.0.dist-info/RECORD,,
21
+ modern_di-0.12.0.dist-info/METADATA,sha256=AcoUZqQyNPFSJ3E5ZMTc51wpszLAqPYk7STQoa2s83c,3155
22
+ modern_di-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ modern_di-0.12.0.dist-info/RECORD,,