`NameError: name 'Annotated' is not defined`
Note: This is quite a weird error, it took a while for me to be able to cut down my application to a minimal version that kept the error.
Attempted Explanation
If from __future__ import annotations is used in a file where a class decorated with @Parameter @dataclass is defined (class used to define common parameters) and that class is used as a base in another file, a runtime error will occur when any parameter from the inherited class is used.
Replication
- Make a file
a.py:
from __future__ import annotations
from dataclasses import dataclass
from typing import Annotated
from cyclopts import Parameter
@Parameter(name="*")
@dataclass
class A:
verbose: Annotated[bool, Parameter(negative="")] = False
- Make a file
main.py(this has to be a separate file, merging these two files avoids the issue):
#!/usr/bin/env python3
from dataclasses import dataclass
from cyclopts import App
from a import A
app = App()
@dataclass
class B(A):
...
@app.command()
def bad(
*,
common: B | None = None,
) -> None:
...
if __name__ == "__main__":
app()
- Run:
chmod +x main.py`
uv venv --python 3.10
source .venv/bin/activate
uv pip install cyclopts==3.16.1
./main.py bad --verbose
- Output:
Traceback (most recent call last):
File "/tmp/cyclopts/minimal/./main.py", line 26, in <module>
app()
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/core.py", line 1213, in __call__
command, bound, _ = self.parse_args(
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/core.py", line 1128, in parse_args
command, bound, unused_tokens, ignored, argument_collection = self._parse_known_args(
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/core.py", line 1018, in _parse_known_args
bound, unused_tokens = create_bound_arguments(
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/bind.py", line 351, in create_bound_arguments
argument_collection._convert()
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 190, in _convert
argument.convert_and_validate()
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 1181, in convert_and_validate
val = self.convert(converter=converter)
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 1097, in convert
self.value = self._convert(converter=converter)
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 1070, in _convert
self._run_missing_keys_checker(data)
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 1281, in _run_missing_keys_checker
if not (missing_keys := self._missing_keys_checker(self, data)):
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/argument.py", line 101, in inner
field_info = get_field_info(argument.hint)
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/field_info.py", line 149, in _generic_class_field_infos
for name, field_info in signature_parameters(f.__init__).items():
File "/tmp/cyclopts/minimal/.venv/lib/python3.10/site-packages/cyclopts/field_info.py", line 295, in signature_parameters
type_hints = get_type_hints(func, include_extras=True)
File "/home/dwalters/.local/share/uv/python/cpython-3.10.16-linux-x86_64-gnu/lib/python3.10/typing.py", line 1871, in get_type_hints
value = _eval_type(value, globalns, localns)
File "/home/dwalters/.local/share/uv/python/cpython-3.10.16-linux-x86_64-gnu/lib/python3.10/typing.py", line 327, in _eval_type
return t._evaluate(globalns, localns, recursive_guard)
File "/home/dwalters/.local/share/uv/python/cpython-3.10.16-linux-x86_64-gnu/lib/python3.10/typing.py", line 694, in _evaluate
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
NameError: name 'Annotated' is not defined
If you remove the from __future__ line, this example will work fine.
This might be related to #352
For reference as to what I did that caused this to happen:
- I changed my
ruffconfig to add the following:
[tool.ruff.lint.isort]
required-imports = ["from __future__ import annotations"]
- This caused issues with
cycloptsas this combined with someflake8rules caused imports thatcycloptsuses at runtime to be moved toTYPE_CHECKINGblocks.- I fixed this with:
[tool.ruff.lint.flake8-type-checking]
exempt-modules = ["cyclopts", "typing"]
This error started occurring as a result, although I didn't realised until sometime later after making a load of other changes.
The following prevented the I002 rule from attempting to add the from __future__ ... line to the file with the issue:
[tool.ruff.lint.per-file-ignores]
"/path/to/my/version/of/a.py" = ["I002"]
This allows me to avoid the issue, whilst still mandating that from __future__ ... be used in every file except the one that causes this issue.
hmmm, this is definitely weird (and maybe actually a bug in python's typing? Definitely want to investigate further before making any claims).
If you add the following unused imports to your main.py, it all works:
from typing import Annotated
from cyclopts import Parameter
related: I think pydantic had to solve a very similar problem, and we can re-use their solution. Related links:
- https://github.com/pydantic/pydantic/blob/103f64da67ad23dfdc7406187db32019a0204d70/pydantic/_internal/_typing_extra.py#L210
- https://github.com/pydantic/pydantic/blob/103f64da67ad23dfdc7406187db32019a0204d70/docs/internals/resolving_annotations.md?plain=1#L43
More related:
- https://github.com/python/cpython/issues/89687
@domWalters So I'm not 100% sure how to proceed with this:
- I made this branch that I think fixes it, but the code is kind of jank. Further still, docstrings from
class Aare not being resolved properly. I could probably pile on more jank to fix this, but I'm not sure if it's the way to go. - It seems like CPython has some bugs related to this.
- It looks like they might be fixing those dataclass bugs, but it would only be backported to 3.13 and 3.14.
-
PEP-649 and PEP-749 deprecate the usage of
from __future__ import annotations
@domWalters any additional input? Otherwise I'll close this issue as "not planned."
Apologies for not replying sooner.
I think not planned is fair as this does seem like more of an issue with python itself.
Might make sense to mention it in some FAQ on the docs?
Added a "Known Issues" page to the documentation. Let me know if you have any further issues!