Skip to content

Commit 2127e3c

Browse files
committed
Merge branch 'release/4.21.0' into master
2 parents 81da4e0 + 7fdd25e commit 2127e3c

File tree

16 files changed

+29946
-24465
lines changed

16 files changed

+29946
-24465
lines changed

docs/main/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
4.21.0
11+
------
12+
- Improve ``Dependency`` provider error message: when dependency is undefined,
13+
error message contains its name.
14+
1015
4.20.2
1116
------
1217
- Move docs on container "self" injections to "Providers" section.

docs/providers/dependency.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ Dependency provider will control that returned object is an instance of ``instan
1515

1616
To provide a dependency you need to override the ``Dependency`` provider. You can call
1717
provider ``.override()`` method or provide an overriding provider when creating a container.
18-
See :ref:`provider-overriding`.
18+
See :ref:`provider-overriding`. If you don't provide a dependency, ``Dependency`` provider
19+
will raise an error:
20+
21+
.. literalinclude:: ../../examples/providers/dependency_undefined_error.py
22+
:language: python
23+
:lines: 18-
1924

2025
You also can provide a default for the dependency. To provide a default use ``default`` argument:
2126
``Dependency(..., default=...)``. Default can be a value or a provider. If default is not a provider,

examples/providers/dependency.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import abc
44
import dataclasses
55

6-
from dependency_injector import containers, providers, errors
6+
from dependency_injector import containers, providers
77

88

99
class DbAdapter(metaclass=abc.ABCMeta):
@@ -41,10 +41,5 @@ class Container(containers.DeclarativeContainer):
4141
assert isinstance(container2.user_service().database, PostgresDbAdapter)
4242

4343
container3 = Container(database=providers.Singleton(object))
44-
try:
45-
container3.user_service()
46-
except errors.Error as exception:
47-
print(exception)
48-
# The output is:
49-
# <object object at 0x107ce5c40> is not an
50-
# instance of <class '__main__.DbAdapter'>
44+
container3.user_service() # <-- raises error:
45+
# <object ...> is not an instance of DbAdapter
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""`Dependency` provider undefined error example."""
2+
3+
import abc
4+
import dataclasses
5+
6+
from dependency_injector import containers, providers
7+
8+
9+
class DbAdapter(metaclass=abc.ABCMeta):
10+
...
11+
12+
13+
@dataclasses.dataclass
14+
class UserService:
15+
database: DbAdapter
16+
17+
18+
class Container(containers.DeclarativeContainer):
19+
20+
database = providers.Dependency(instance_of=DbAdapter)
21+
22+
user_service = providers.Factory(
23+
UserService,
24+
database=database,
25+
)
26+
27+
28+
if __name__ == '__main__':
29+
container = Container()
30+
container.user_service() # <-- raises error:
31+
# Dependency "Container.database" is not defined

src/dependency_injector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Top-level package."""
22

