1from collections.abc import Callable, Sequence
2from functools import cached_property
3from typing import TYPE_CHECKING, Any
4from uuid import UUID
5
6from plain import exceptions
7
8from .base import NOT_PROVIDED, ColumnField
9
10if TYPE_CHECKING:
11 from plain.postgres.connection import DatabaseConnection
12 from plain.postgres.expressions import Func
13
14
15class UUIDField[T: (UUID, UUID | None) = UUID](ColumnField[T]):
16 db_type_sql = "uuid"
17 empty_strings_allowed = False
18
19 def __init__(
20 self,
21 *,
22 generate: bool = False,
23 default: Any = NOT_PROVIDED,
24 required: bool = True,
25 allow_null: bool = False,
26 validators: Sequence[Callable[..., Any]] = (),
27 ):
28 # `default` accepts only None (the nullable-optional marker). There is
29 # no Python-side UUID default; for generated values use generate=True.
30 self.generate = generate
31 super().__init__(
32 default=default,
33 required=required,
34 allow_null=allow_null,
35 validators=validators,
36 )
37
38 @cached_property
39 def _db_default_expression(self) -> Func | None:
40 if self.generate:
41 from plain.postgres.functions.uuid import GenRandomUUID
42
43 return GenRandomUUID()
44 return None
45
46 def get_db_default_expression(self) -> Func | None:
47 return self._db_default_expression
48
49 def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
50 name, path, args, kwargs = super().deconstruct()
51 if self.generate:
52 kwargs["generate"] = True
53 return name, path, args, kwargs
54
55 def get_prep_value(self, value: Any) -> Any:
56 value = super().get_prep_value(value)
57 return self.to_python(value)
58
59 def get_db_prep_value(
60 self, value: Any, connection: DatabaseConnection, prepared: bool = False
61 ) -> UUID | None:
62 # PostgreSQL has native UUID type
63 if value is None:
64 return None
65 if not isinstance(value, UUID):
66 value = self.to_python(value)
67 return value
68
69 def to_python(self, value: Any) -> UUID | None:
70 if value is not None and not isinstance(value, UUID):
71 input_form = "int" if isinstance(value, int) else "hex"
72 try:
73 return UUID(**{input_form: value})
74 except AttributeError, ValueError:
75 raise exceptions.ValidationError(
76 '"%(value)s" is not a valid UUID.',
77 code="invalid",
78 params={"value": value},
79 )
80 return value