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)