3-
__version__ = '4.20.2'
3+
__version__ = '4.21.0'
44
"""Version number.
55
66
:type: str

src/dependency_injector/containers.c

Lines changed: 3786 additions & 2148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dependency_injector/containers.pyi

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ from typing import (
88
ClassVar,
99
Callable as _Callable,
1010
Iterable,
11+
Iterator,
1112
TypeVar,
1213
Awaitable,
1314
overload,
1415
)
1516

16-
from .providers import Provider, Self
17+
from .providers import Provider, Self, ProviderParent
1718

1819

1920
C_Base = TypeVar('C_Base', bound='Container')
@@ -38,18 +39,27 @@ class Container:
3839
def override_providers(self, **overriding_providers: Provider) -> None: ...
3940
def reset_last_overriding(self) -> None: ...
4041
def reset_override(self) -> None: ...
41-
def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ...
4242
def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None) -> None: ...
4343
def unwire(self) -> None: ...
4444
def init_resources(self) -> Optional[Awaitable]: ...
4545
def shutdown_resources(self) -> Optional[Awaitable]: ...
4646
def apply_container_providers_overridings(self) -> None: ...
4747
def reset_singletons(self) -> None: ...
4848
@overload
49-
def traverse(self, types: Optional[Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
49+
def resolve_provider_name(self, provider: Provider) -> str: ...
5050
@classmethod
5151
@overload
52-
def traverse(self, types: Optional[Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
52+
def resolve_provider_name(cls, provider: Provider) -> str: ...
53+
@property
54+
def parent(self) -> Optional[ProviderParent]: ...
55+
@property
56+
def parent_name(self) -> Optional[str]: ...
57+
def assign_parent(self, parent: ProviderParent) -> None: ...
58+
@overload
59+
def traverse(self, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ...
60+
@classmethod
61+
@overload
62+
def traverse(cls, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ...
5363

5464

5565
class DynamicContainer(Container): ...

src/dependency_injector/containers.pyx

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class DynamicContainer(Container):
6666
self.provider_type = providers.Provider
6767
self.providers = {}
6868
self.overridden = tuple()
69+
self.parent = None
6970
self.declarative_parent = None
7071
self.wired_to_modules = []
7172
self.wired_to_packages = []
@@ -92,6 +93,8 @@ class DynamicContainer(Container):
9293
for name, provider in providers.deepcopy(self.providers, memo).items():
9394
copied.set_provider(name, provider)
9495

96+
copied.parent = providers.deepcopy(self.parent, memo)
97+
9598
return copied
9699

97100
def __setattr__(self, str name, object value):
@@ -108,9 +111,16 @@ class DynamicContainer(Container):
108111
109112
:rtype: None
110113
"""
111-
if isinstance(value, providers.Provider) and not isinstance(value, providers.Self):
114+
if isinstance(value, providers.Provider) \
115+
and not isinstance(value, providers.Self) \
116+
and name != 'parent':
112117
_check_provider_type(self, value)
118+
113119
self.providers[name] = value
120+
121+
if isinstance(value, providers.CHILD_PROVIDERS):
122+
value.assign_parent(self)
123+
114124
super(DynamicContainer, self).__setattr__(name, value)
115125

116126
def __delattr__(self, str name):
@@ -295,6 +305,29 @@ class DynamicContainer(Container):
295305
for provider in self.traverse(types=[providers.BaseSingleton]):
296306
provider.reset()
297307

308+
def resolve_provider_name(self, provider):
309+
"""Try to resolve provider name."""
310+
for provider_name, container_provider in self.providers.items():
311+
if container_provider is provider:
312+
return provider_name
313+
else:
314+
raise errors.Error(f'Can not resolve name for provider "{provider}"')
315+
316+
@property
317+
def parent_name(self):
318+
"""Return parent name."""
319+
if self.parent:
320+
return self.parent.parent_name
321+
322+
if self.declarative_parent:
323+
return self.declarative_parent.__name__
324+
325+
return None
326+
327+
def assign_parent(self, parent):
328+
"""Assign parent."""
329+
self.parent = parent
330+
298331

299332
class DeclarativeContainerMetaClass(type):
300333
"""Declarative inversion of control container meta class."""
@@ -341,6 +374,10 @@ class DeclarativeContainerMetaClass(type):
341374
for provider in six.itervalues(cls.providers):
342375
_check_provider_type(cls, provider)
343376

377+
for provider in six.itervalues(cls.cls_providers):
378+
if isinstance(provider, providers.CHILD_PROVIDERS):
379+
provider.assign_parent(cls)
380+
344381
return cls
345382

346383
def __setattr__(cls, str name, object value):
@@ -359,6 +396,10 @@ class DeclarativeContainerMetaClass(type):
359396
"""
360397
if isinstance(value, providers.Provider) and name != '__self__':
361398
_check_provider_type(cls, value)
399+
400+
if isinstance(value, providers.CHILD_PROVIDERS):
401+
value.assign_parent(cls)
402+
362403
cls.providers[name] = value
363404
cls.cls_providers[name] = value
364405
super(DeclarativeContainerMetaClass, cls).__setattr__(name, value)
@@ -399,6 +440,19 @@ class DeclarativeContainerMetaClass(type):
399440
"""Return providers traversal generator."""
400441
yield from providers.traverse(*cls.providers.values(), types=types)
401442

443+
def resolve_provider_name(cls, provider):
444+
"""Try to resolve provider name."""
445+
for provider_name, container_provider in cls.providers.items():
446+
if container_provider is provider:
447+
return provider_name
448+
else:
449+
raise errors.Error(f'Can not resolve name for provider "{provider}"')
450+
451+
@property
452+
def parent_name(cls):
453+
"""Return parent name."""
454+
return cls.__name__
455+
402456
@staticmethod
403457
def __fetch_self(attributes):
404458
self = None

0 commit comments

Comments
 (0)