from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime from enum import StrEnum from typing import Any class FieldType(StrEnum): STRING = "string" INTEGER = "integer" DOUBLE = "double" DATE = "date" PASSWORD = "password" @dataclass(slots=True) class FieldDescription: name: str type: FieldType = FieldType.STRING @dataclass(slots=True) class Field: content: Any @classmethod def with_content(cls, content: Any) -> "Field": if content is None: raise ValueError("content must not be None") return cls(content=content) @property def type(self) -> FieldType: if isinstance(self.content, bool): return FieldType.STRING if isinstance(self.content, int): return FieldType.INTEGER if isinstance(self.content, float): return FieldType.DOUBLE if isinstance(self.content, (date, datetime)): return FieldType.DATE if isinstance(self.content, (bytes, bytearray)): return FieldType.PASSWORD return FieldType.STRING def as_string(self) -> str: if isinstance(self.content, (bytes, bytearray)): return self.content.decode("utf-8") if isinstance(self.content, (date, datetime)): return self.content.isoformat() return str(self.content) @dataclass class FieldConfiguration: fields: list[FieldDescription] = field(default_factory=list) def add_field_at_end(self, field_description: FieldDescription) -> "FieldConfiguration": return self.add_field_at_position(len(self.fields), field_description) def add_field_at_start(self, field_description: FieldDescription) -> "FieldConfiguration": return self.add_field_at_position(0, field_description) def add_field_at_position(self, position: int, field_description: FieldDescription) -> "FieldConfiguration": if self.has_field(field_description.name): raise ValueError(f"field already exists: {field_description.name}") position = max(0, min(position, len(self.fields))) self.fields.insert(position, field_description) return self def has_field(self, name: str) -> bool: return any(f.name == name for f in self.fields) def get_field_description(self, name: str) -> FieldDescription | None: return next((f for f in self.fields if f.name == name), None) def get_field_names(self) -> list[str]: return [f.name for f in self.fields] @dataclass class FieldContents: field_config: FieldConfiguration field_map: dict[str, Field] = field(default_factory=dict) def __post_init__(self) -> None: for field_description in self.field_config.fields: self.ensure_field(field_description) def ensure_field(self, field_description: FieldDescription) -> None: if field_description.name in self.field_map: return match field_description.type: case FieldType.INTEGER: value = 0 case FieldType.DOUBLE: value = 0.0 case FieldType.DATE: value = date.today() case FieldType.PASSWORD: value = b"" case _: value = "" self.field_map[field_description.name] = Field.with_content(value) def get_field_content_from_name(self, name: str) -> Field: try: return self.field_map[name] except KeyError as exc: raise KeyError(f"unknown field: {name}") from exc def set_field_content_for_name(self, name: str, value: Field | Any) -> bool: if name not in self.field_map: return False if not isinstance(value, Field): value = Field.with_content(value) expected = self.field_map[name].type if expected != value.type and expected != FieldType.PASSWORD: raise TypeError(f"field {name!r} expects {expected}, got {value.type}") self.field_map[name] = value return True def as_value_map(self, prefix: str) -> dict[str, str]: return {f"{prefix}::{name}": field.as_string() for name, field in self.field_map.items()}