Coverage for src/lexigram/admin/schema/base.py: 27%
41 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:31 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:31 +0800
1from __future__ import annotations
3from abc import ABC, abstractmethod
4from dataclasses import dataclass, field
5from typing import Any, Generic, TypeVar
7from lexigram.admin.schema.exceptions import FieldError
8from lexigram.admin.schema.validators import FieldValidator
9from lexigram.result import Ok, Result
10from lexigram.ui import Element
12T = TypeVar("T")
15@dataclass(frozen=True, kw_only=True)
16class SchemaField(ABC, Generic[T]):
17 """.. stability:: stable"""
19 name: str
20 label: str | None = None
21 help_text: str | None = None
22 placeholder: str | None = None
24 nullable: bool = True
25 readonly: bool = False
26 required: bool = False
27 sortable: bool = True
28 searchable: bool = False
29 filterable: bool = True
30 visible_in_form: bool = True
31 visible_in_list: bool = True
32 visible_in_view: bool = True
34 validators: list[FieldValidator] = field(default_factory=list)
35 default: T | None = None
37 @abstractmethod
38 def render_form(
39 self, value: T | None, *, errors: list[str] | None = None
40 ) -> Element:
41 """Render this field as a form input."""
43 @abstractmethod
44 def render_column(self, record: Any, value: T | None) -> Element:
45 """Render this field as a table-cell value."""
47 def render_infolist_entry(self, value: T | None) -> Any:
48 """Render this field as a read-only detail entry."""
49 from lexigram.ui import InfolistEntry, InfolistEntryType
51 return InfolistEntry(
52 name=self.name,
53 label=self.label or self.name.replace("_", " ").title(),
54 value=value,
55 type=InfolistEntryType.TEXT,
56 )
58 def render_filter(self, current_value: Any | None = None) -> Element | None:
59 """Render this field as a filter widget. Return None to opt out."""
60 return None
62 def get_default(self) -> T | None:
63 """Return the default value for this field."""
64 return self.default
66 def from_form(self, raw: str | None) -> Result[T | None, FieldError]:
67 """Coerce a raw form string to the field's Python type."""
68 return Ok(raw) # type: ignore[arg-type]
70 def to_form(self, value: T | None) -> str:
71 """Coerce the field's Python value to a form-display string."""
72 return "" if value is None else str(value)