modern-di 0.11.0__tar.gz → 0.13.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.

Potentially problematic release.


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

Files changed (24) hide show
  1. modern_di-0.13.0/PKG-INFO +43 -0
  2. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/container.py +7 -4
  3. modern_di-0.13.0/modern_di/helpers/attr_getter_helpers.py +7 -0
  4. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/abstract.py +60 -4
  5. modern_di-0.13.0/modern_di/py.typed +0 -0
  6. modern_di-0.11.0/PKG-INFO +0 -141
  7. {modern_di-0.11.0 → modern_di-0.13.0}/.gitignore +0 -0
  8. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/__init__.py +0 -0
  9. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/graph.py +0 -0
  10. /modern_di-0.11.0/modern_di/py.typed → /modern_di-0.13.0/modern_di/helpers/__init__.py +0 -0
  11. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/provider_state.py +0 -0
  12. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/__init__.py +0 -0
  13. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/container_provider.py +0 -0
  14. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/context_adapter.py +0 -0
  15. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/dict.py +0 -0
  16. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/factory.py +0 -0
  17. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/injected_factory.py +0 -0
  18. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/list.py +0 -0
  19. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/object.py +0 -0
  20. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/resource.py +0 -0
  21. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/selector.py +0 -0
  22. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/providers/singleton.py +0 -0
  23. {modern_di-0.11.0 → modern_di-0.13.0}/modern_di/scope.py +0 -0
  24. {modern_di-0.11.0 → modern_di-0.13.0}/pyproject.toml +0 -0
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: modern-di
3
+ Version: 0.13.0
4
+ Summary: Dependency Injection framework with IOC-container and scopes
5
+ Project-URL: repository, https://github.com/modern-python/modern-di
6
+ Project-URL: docs, https://modern-di.readthedocs.io
7
+ Author-email: Artur Shiriev <me@shiriev.ru>
8
+ License-Expression: MIT
9
+ Keywords: DI,dependency injector,ioc-container,mocks,python
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: <4,>=3.10
17
+ Description-Content-Type: text/markdown
18
+
19
+ "Modern-DI"
20
+ ==
21
+
22
+ | Project | Badges |
23
+ |--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
24
+ | common | [![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration) [![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di)](https://github.com/modern-python/modern-di/stargazers) |
25
+ | modern-di | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di.svg)](https://pypi.python.org/pypi/modern-di ) [![downloads](https://img.shields.io/pypi/dm/modern-di.svg)](https://pypistats.org/packages/modern-di) |
26
+ | modern-di-fastapi | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-fastapi.svg)](https://pypi.python.org/pypi/modern-di-fastapi) [![downloads](https://img.shields.io/pypi/dm/modern-di-fastapi.svg)](https://pypistats.org/packages/modern-di-fastapi) |
27
+ | modern-di-litestar | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-litestar.svg)](https://pypi.python.org/pypi/modern-di-litestar) [![downloads](https://img.shields.io/pypi/dm/modern-di-litestar.svg)](https://pypistats.org/packages/modern-di-litestar) |
28
+
29
+ `modern-di` is a python dependency injection framework which, among other things,
30
+ supports the following:
31
+
32
+ - Async and sync dependency resolution
33
+ - Scopes and granular context management
34
+ - Python 3.10+ support
35
+ - Fully typed and tested
36
+ - Compatibility with popular frameworks like `FastAPI` and `LiteStar`
37
+ - Thread-safe and asyncio concurrency safe providers
38
+
39
+ ## 📚 [Documentation](https://modern-di.readthedocs.io)
40
+
41
+ ## 📦 [PyPi](https://pypi.org/project/modern-di)
42
+
43
+ ## 📝 [License](LICENSE)
@@ -123,16 +123,19 @@ class Container(contextlib.AbstractAsyncContextManager["Container"], contextlib.
123
123
  self._is_async = True
124
124
  return self
125
125
 
126
+ async def async_close(self) -> None:
127
+ self._check_entered()
128
+ for provider_state in reversed(self._provider_states.values()):
129
+ await provider_state.async_tear_down()
130
+ self._exit()
131
+
126
132
  async def __aexit__(
127
133
  self,
128
134
  exc_type: type[BaseException] | None,
129
135
  exc_val: BaseException | None,
130
136
  traceback: types.TracebackType | None,
131
137
  ) -> None:
132
- self._check_entered()
133
- for provider_state in reversed(self._provider_states.values()):
134
- await provider_state.async_tear_down()
135
- self._exit()
138
+ await self.async_close()
136
139
 
137
140
  def __enter__(self) -> "Container":
138
141
  self._is_async = False
@@ -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)
File without changes
modern_di-0.11.0/PKG-INFO DELETED
@@ -1,141 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: modern-di
3
- Version: 0.11.0
4
- Summary: Dependency Injection framework with IOC-container and scopes
5
- Project-URL: repository, https://github.com/modern-python/modern-di
6
- Project-URL: docs, https://modern-di.readthedocs.io
7
- Author-email: Artur Shiriev <me@shiriev.ru>
8
- License-Expression: MIT
9
- Keywords: DI,dependency injector,ioc-container,mocks,python
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
14
- Classifier: Topic :: Software Development :: Libraries
15
- Classifier: Typing :: Typed
16
- Requires-Python: <4,>=3.10
17
- Description-Content-Type: text/markdown
18
-
19
- "Modern-DI"
20
- ==
21
-
22
- | Project | Badges |
23
- |--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
24
- | common | [![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration) [![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di)](https://github.com/modern-python/modern-di/stargazers) |
25
- | modern-di | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di.svg)](https://pypi.python.org/pypi/modern-di ) [![downloads](https://img.shields.io/pypi/dm/modern-di.svg)](https://pypistats.org/packages/modern-di) |
26
- | modern-di-fastapi | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-fastapi.svg)](https://pypi.python.org/pypi/modern-di-fastapi) [![downloads](https://img.shields.io/pypi/dm/modern-di-fastapi.svg)](https://pypistats.org/packages/modern-di-fastapi) |
27
- | modern-di-litestar | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-litestar.svg)](https://pypi.python.org/pypi/modern-di-litestar) [![downloads](https://img.shields.io/pypi/dm/modern-di-litestar.svg)](https://pypistats.org/packages/modern-di-litestar) |
28
-
29
- Dependency injection framework for Python inspired by `dependency-injector` and `dishka`.
30
-
31
- It is in development state yet and gives you the following:
32
- - DI framework with IOC-container and scopes.
33
- - Async and sync resolving.
34
- - Python 3.10-3.13 support.
35
- - Full coverage by types annotations (mypy in strict mode).
36
- - Overriding dependencies for tests.
37
- - Package with zero dependencies.
38
- - Integration with FastAPI and LiteStar
39
- - Thread-safe and asyncio concurrency safe providers
40
-
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
- ```
File without changes
File without changes