Coverage for openhcs/textual_tui/services/config_reflection_service.py: 0.0%

45 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-04 02:09 +0000

1"""Config reflection service - port from old TUI.""" 

2 

3import dataclasses 

4from dataclasses import dataclass, fields 

5from typing import Any, List, Optional, get_origin, get_args, Union as TypingUnion 

6 

7 

8@dataclass 

9class FieldSpec: 

10 """Specification for a dataclass field.""" 

11 name: str 

12 label: str 

13 field_type: type 

14 actual_type: type 

15 current_value: Any 

16 default_value: Any 

17 is_optional: bool 

18 is_nested_dataclass: bool = False 

19 nested_fields: Optional[List['FieldSpec']] = None 

20 

21 

22class FieldIntrospector: 

23 """Analyzes dataclass fields for form generation.""" 

24 

25 @staticmethod 

26 def analyze_dataclass(dataclass_type: type, instance: Any) -> List[FieldSpec]: 

27 """Analyze dataclass and return field specifications.""" 

28 if not dataclasses.is_dataclass(dataclass_type): 

29 raise ValueError(f"{dataclass_type} is not a dataclass") 

30 

31 specs = [] 

32 for field in fields(dataclass_type): 

33 if field.name.startswith('_'): 

34 continue 

35 

36 spec = FieldIntrospector._analyze_field(field, instance) 

37 specs.append(spec) 

38 

39 return specs 

40 

41 @staticmethod 

42 def _analyze_field(field: dataclasses.Field, instance: Any) -> FieldSpec: 

43 """Analyze individual field.""" 

44 # Extract type information 

45 field_type = field.type 

46 actual_type = field_type 

47 is_optional = get_origin(field_type) is TypingUnion and type(None) in get_args(field_type) 

48 

49 if is_optional: 

50 actual_type = next((t for t in get_args(field_type) if t is not type(None)), actual_type) 

51 

52 # Get values 

53 current_value = getattr(instance, field.name, None) 

54 default_value = None 

55 

56 if field.default is not dataclasses.MISSING: 

57 default_value = field.default 

58 elif field.default_factory is not dataclasses.MISSING: 

59 default_value = field.default_factory() 

60 

61 # Create label 

62 label = field.name.replace('_', ' ').title() 

63 

64 # Check if this is a nested dataclass 

65 is_nested_dataclass = dataclasses.is_dataclass(actual_type) 

66 nested_fields = None 

67 

68 if is_nested_dataclass and current_value is not None: 

69 # Recursively analyze nested dataclass 

70 nested_fields = FieldIntrospector.analyze_dataclass(actual_type, current_value) 

71 

72 return FieldSpec( 

73 name=field.name, 

74 label=label, 

75 field_type=field_type, 

76 actual_type=actual_type, 

77 current_value=current_value, 

78 default_value=default_value, 

79 is_optional=is_optional, 

80 is_nested_dataclass=is_nested_dataclass, 

81 nested_fields=nested_fields 

82 )