Type annotations in `exec`d code do not resolve as expected
还没有人认领这个 Issue。
- 主要语言
- Python
- 星标
- 77.2k
- 派生
- 35.9k
- PR 合并指标
- PR 指标待抓取
描述
Bug report
Bug description:
We've counted on the ability to exec code to test that various type annotations lead to the correct success/error scenarios
@pytest.mark.parametrize(
"annotation, value, raises, match",
[
pytest.param("Annotated[int, at.Gt(1)]", "0", True, "0 is not greater than 1", id="Gt fail"),
pytest.param("Annotated[int, at.Gt(1)]", "2", False, "", id="Gt ok"),
pytest.param("Annotated[int, at.Lt(1)]", "2", True, "2 is not less than 1", id="Lt fail"),
pytest.param("Annotated[int, at.Lt(1)]", "0", False, "", id="Lt ok"),
pytest.param("Annotated[int, at.Ge(1)]", "0", True, "0 is not greater than or equal to 1", id="Ge fail"),
pytest.param("Annotated[int, at.Ge(1)]", "1", False, "", id="Ge ok"),
pytest.param("Annotated[int, at.Le(1)]", "2", True, "2 is not less than or equal to 1", id="Le fail"),
pytest.param("Annotated[int, at.Le(1)]", "1", False, "", id="Le ok"),
pytest.param("Annotated[int, at.MultipleOf(2)]", "3", True, "3 is not a multiple of 2", id="MultipleOf fail"),
pytest.param("Annotated[int, at.MultipleOf(2)]", "4", False, "", id="MultipleOf ok"),
pytest.param("Annotated[str, at.MinLen(2)]", "'a'", True, "a is not longer than 2", id="MinLen fail"),
pytest.param("Annotated[str, at.MinLen(2)]", "'abc'", False, "", id="MinLen ok"),
pytest.param("Annotated[str, at.MaxLen(2)]", "'abc'", True, "abc is not shorter than 2", id="MaxLen fail"),
pytest.param("Annotated[str, at.MaxLen(2)]", "'a'", False, "", id="MaxLen ok"),
pytest.param(
"Annotated[int, at.Predicate(lambda x: False)]", "0", True, "0 does not satisfy", id="Predicate fail"
),
pytest.param("Annotated[int, at.Predicate(lambda x: True)]", "0", False, "", id="Predicate ok"),
],
)
async def test_test_with_annotated_type(annotation: str, value: str, raises: bool, match: str):
loc: dict = {}
exec(
dedent(
f"""
from typing import Annotated
import annotated_types as at
@Test("test")
async def test(test: TestInstance, foo: {annotation}) -> None:
pass
test_instance = test.use_options(foo={value})
"""
),
globals(),
loc,
)
test = loc["test_instance"]
ctx: AbstractContextManager
if raises:
ctx = pytest.raises(ValueError, match=match)
else:
ctx = nullcontext()
with ctx:
await parameterize(test, Environment(TestConfig("test")))
However, this now fails in python 3.14+
A "minimal" reproduction:
import inspect
from textwrap import dedent
from typing import TypeAlias, Callable, Coroutine, Self, get_type_hints
TestFunc: TypeAlias = Callable[..., Coroutine[None, None, None]]
class Test:
def __call__(self, func: TestFunc) -> Self:
print(inspect.signature(func).parameters)
print(get_type_hints(func))
def main():
exec(
dedent(
f"""
from typing import Annotated
import annotated_types as at
@Test()
async def test(foo: Annotated[int, at.Gt(1)]) -> None:
pass
print("After function call")
"""
),
globals(),
{},
)
if __name__ == "__main__":
main()
Despite the types Annotated and at being imported in the exec'd code, the values do not get resolved when trying to get annotations.
Traceback (most recent call last):
File "/home/vfazio/development/xtf/314annotations.py", line 33, in <module>
main()
~~~~^^
File "/home/vfazio/development/xtf/314annotations.py", line 14, in main
exec(
~~~~^
dedent(
^^^^^^^
...<12 lines>...
{},
^^^
)
^
File "<string>", line 5, in <module>
File "/home/vfazio/development/xtf/314annotations.py", line 10, in __call__
print(inspect.signature(func).parameters)
~~~~~~~~~~~~~~~~~^^^^^^
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 3322, in signature
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
globals=globals, locals=locals, eval_str=eval_str,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
annotation_format=annotation_format)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 3037, in from_callable
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped,
globals=globals, locals=locals, eval_str=eval_str,
annotation_format=annotation_format)
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 2512, in _signature_from_callable
return _signature_from_function(sigcls, obj,
skip_bound_arg=skip_bound_arg,
globals=globals, locals=locals, eval_str=eval_str,
annotation_format=annotation_format)
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 2335, in _signature_from_function
annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str,
format=annotation_format)
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/annotationlib.py", line 966, in get_annotations
ann = _get_dunder_annotations(obj)
File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/annotationlib.py", line 1146, in _get_dunder_annotations
ann = getattr(obj, "__annotations__", None)
File "<string>", line 6, in __annotate__
NameError: name 'Annotated' is not defined
This seems to imply that code generators/exec can no longer rely on imports in code to satisfy annotations? I don't know if this is due to changes in exec or PEP 649/749 but it is inconvenient.
CPython versions tested on:
3.14, 3.13, 3.12, 3.11
Operating systems tested on:
Linux
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
首先在 Python 3.14 上运行最小复现,并跟踪 inspect.signature 经由 annotationlib.get_annotations 的路径,尤其关注 traceback 中显示的 annotate 路径。将其行为与 Python 3.13 以及现有的基于 exec 的测试进行比较;当在通过 exec 执行的代码中导入的注解能够正确解析,且不会破坏报告中的测试场景时,即表示完成。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- python
- 领域
- compilers
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 冷清
- 描述清晰度
- 基本清楚
- 新手友好度
- 45/100