From 73e74c29ab5f6cb696ffea29e7e9ad8303a10c67 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Wed, 26 Aug 2026 01:25:59 -0700 Subject: [PATCH 1/2] fix: accept string timeout values in pyproject.toml PR #200 registered the timeout ini option as type='float', which rejects string values like timeout = '20.0' in pyproject.toml. Users must now choose a config format compatible with only one pytest-timeout version: 2.4.0 requires strings, 2.5.0 requires floats, and no value works for both. Remove the explicit type so the ini parsing accepts both string and numeric representations. _validate_timeout already calls float(timeout) on the raw value and handles both forms correctly. Fixes #203 Co-authored-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> --- pytest_timeout.py | 4 ++-- test_pytest_timeout.py | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 192a16c..29f2a57 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -94,7 +94,7 @@ def pytest_addoption(parser): metavar="SECONDS", help=SESSION_TIMEOUT_DESC, ) - parser.addini("timeout", TIMEOUT_DESC, type="float") + parser.addini("timeout", TIMEOUT_DESC) parser.addini("timeout_method", METHOD_DESC) parser.addini("timeout_func_only", FUNC_ONLY_DESC, type="bool", default=False) parser.addini( @@ -103,7 +103,7 @@ def pytest_addoption(parser): type="bool", default=False, ) - parser.addini("session_timeout", SESSION_TIMEOUT_DESC, type="float") + parser.addini("session_timeout", SESSION_TIMEOUT_DESC) class TimeoutHooks: diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index ff36c40..cf19a34 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -372,6 +372,32 @@ def test_foo(): assert result.ret +@pytest.mark.skipif( + pytest.version_tuple < (9, 0), + reason="native [tool.pytest] table requires pytest 9", +) +def test_pyproject_toml_str_timeout(pytester): + """Regression test for #203: accept string timeout values in pyproject.toml.""" + pytester.makepyfile( + """ + import time + + def test_foo(): + time.sleep(2) + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest] + timeout = "1" + session_timeout = "60" + """ + ) + result = pytester.runpytest_subprocess() + result.stdout.no_fnmatch_line("INTERNALERROR*") + assert result.ret + + def test_ini_timeout_func_only(pytester): pytester.makepyfile( """ From 4c9d604f0c3c8f87e1987492cfdec37e361eecd2 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:01:26 -0700 Subject: [PATCH 2/2] fix: accept both quoted and unquoted timeout values in pyproject.toml On pytest 9+, registering an ini option without an explicit type (defaulting to "string") rejects non-string TOML values, while type="float" rejects quoted strings. Both representations need to work so users don't have to choose a config format by version. Introduce _get_ini_value() which reads via config.getini() but catches TypeError and falls back to the raw config dict, then use it for both and ini reads. The regression test now covers both (quoted string) and (unquoted int), and asserts a real timeout outcome via the failure message and assert_outcomes instead of a stdout-only INTERNALERROR check that masked the configuration error. Closes #203 --- pytest_timeout.py | 20 +++++++++++++++++--- test_pytest_timeout.py | 25 ++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/pytest_timeout.py b/pytest_timeout.py index 29f2a57..3ea64da 100644 --- a/pytest_timeout.py +++ b/pytest_timeout.py @@ -57,6 +57,20 @@ ) +def _get_ini_value(config, name): + """Read an ini option, falling back to the raw config for non-string TOML values. + + On pytest 9+ the ``"string"`` ini-type rejects non-string TOML values + (e.g. ``timeout = 1``), but ``"float"`` rejects quoted strings such as + ``timeout = "1"``. We register the option with the default string type + and catch the ``TypeError`` here so both representations work. + """ + try: + return config.getini(name) + except TypeError: + return config.inicfg.get(name) + + @pytest.hookimpl def pytest_addoption(parser): """Add options to control the timeout plugin.""" @@ -161,9 +175,9 @@ def pytest_configure(config): timeout = config.getoption("session_timeout") if timeout is None: - ini = config.getini("session_timeout") + ini = _get_ini_value(config, "session_timeout") if ini: - timeout = _validate_timeout(config.getini("session_timeout"), "config file") + timeout = _validate_timeout(ini, "config file") if timeout is not None: expire_time = time.time() + timeout else: @@ -350,7 +364,7 @@ def get_env_settings(config): os.environ.get("PYTEST_TIMEOUT"), "PYTEST_TIMEOUT environment variable" ) if timeout is None: - ini = config.getini("timeout") + ini = _get_ini_value(config, "timeout") if ini: timeout = _validate_timeout(ini, "config file") diff --git a/test_pytest_timeout.py b/test_pytest_timeout.py index cf19a34..865f12c 100644 --- a/test_pytest_timeout.py +++ b/test_pytest_timeout.py @@ -376,8 +376,19 @@ def test_foo(): pytest.version_tuple < (9, 0), reason="native [tool.pytest] table requires pytest 9", ) -def test_pyproject_toml_str_timeout(pytester): - """Regression test for #203: accept string timeout values in pyproject.toml.""" +@pytest.mark.parametrize( + ("toml_timeout", "toml_session_timeout"), + [ + pytest.param('"1"', '"60"', id="quoted-string"), + pytest.param("1", "60", id="unquoted-int"), + ], +) +def test_pyproject_toml_timeout(pytester, toml_timeout, toml_session_timeout): + """Regression test for #203: accept timeout values in pyproject.toml. + + Both quoted (string) and unquoted (int) forms should work in the + ``[tool.pytest]`` table (``timeout = "1"`` and ``timeout = 1``). + """ pytester.makepyfile( """ import time @@ -387,15 +398,15 @@ def test_foo(): """ ) pytester.makepyprojecttoml( - """ + f""" [tool.pytest] - timeout = "1" - session_timeout = "60" + timeout = {toml_timeout} + session_timeout = {toml_session_timeout} """ ) result = pytester.runpytest_subprocess() - result.stdout.no_fnmatch_line("INTERNALERROR*") - assert result.ret + result.stdout.fnmatch_lines([MATCH_FAILURE_MESSAGE % "1.0"]) + result.assert_outcomes(failed=1) def test_ini_timeout_func_only(pytester):