Coverage for openhcs/textual_tui/services/config_reflection_service.py: 0.0%
47 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""Config reflection service - port from old TUI."""
3import dataclasses
4from dataclasses import dataclass, fields
5from typing import Any, List, Optional, get_origin, get_args, Union as TypingUnion
6from pathlib import Path
7from enum import Enum
10@dataclass
11class FieldSpec:
12 """Specification for a dataclass field."""
13 name: str
14 label: str
15 field_type: type
16 actual_type: type
17 current_value: Any
18 default_value: Any
19 is_optional: bool
20 is_nested_dataclass: bool = False
21 nested_fields: Optional[List['FieldSpec']] = None
24class FieldIntrospector:
25 """Analyzes dataclass fields for form generation."""
27 @staticmethod
28 def analyze_dataclass(dataclass_type: type, instance: Any) -> List[FieldSpec]:
29 """Analyze dataclass and return field specifications."""
30 if not dataclasses.is_dataclass(dataclass_type):
31 raise ValueError(f"{dataclass_type} is not a dataclass")
33 specs = []
34 for field in fields(dataclass_type):
35 if field.name.startswith('_'):
36 continue
38 spec = FieldIntrospector._analyze_field(field, instance)
39 specs.append(spec)
41 return specs
43 @staticmethod
44 def _analyze_field(field: dataclasses.Field, instance: Any) -> FieldSpec:
45 """Analyze individual field."""
46 # Extract type information
47 field_type = field.type
48 actual_type = field_type
49 is_optional = get_origin(field_type) is TypingUnion and type(None) in get_args(field_type)
51 if is_optional:
52 actual_type = next((t for t in get_args(field_type) if t is not type(None)), actual_type)
54 # Get values
55 current_value = getattr(instance, field.name, None)
56 default_value = None
58 if field.default is not dataclasses.MISSING:
59 default_value = field.default
60 elif field.default_factory is not dataclasses.MISSING:
61 default_value = field.default_factory()
63 # Create label
64 label = field.name.replace('_', ' ').title()
66 # Check if this is a nested dataclass
67 is_nested_dataclass = dataclasses.is_dataclass(actual_type)
68 nested_fields = None
70 if is_nested_dataclass and current_value is not None:
71 # Recursively analyze nested dataclass
72 nested_fields = FieldIntrospector.analyze_dataclass(actual_type, current_value)
74 return FieldSpec(
75 name=field.name,
76 label=label,
77 field_type=field_type,
78 actual_type=actual_type,
79 current_value=current_value,
80 default_value=default_value,
81 is_optional=is_optional,
82 is_nested_dataclass=is_nested_dataclass,
83 nested_fields=nested_fields
84 )