modern-di 0.10.0__tar.gz → 0.12.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 (23) hide show
  1. {modern_di-0.10.0 → modern_di-0.12.0}/PKG-INFO +1 -83
  2. modern_di-0.12.0/modern_di/helpers/attr_getter_helpers.py +7 -0
  3. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/__init__.py +2 -0
  4. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/abstract.py +60 -4
  5. modern_di-0.12.0/modern_di/providers/object.py +27 -0
  6. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/resource.py +1 -1
  7. modern_di-0.12.0/modern_di/py.typed +0 -0
  8. {modern_di-0.10.0 → modern_di-0.12.0}/.gitignore +0 -0
  9. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/__init__.py +0 -0
  10. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/container.py +0 -0
  11. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/graph.py +0 -0
  12. /modern_di-0.10.0/modern_di/py.typed → /modern_di-0.12.0/modern_di/helpers/__init__.py +0 -0
  13. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/provider_state.py +0 -0
  14. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/container_provider.py +0 -0
  15. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/context_adapter.py +0 -0
  16. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/dict.py +0 -0
  17. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/factory.py +0 -0
  18. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/injected_factory.py +0 -0
  19. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/list.py +0 -0
  20. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/selector.py +0 -0
  21. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/providers/singleton.py +0 -0
  22. {modern_di-0.10.0 → modern_di-0.12.0}/modern_di/scope.py +0 -0
  23. {modern_di-0.10.0 → modern_di-0.12.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-di
3
- Version: 0.10.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,85 +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
- ## Describe resources and classes:
44
- ```python
45
- import dataclasses
46
- import logging
47
- import typing
48
-
49
-
50
- logger = logging.getLogger(__name__)
51
-
52
-
53
- # singleton provider with finalization
54
- def create_sync_resource() -> typing.Iterator[str]:
55
- logger.debug("Resource initiated")
56
- try:
57
- yield "sync resource"
58
- finally:
59
- logger.debug("Resource destructed")
60
-
61
-
62
- # same, but async
63
- async def create_async_resource() -> typing.AsyncIterator[str]:
64
- logger.debug("Async resource initiated")
65
- try:
66
- yield "async resource"
67
- finally:
68
- logger.debug("Async resource destructed")
69
-
70
-
71
- @dataclasses.dataclass(kw_only=True, slots=True)
72
- class DependentFactory:
73
- sync_resource: str
74
- async_resource: str
75
- ```
76
-
77
- ## Describe dependencies graph (IoC-container)
78
- ```python
79
- from modern_di import BaseGraph, Scope, providers
80
-
81
-
82
- class Dependencies(BaseGraph):
83
- sync_resource = providers.Resource(Scope.APP, create_sync_resource)
84
- async_resource = providers.Resource(Scope.APP, create_async_resource)
85
-
86
- simple_factory = providers.Factory(Scope.REQUEST, SimpleFactory, dep1="text", dep2=123)
87
- dependent_factory = providers.Factory(
88
- Scope.REQUEST,
89
- sync_resource=sync_resource,
90
- async_resource=async_resource,
91
- )
92
- ```
93
-
94
- ## Create container and resolve dependencies in your code
95
- ```python
96
- from modern_di import Container, Scope
97
-
98
-
99
- # init container of app scope in sync mode
100
- with Container(scope=Scope.APP) as app_container:
101
- # resolve sync resource
102
- Dependencies.sync_resource.sync_resolve(app_container)
103
-
104
-
105
- # init container of app scope in async mode
106
- async with Container(scope=Scope.APP) as app_container:
107
- # resolve async resource
108
- await Dependencies.async_resource.async_resolve(app_container)
109
-
110
- # resolve sync resource
111
- instance1 = await Dependencies.sync_resource.async_resolve(app_container)
112
- instance2 = Dependencies.sync_resource.sync_resolve(app_container)
113
- assert instance1 is instance2
114
-
115
- # create container of request scope
116
- async with app_container.build_child_container(scope=Scope.REQUEST) as request_container:
117
- # resolve factories of request scope
118
- Dependencies.simple_factory.sync_resolve(request_container)
119
- await Dependencies.dependent_factory.async_resolve(request_container)
120
-
121
- # resources of app-scope also can be resolved here
122
-
123
- ```
@@ -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,6 +4,7 @@ from modern_di.providers.context_adapter import ContextAdapter
4
4
  from modern_di.providers.dict import Dict
5
5
  from modern_di.providers.factory import Factory
6
6
  from modern_di.providers.list import List
7
+ from modern_di.providers.object import Object
7
8
  from modern_di.providers.resource import Resource
8
9
  from modern_di.providers.selector import Selector
9
10
  from modern_di.providers.singleton import Singleton
@@ -16,6 +17,7 @@ __all__ = [
16
17
  "Dict",
17
18
  "Factory",
18
19
  "List",
20
+ "Object",
19
21
  "Resource",
20
22
  "Selector",
21
23
  "Singleton",
@@ -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)
@@ -0,0 +1,27 @@
1
+ import enum
2
+ import typing
3
+
4
+ from modern_di import Container
5
+ from modern_di.providers.abstract import AbstractOverrideProvider
6
+
7
+
8
+ T_co = typing.TypeVar("T_co", covariant=True)
9
+ P = typing.ParamSpec("P")
10
+
11
+
12
+ class Object(AbstractOverrideProvider[T_co]):
13
+ __slots__ = [*AbstractOverrideProvider.BASE_SLOTS, "_obj"]
14
+
15
+ def __init__(self, scope: enum.IntEnum, obj: T_co) -> None:
16
+ super().__init__(scope)
17
+ self._obj: typing.Final = obj
18
+
19
+ async def async_resolve(self, container: Container) -> T_co:
20
+ return self.sync_resolve(container)
21
+
22
+ def sync_resolve(self, container: Container) -> T_co:
23
+ container = container.find_container(self.scope)
24
+ if (override := container.fetch_override(self.provider_id)) is not None:
25
+ return typing.cast(T_co, override)
26
+
27
+ return self._obj
@@ -1,4 +1,4 @@
1
- import contextlib
1
+ import contextlib # noqa: A005
2
2
  import enum
3
3
  import inspect
4
4
  import typing
File without changes
File without changes
File without changes