Textualize/textual · error · DurationParseError
{duration!r} is not a valid duration.
Error message
{duration!r} is not a valid duration. What it means
_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.)
Source
Thrown at src/textual/_duration.py:42
Raises:
DurationParseError: If the argument `duration` is not a valid duration string.
Returns:
The duration in seconds.
"""
match = _match_duration(duration)
if match:
value, unit_name = match.groups()
value = float(value)
if unit_name == "ms":
duration_secs = value / 1000
else:
duration_secs = value
else:
try:
duration_secs = float(duration)
except ValueError:
raise DurationParseError(f"{duration!r} is not a valid duration.") from None
return duration_secs
View on GitHub (pinned to 06dbeef4bb)
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'
Example fix
# before process_transition(widget, '0.4sec') # DurationParseError # after process_transition(widget, '0.4s')
Defensive patterns
Strategy: validation
Validate before calling
import re
DURATION_RE = re.compile(r'^[0-9]*\.?[0-9]+(ms|s)?$')
def valid_duration(d) -> bool:
if isinstance(d, (int, float)):
return True
return isinstance(d, str) and bool(DURATION_RE.match(d.strip())) Type guard
import re
from typing import Any
def is_valid_duration(value: Any) -> bool:
if isinstance(value, (int, float)):
return True
return isinstance(value, str) and bool(re.match(r'^[0-9]*\.?[0-9]+(ms|s)?$', value.strip())) Try / catch
from textual._duration import DurationParseError
try:
secs = _duration_as_seconds(raw)
except DurationParseError:
secs = 0.3 # sensible default Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Can't animate attribute {attribute!r} on {obj!r}; attribute
- Don't know how to animate {value!r}; Can only animate <int>,
- Can't encode {datum!r}
- must be bytes
- More data expected
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/9b6fd7f0c9686615.
Report an issue: GitHub.