v0.166.0
 1from base64 import b64decode
 2from collections.abc import Callable, Sequence
 3from typing import TYPE_CHECKING, Any
 4
 5import psycopg
 6from plain.validators import MaxLengthValidator
 7
 8from .base import NOT_PROVIDED, DefaultableField
 9
10if TYPE_CHECKING:
11    from plain.postgres.connection import DatabaseConnection
12    from plain.postgres.sql.compiler import SQLCompiler
13
14
15class BinaryField[
16    T: (bytes | memoryview, bytes | memoryview | None) = bytes | memoryview
17](DefaultableField[T]):
18    db_type_sql = "bytea"
19    empty_values = (None, b"")
20    _default_empty_value = b""
21    only_empty_default = True
22
23    def __init__(
24        self,
25        *,
26        max_length: int | None = None,
27        required: bool = True,
28        allow_null: bool = False,
29        default: Any = NOT_PROVIDED,
30        validators: Sequence[Callable[..., Any]] = (),
31    ):
32        self.max_length = max_length
33        super().__init__(
34            required=required,
35            allow_null=allow_null,
36            default=default,
37            validators=validators,
38        )
39        if self.max_length is not None:
40            self.validators.append(MaxLengthValidator(self.max_length))
41
42    def deconstruct(self) -> tuple[str, str, list[Any], dict[str, Any]]:
43        name, path, args, kwargs = super().deconstruct()
44        if self.max_length is not None:
45            kwargs["max_length"] = self.max_length
46        return name, path, args, kwargs
47
48    def get_placeholder(
49        self, value: Any, compiler: SQLCompiler, connection: DatabaseConnection
50    ) -> Any:
51        return "%s"
52
53    def get_db_prep_value(
54        self, value: Any, connection: DatabaseConnection, prepared: bool = False
55    ) -> Any:
56        value = super().get_db_prep_value(value, connection, prepared)
57        if value is not None:
58            return psycopg.Binary(value)
59        return value
60
61    def to_python(self, value: Any) -> bytes | memoryview | None:
62        # If it's a string, it should be base64-encoded data
63        if isinstance(value, str):
64            return memoryview(b64decode(value.encode("ascii")))
65        return value