Coverage for src / lexigram / admin / forms / state.py: 50%
62 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
1"""Unified Form State Management."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import datetime
7from typing import Any
10@dataclass
11class FormState:
12 """Manages the state of a form, including values and errors."""
14 data: dict[str, Any] = field(default_factory=dict)
15 errors: dict[str, list[str]] = field(default_factory=dict)
16 is_dirty: bool = False
17 is_submitting: bool = False
18 last_saved_at: datetime | None = None
20 def update_field(self, name: str, value: Any) -> Any:
21 """Update a field's value and mark as dirty."""
22 self.data[name] = value
23 self.is_dirty = True
25 def set_error(self, name: str, error: str) -> Any:
26 """Set an error for a field."""
27 if name not in self.errors:
28 self.errors[name] = []
29 self.errors[name].append(error)
31 def clear_errors(self, name: str | None = None) -> Any:
32 """Clear errors for a specific field or all fields."""
33 if name:
34 self.errors.pop(name, None)
35 else:
36 self.errors.clear()
38 @property
39 def has_errors(self) -> bool:
40 """Check if any field has errors."""
41 return len(self.errors) > 0
43 def to_dict(self) -> dict[str, Any]:
44 """Convert state to a dictionary for persistence."""
45 return {
46 "data": self.data,
47 "errors": self.errors,
48 "is_dirty": self.is_dirty,
49 "last_saved_at": self.last_saved_at.isoformat()
50 if self.last_saved_at
51 else None,
52 }
54 @classmethod
55 def from_dict(cls, data: dict[str, Any]) -> FormState:
56 """Create state from a dictionary."""
57 last_saved = data.get("last_saved_at")
58 return cls(
59 data=data.get("data", {}),
60 errors=data.get("errors", {}),
61 is_dirty=data.get("is_dirty", False),
62 last_saved_at=datetime.fromisoformat(last_saved) if last_saved else None,
63 )
66class FormStore:
67 """Orchestrates form state and validation logic."""
69 def __init__(
70 self,
71 initial_values: dict[str, Any] | None = None,
72 validation_engine: Any | None = None,
73 ):
74 self.state = FormState(data=initial_values or {})
75 self.validation_engine = validation_engine
77 @property
78 def data(self) -> dict[str, Any]:
79 return self.state.data
81 @property
82 def errors(self) -> dict[str, list[str]]:
83 return self.state.errors
85 @property
86 def is_dirty(self) -> bool:
87 return self.state.is_dirty
89 def get_value(self, name: str, default: Any = None) -> Any:
90 return self.data.get(name, default)
92 def set_value(self, name: str, value: Any) -> Any:
93 self.state.update_field(name, value)
95 async def validate(self) -> bool:
96 """Validate the current form data using the validation engine."""
97 if not self.validation_engine:
98 return True
100 self.state.clear_errors()
101 errors = await self.validation_engine.validate_form(self.data)
103 if errors:
104 for field_name, field_errors in errors.items():
105 for error in field_errors:
106 msg = error.message if hasattr(error, "message") else str(error)
107 self.state.set_error(field_name, msg)
108 return False
110 return True
112 def to_dict(self) -> dict[str, Any]:
113 return self.state.to_dict()