ets-labs/python-dependency-injector

Making injections into class attributes can't work with `Resource` in nested container.

Opened this issue · 2 comments

Reproducible project: https://github.com/YogiLiu/pdi_issue

When I run python -m pdi_issue.main, an error was raised:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/yogiliu/Workspace/YogiLiu/pdi_issue/src/pdi_issue/main.py", line 4, in <module>
    container = SrvContainer()
                ^^^^^^^^^^^^^^
  File "src/dependency_injector/containers.pyx", line 727, in dependency_injector.containers.DeclarativeContainer.__new__
  File "src/dependency_injector/providers.pyx", line 4916, in dependency_injector.providers.deepcopy
  File "src/dependency_injector/providers.pyx", line 4923, in dependency_injector.providers.deepcopy
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 153, in deepcopy
    y = copier(memo)
        ^^^^^^^^^^^^
  File "src/dependency_injector/providers.pyx", line 4024, in dependency_injector.providers.Container.__deepcopy__
  File "src/dependency_injector/providers.pyx", line 4923, in dependency_injector.providers.deepcopy
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 153, in deepcopy
    y = copier(memo)
        ^^^^^^^^^^^^
  File "src/dependency_injector/containers.pyx", line 125, in dependency_injector.containers.DynamicContainer.__deepcopy__
  File "src/dependency_injector/providers.pyx", line 4916, in dependency_injector.providers.deepcopy
  File "src/dependency_injector/providers.pyx", line 4923, in dependency_injector.providers.deepcopy
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 146, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 231, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "/home/yogiliu/.local/share/uv/python/cpython-3.11.10-linux-x86_64-gnu/lib/python3.11/copy.py", line 153, in deepcopy
    y = copier(memo)
        ^^^^^^^^^^^^
  File "src/dependency_injector/providers.pyx", line 3669, in dependency_injector.providers.Resource.__deepcopy__
dependency_injector.errors.Error: Can not copy initialized resource

Thanks for the report. Nested containers and wiring is an area we can improve. No ETA when we'll work on it, but in the mean time, try doing so:

diff
diff --git a/src/pdi_issue/container.py b/src/pdi_issue/container.py
index e4485ec..47f75ef 100644
--- a/src/pdi_issue/container.py
+++ b/src/pdi_issue/container.py
@@ -6,8 +6,6 @@ def create_engine():
 
 
 class RepoContainer(containers.DeclarativeContainer):
-    wiring_config = containers.WiringConfiguration(modules=['.repo'])
-
     engine = providers.Resource(create_engine)
     # engine = providers.Factory(lambda: 'database engine')
 
@@ -15,4 +13,6 @@ class RepoContainer(containers.DeclarativeContainer):
 
 
 class SrvContainer(containers.DeclarativeContainer):
+    wiring_config = containers.WiringConfiguration(modules=['.repo'])
+
     repo = providers.Container(RepoContainer)
diff --git a/src/pdi_issue/repo.py b/src/pdi_issue/repo.py
index e34a497..e78df19 100644
--- a/src/pdi_issue/repo.py
+++ b/src/pdi_issue/repo.py
@@ -2,7 +2,7 @@ from dependency_injector.wiring import Provide
 
 
 class UserRepo:
-    engine = Provide['engine']
+    engine = Provide['repo.engine']
 
     def do_something(self):
         print(self.engine)

Thanks for reply.

To be honest, there are many solutions for this case, like injecting as params of __init__ method. So, I think this is a quite special case. It must be great if it's supported.

Thanks for your contributions.