1from __future__ import annotations
 2
 3import datetime
 4from collections.abc import Callable
 5from typing import TYPE_CHECKING, Any
 6
 7from plain import exceptions
 8from plain.utils.dateparse import parse_duration
 9
10from .base import DefaultableField
11
12if TYPE_CHECKING:
13    from plain.postgres.connection import DatabaseConnection
14
15
16class DurationField(DefaultableField[datetime.timedelta]):
17    """Store timedelta objects using PostgreSQL's interval type."""
18
19    db_type_sql = "interval"
20    empty_strings_allowed = False
21
22    def to_python(self, value: Any) -> datetime.timedelta | None:
23        if value is None:
24            return value
25        if isinstance(value, datetime.timedelta):
26            return value
27        try:
28            parsed = parse_duration(value)
29        except ValueError:
30            pass
31        else:
32            if parsed is not None:
33                return parsed
34
35        raise exceptions.ValidationError(
36            '"%(value)s" value has an invalid format. It must be in [DD] [[HH:]MM:]ss[.uuuuuu] format.',
37            code="invalid",
38            params={"value": value},
39        )
40
41    def get_db_prep_value(
42        self, value: Any, connection: DatabaseConnection, prepared: bool = False
43    ) -> Any:
44        # PostgreSQL has native interval (duration) type
45        return value
46
47    def get_db_converters(
48        self, connection: DatabaseConnection
49    ) -> list[Callable[..., Any]]:
50        # PostgreSQL has native duration field, no converters needed
51        return super().get_db_converters(connection)