Coverage for src / lexigram / admin / schema / base.py: 89%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 22:14 +0800

1from __future__ import annotations 

2 

3from abc import ABC, abstractmethod 

4from dataclasses import dataclass, field 

5from typing import Any, Generic, TypeVar 

6 

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 

11 

12T = TypeVar("T") 

13 

14 

15@dataclass(frozen=True, kw_only=True) 

16class SchemaField(ABC, Generic[T]): 

17 """.. stability:: stable""" 

18 

19 name: str 

20 label: str | None = None 

21 help_text: str | None = None 

22 placeholder: str | None = None 

23 

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 

33 

34 validators: list[FieldValidator] = field(default_factory=list) 

35 default: T | None = None 

36 

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.""" 

42 

43 @abstractmethod 

44 def render_column(self, record: Any, value: T | None) -> Element: 

45 """Render this field as a table-cell value.""" 

46 

47 def render_filter(self, current_value: Any | None = None) -> Element | None: 

48 """Render this field as a filter widget. Return None to opt out.""" 

49 return None 

50 

51 def get_default(self) -> T | None: 

52 """Return the default value for this field.""" 

53 return self.default 

54 

55 def from_form(self, raw: str | None) -> Result[T | None, FieldError]: 

56 """Coerce a raw form string to the field's Python type.""" 

57 return Ok(raw) # type: ignore[arg-type] 

58 

59 def to_form(self, value: T | None) -> str: 

60 """Coerce the field's Python value to a form-display string.""" 

61 return "" if value is None else str(value)