python-injector / python-injector/injector
[Question] Circular imports and "deferred"
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.5k
- Forks
- 94
- PR merge metrics
- No merged PRs in 30d
Description
I have an issue with circular imports that I am having trouble resolving.
Here is a simple repro case which is similar to what I am doing:
$ tree
.
└── circular
├── a.py
├── b.py
├── c.py
└── main.py
1 directory, 4 files
$ cat circular/a.py
# from circular import b
import circular.b as b
class A:
def __init__(self):
print(f'A.__init__ {id(self)=}')
$ cat circular/b.py
from injector import inject, Injector
import circular.c as c
class B:
@inject
def __init__(self, injector: Injector):
print(f'B.__init__ {id(self)=} {id(injector)=}')
$ cat circular/c.py
from injector import inject
import circular.b as b
class C:
@inject
def __init__(self, b: 'b.B'):
print(f'C.__init__ {id(self)=} {id(b)=}')
$ cat circular/main.py
# add to path to make this runnable from anywhere
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from injector import Injector
import circular.b as b
def main():
injector = Injector()
b_inst = injector.get(b.B)
if __name__ == '__main__':
main()
When I run this, I get an AttributeError related to circular imports caused by a call to get_type_hints. I hoped this would defer, due to the use of a forward reference in c.py and module imports, but maybe I am misunderstanding?
Traceback (most recent call last):
File "inj\circular\main.py", line 8, in <module>
import circular.b as b
File "inj\circular\b.py", line 3, in <module>
import circular.c as c
File "inj\circular\c.py", line 6, in <module>
class C:
File "inj\circular\c.py", line 8, in C
def __init__(self, b: 'b.B'):
File "[venv]\lib\site-packages\injector\__init__.py", line 1367, in inject
bindings = _infer_injected_bindings(function, only_explicit_bindings=False)
File "[venv]\lib\site-packages\injector\__init__.py", line 1176, in _infer_injected_bindings
bindings = get_type_hints(callable, include_extras=True)
File "[venv]\lib\site-packages\typing_extensions.py", line 1880, in get_type_hints
hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
File "[python]\Python38\lib\typing.py", line 1261, in get_type_hints
value = _eval_type(value, globalns, localns)
File "[python]\Python38\lib\typing.py", line 270, in _eval_type
return t._evaluate(globalns, localns)
File "[python]\Python38\lib\typing.py", line 518, in _evaluate
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
AttributeError: partially initialized module 'circular.b' has no attribute 'B' (most likely due to a circular import)
Since I don't have a full understanding of Injector internals, I tried a longshot of adding AttributeError to the try/except (in addition to NameError that's already caught) in _infer_injected_bindings at line 1164. This actually works for both the simplified repro and my actual application. However, I don't know what other implications this change might have and I wanted to seek you advice.
Thanks!
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the circular import using circular/a.py, b.py, c.py, and main.py, then inspect injector/init.py around line 1164 and _infer_injected_bindings. Start with the get_type_hints call and its existing exception handling. Done means determining whether the reported AttributeError should be handled and documenting or testing the resulting behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100