v0.166.0
 1from typing import TYPE_CHECKING, Any, cast
 2
 3from plain.preflight import PreflightResult
 4
 5from plain import exceptions
 6
 7from .base import ColumnField
 8
 9if TYPE_CHECKING:
10    from plain.postgres.base import Model
11    from plain.postgres.connection import DatabaseConnection
12
13
14class PrimaryKeyField(ColumnField[int]):
15    """Identity primary key — Postgres generates the value on INSERT.
16
17    Accepts no constructor arguments: the column is always ``bigint GENERATED
18    BY DEFAULT AS IDENTITY``, NOT NULL, with RETURNING populating the value
19    back onto the instance after save.
20    """
21
22    db_type_sql = "bigint"
23    cast_db_type_sql = "bigint"
24    db_type_suffix_sql = "GENERATED BY DEFAULT AS IDENTITY"
25    empty_strings_allowed = False
26
27    def __init__(self) -> None:
28        super().__init__(required=False)
29        self.primary_key = True
30        self.auto_created = True
31
32    @property
33    def db_returning(self) -> bool:
34        return True
35
36    def preflight(self, **kwargs: Any) -> list[PreflightResult]:
37        errors = super().preflight(**kwargs)
38        # `id` is the reserved PK name — Field._check_field_name rejects it
39        # for every other field type.
40        return [e for e in errors if e.id != "fields.reserved_field_name_id"]
41
42    def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
43        return (
44            self.name,
45            "plain.postgres.PrimaryKeyField",
46            cast(list[Any], []),
47            cast(dict[str, Any], {}),
48        )
49
50    def validate(self, value: Any, model_instance: Model) -> None:
51        # Identity columns aren't user-validated — Postgres owns the value.
52        pass
53
54    def to_python(self, value: Any) -> int | None:
55        if value is None:
56            return value
57        try:
58            return int(value)
59        except TypeError, ValueError:
60            raise exceptions.ValidationError(
61                '"%(value)s" value must be an integer.',
62                code="invalid",
63                params={"value": value},
64            )
65
66    def get_prep_value(self, value: Any) -> Any:
67        value = super().get_prep_value(value)
68        if value is None:
69            return None
70        try:
71            return int(value)
72        except (TypeError, ValueError) as e:
73            raise e.__class__(
74                f"Field '{self.name}' expected a number but got {value!r}.",
75            ) from e
76
77    def get_db_prep_value(
78        self, value: Any, connection: DatabaseConnection, prepared: bool = False
79    ) -> Any:
80        if not prepared:
81            value = self.get_prep_value(value)
82        return value
83
84    def rel_db_type(self) -> str:
85        return "bigint"