From 0c3d66ea981adb13afef8fcd8a45324e1a91b476 Mon Sep 17 00:00:00 2001 From: Hrach Date: Wed, 1 Jul 2026 19:35:49 +0000 Subject: [PATCH] _parse_timedelta: raise options.Error with the offending value The previous code raised a bare 'Exception()' (no message, no type info) when the timedelta pattern failed to match, then wrapped the whole body in 'try/except Exception: raise' which is a no-op. A bare Exception() gives no hint about what failed; downstream code that catches options.Error (the rest of the module) would not see the failure, and the user would get 'Exception:' with no message at all. Replace 'raise Exception()' with 'raise Error("Invalid time delta: %r" % value)' (matching the style of _parse_datetime's 'Unrecognized date/time format') and drop the try/except: raise wrapper so genuine programming errors surface normally. Added test_parse_timedelta_invalid_raises_options_error in tornado/test/options_test.py covers --foo=xyz; the test fails on the pre-fix code (raises Exception, not Error) and passes with the fix. --- tornado/options.py | 29 +++++++++++++---------------- tornado/test/options_test.py | 9 +++++++++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/tornado/options.py b/tornado/options.py index 7ebae719f..fdfaea424 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -640,22 +640,19 @@ def _parse_datetime(self, value: str) -> datetime.datetime: ) def _parse_timedelta(self, value: str) -> datetime.timedelta: - try: - sum = datetime.timedelta() - start = 0 - while start < len(value): - m = self._TIMEDELTA_PATTERN.match(value, start) - if not m: - raise Exception() - num = float(m.group(1)) - units = m.group(2) or "seconds" - units = self._TIMEDELTA_ABBREV_DICT.get(units, units) - - sum += datetime.timedelta(**{units: num}) - start = m.end() - return sum - except Exception: - raise + sum = datetime.timedelta() + start = 0 + while start < len(value): + m = self._TIMEDELTA_PATTERN.match(value, start) + if not m: + raise Error("Invalid time delta: %r" % value) + num = float(m.group(1)) + units = m.group(2) or "seconds" + units = self._TIMEDELTA_ABBREV_DICT.get(units, units) + + sum += datetime.timedelta(**{units: num}) + start = m.end() + return sum def _parse_bool(self, value: str) -> bool: return value.lower() not in ("false", "0", "f") diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index 6c76da364..b193c0adf 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -254,6 +254,15 @@ def test_multiple_int(self): options.parse_command_line(["main.py", "--foo=1,3,5:7"]) self.assertEqual(options.foo, [1, 3, 5, 6, 7]) + def test_parse_timedelta_invalid_raises_options_error(self): + # Malformed timedelta input should raise options.Error with a + # human-readable message, not a bare Exception with no context. + options = OptionParser() + options.define("foo", type=datetime.timedelta) + with self.assertRaises(Error) as cm: + options.parse_command_line(["main.py", "--foo=xyz"]) + self.assertIn("xyz", str(cm.exception)) + def test_error_redefine(self): options = OptionParser() options.define("foo")