{"record":{"id":"9b6fd7f0c9686615","repo":"Textualize/textual","slug":"duration-r-is-not-a-valid-duration","errorCode":null,"errorMessage":"{duration!r} is not a valid duration.","messagePattern":"(.+?) is not a valid duration\\.","errorType":"validation","errorClass":"DurationParseError","httpStatus":null,"severity":"error","filePath":"src/textual/_duration.py","lineNumber":42,"sourceCode":"    Raises:\n        DurationParseError: If the argument `duration` is not a valid duration string.\n    Returns:\n        The duration in seconds.\n    \"\"\"\n    match = _match_duration(duration)\n\n    if match:\n        value, unit_name = match.groups()\n        value = float(value)\n        if unit_name == \"ms\":\n            duration_secs = value / 1000\n        else:\n            duration_secs = value\n    else:\n        try:\n            duration_secs = float(duration)\n        except ValueError:\n            raise DurationParseError(f\"{duration!r} is not a valid duration.\") from None\n\n    return duration_secs\n","sourceCodeStart":24,"sourceCodeEnd":45,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/_duration.py#L24-L45","documentation":"_duration_as_seconds parses CSS-style durations (e.g. '400ms', '2s') into seconds. If the string doesn't match a recognized unit pattern, the code falls back to float(duration); when that also fails with ValueError, it raises DurationParseError. (Note: the check is `except ValueError` while float raises TypeError for non-strings in some paths — pass a string or number.)","triggerScenarios":"Calling with an unparseable string like 'fast', '400 ms' (depending on supported grammar), '' or '10px'; or passing a non-numeric, non-string type such as None or a list.","commonSituations":"Loading animation/transition durations from config files or CLI args with typos ('0.5sec' vs '0.5s'), missing values defaulting to None, or wrong units copied from CSS.","solutions":["Use a supported format: a plain number of seconds ('0.4', 0.4) or a string with s/ms units like '400ms' or '2s'","Validate duration strings at config-load time with a regex or try _duration_as_seconds early","Default missing config values to a valid literal (e.g. '300ms') rather than None","Check for unit typos such as 'sec' instead of 's'"],"exampleFix":"# before\nprocess_transition(widget, '0.4sec')  # DurationParseError\n\n# after\nprocess_transition(widget, '0.4s')","handlingStrategy":"validation","validationCode":"import re\nDURATION_RE = re.compile(r'^[0-9]*\\.?[0-9]+(ms|s)?$')\n\ndef valid_duration(d) -> bool:\n    if isinstance(d, (int, float)):\n        return True\n    return isinstance(d, str) and bool(DURATION_RE.match(d.strip()))","typeGuard":"import re\nfrom typing import Any\n\ndef is_valid_duration(value: Any) -> bool:\n    if isinstance(value, (int, float)):\n        return True\n    return isinstance(value, str) and bool(re.match(r'^[0-9]*\\.?[0-9]+(ms|s)?$', value.strip()))","tryCatchPattern":"from textual._duration import DurationParseError\ntry:\n    secs = _duration_as_seconds(raw)\nexcept DurationParseError:\n    secs = 0.3  # sensible default","preventionTips":["Use canonical units: '300ms' or '0.3s' or plain numbers","Validate duration config values at startup with a regex","Provide typed config (float seconds) rather than free-form strings"],"tags":["python","textual","animation","duration","parse-error"],"backgroundTag":"invalid-duration-string","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}