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