Type annotations in `exec`d code do not resolve as expected
まだ誰も着手していません。
- 主要言語
- 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 にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
まず Python 3.14 で最小再現を実行し、特に traceback に示されている annotate パスに注目して、inspect.signature が annotationlib.get_annotations を通る経路を追跡します。Python 3.13 および既存の exec ベースのテストと動作を比較します。exec で実行されたコード内でインポートされたアノテーションが、報告されているテストシナリオを壊すことなく正しく解決されれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- compilers
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 静か
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 45/100