477 Containers @copy fix and refactoring (#485)

* Rename local variables

* Make code layout enhancements

* Add fix and tests

* Add more refactoring

* Update changelog
This commit is contained in:
Roman Mogylatov 2021-08-11 21:18:37 -04:00 committed by GitHub
parent cde7dee4b3
commit 7b19fa0964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 571 additions and 516 deletions

View File

@ -7,6 +7,12 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
Dev version
------
- Fix ``@containers.copy()`` decorator to respect dependencies on parent providers.
See issue `#477 <https://github.com/ets-labs/python-dependency-injector/issues/477>`_.
Thanks to `Andrey Torsunov @gtors <https://github.com/gtors>`_ for reporting the issue.
4.35.2
------
- Update wiring to support modules provided as packages.

File diff suppressed because it is too large Load Diff

View File

@ -733,43 +733,44 @@ def override(object container):
return _decorator
def copy(object container):
def copy(object base_container):
""":py:class:`DeclarativeContainer` copying decorator.
This decorator copies all providers from provided container to decorated one.
If one of the decorated container providers matches to source container
providers by name, it would be replaced by reference.
:param container: Container that should be copied by decorated container.
:type container: :py:class:`DeclarativeContainer`
:param base_container: Container that should be copied by decorated container.
:type base_container: :py:class:`DeclarativeContainer`
:return: Declarative container's copying decorator.
:rtype: callable(:py:class:`DeclarativeContainer`)
"""
def _get_providers_memo(from_providers, source_providers):
memo = dict()
for name, provider in from_providers.items():
try:
source_provider = source_providers[name]
except KeyError:
def _get_memo_for_matching_names(new_providers, base_providers):
memo = {}
for new_provider_name, new_provider in six.iteritems(new_providers):
if new_provider_name not in base_providers:
continue
else:
memo[id(source_provider)] = provider
source_provider = base_providers[new_provider_name]
memo[id(source_provider)] = new_provider
if hasattr(provider, 'providers') and hasattr(source_provider, 'providers'):
sub_memo = _get_providers_memo(provider.providers, source_provider.providers)
memo.update(sub_memo)
if hasattr(new_provider, 'providers') and hasattr(source_provider, 'providers'):
sub_memo = _get_memo_for_matching_names(new_provider.providers, source_provider.providers)
memo.update(sub_memo)
return memo
def _decorator(copied_container):
memo = _get_providers_memo(copied_container.cls_providers, container.providers)
def _decorator(new_container):
memo = {}
memo.update(_get_memo_for_matching_names(new_container.cls_providers, base_container.providers))
providers_copy = providers.deepcopy(container.providers, memo)
for name, provider in six.iteritems(providers_copy):
setattr(copied_container, name, provider)
new_providers = {}
new_providers.update(providers.deepcopy(base_container.providers, memo))
new_providers.update(providers.deepcopy(new_container.cls_providers, memo))
for name, provider in six.iteritems(new_providers):
setattr(new_container, name, provider)
return new_container
return copied_container
return _decorator

View File

@ -308,17 +308,31 @@ class DeclarativeContainerTests(unittest.TestCase):
self.assertIsNot(_Container1.p11, _Container2.p11)
self.assertIsNot(_Container1.p12, _Container2.p12)
self.assertIs(_Container.p12.kwargs['p11'], _Container.p11)
self.assertIs(_Container1.p12.kwargs['p11'], _Container1.p11)
self.assertIs(_Container2.p12.kwargs['p11'], _Container2.p11)
self.assertEqual(_Container.p12(), dict(p11=0))
self.assertEqual(_Container1.p12(), dict(p11=1))
self.assertEqual(_Container2.p12(), dict(p11=2))
self.assertEqual(_Container.p12(), {'p11': 0})
self.assertEqual(_Container1.p12(), {'p11': 1})
self.assertEqual(_Container2.p12(), {'p11': 2})
self.assertEqual(_Container1.p13(), 11)
self.assertEqual(_Container2.p13(), 22)
def test_copy_with_parent_dependency(self):
# See: https://github.com/ets-labs/python-dependency-injector/issues/477
class Base(containers.DeclarativeContainer):
p11 = providers.Object(0)
p12 = providers.Factory(dict, p11=p11)
@containers.copy(Base)
class New(Base):
p13 = providers.Factory(dict, p12=Base.p12)
new1 = New()
new2 = New(p11=1)
new3 = New(p11=2)
self.assertEqual(new1.p13(), {'p12': {'p11': 0}})
self.assertEqual(new2.p13(), {'p12': {'p11': 1}})
self.assertEqual(new3.p13(), {'p12': {'p11': 2}})
def test_copy_with_replacing_subcontainer_providers(self):
# See: https://github.com/ets-labs/python-dependency-injector/issues/374
class X(containers.DeclarativeContainer):