Skip to content

Commit 345e6b3

Browse files
committed
fix: make pytest_typeguard a no-op on Python 3.14
typeguard 2.13.3 uses ast.Str at import time, which was removed in Python 3.14. The pytest_typeguard plugin imported it eagerly at module load, crashing pytest session start on 3.14 and taking down test collection for the entire suite. This is a surgical fix: defer the typeguard import inside pytest_sessionstart and skip the install_import_hook call on Python 3.14+. Behavior on Python 3.13 and below is unchanged. Proper fix (typeguard 4.x migration) remains tracked by the existing TODO comment on the typeguard pin in pyproject.toml.
1 parent 99bad6c commit 345e6b3

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
from typeguard.importhook import install_import_hook
1+
import sys
2+
import warnings
23

34

45
def pytest_sessionstart(session):
6+
if sys.version_info >= (3, 14):
7+
warnings.warn(
8+
"pytest_typeguard plugin is disabled on Python 3.14+ until typeguard "
9+
"is upgraded to 4.x (typeguard 2.13.3 uses removed ast.Str). "
10+
"See pyproject.toml TODO.",
11+
stacklevel=2,
12+
)
13+
return
14+
from typeguard.importhook import install_import_hook
15+
516
install_import_hook(packages=["cumulusci"])
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Pin tests for the pytest_typeguard plugin's Python 3.14 compatibility shim.
2+
3+
typeguard 2.13.3 is pinned in pyproject.toml (TODO: upgrade to v4) and its
4+
import hook references ast.Str, which was removed in Python 3.14. The plugin
5+
must skip installing the hook on Python 3.14+ so pytest's session start does
6+
not crash and take the entire suite down.
7+
8+
These tests use mocks to simulate both Python version regimes so they pass
9+
under any interpreter that has a typeguard package available.
10+
"""
11+
12+
import importlib
13+
import sys
14+
import types
15+
from unittest import mock
16+
17+
18+
def _make_fake_typeguard():
19+
"""Build a fake `typeguard.importhook` module exposing a Mock install_import_hook."""
20+
fake_install_import_hook = mock.Mock()
21+
fake_typeguard = types.ModuleType("typeguard")
22+
fake_importhook = types.ModuleType("typeguard.importhook")
23+
fake_importhook.install_import_hook = fake_install_import_hook
24+
fake_typeguard.importhook = fake_importhook
25+
return fake_typeguard, fake_importhook, fake_install_import_hook
26+
27+
28+
def _reload_plugin():
29+
from cumulusci.tests.pytest_plugins import pytest_typeguard
30+
31+
importlib.reload(pytest_typeguard)
32+
return pytest_typeguard
33+
34+
35+
def test_pytest_sessionstart_is_noop_on_py314():
36+
"""On Python 3.14+, pytest_sessionstart must not call install_import_hook."""
37+
fake_typeguard, fake_importhook, fake_install_import_hook = _make_fake_typeguard()
38+
39+
with (
40+
mock.patch.dict(
41+
sys.modules,
42+
{"typeguard": fake_typeguard, "typeguard.importhook": fake_importhook},
43+
),
44+
mock.patch.object(sys, "version_info", (3, 14, 0, "final", 0)),
45+
):
46+
plugin = _reload_plugin()
47+
result = plugin.pytest_sessionstart(None)
48+
49+
assert result is None
50+
fake_install_import_hook.assert_not_called()
51+
52+
53+
def test_pytest_sessionstart_installs_hook_on_py313():
54+
"""On Python <= 3.13, pytest_sessionstart must install the typeguard import hook."""
55+
fake_typeguard, fake_importhook, fake_install_import_hook = _make_fake_typeguard()
56+
57+
with (
58+
mock.patch.dict(
59+
sys.modules,
60+
{"typeguard": fake_typeguard, "typeguard.importhook": fake_importhook},
61+
),
62+
mock.patch.object(sys, "version_info", (3, 13, 0, "final", 0)),
63+
):
64+
plugin = _reload_plugin()
65+
plugin.pytest_sessionstart(None)
66+
67+
fake_install_import_hook.assert_called_once_with(packages=["cumulusci"])

0 commit comments

Comments
 (0)