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

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 

6from pathlib import Path 

7from enum import Enum 

8 

9 

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 

22 

23 

24class FieldIntrospector: 

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

26 

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

32 

33 specs = [] 

34 for field in fields(dataclass_type): 

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

36 continue 

37 

38 spec = FieldIntrospector._analyze_field(field, instance) 

39 specs.append(spec) 

40 

41 return specs 

42 

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) 

50 

51 if is_optional: 

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

53 

54 # Get values 

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

56 default_value = None 

57 

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() 

62 

63 # Create label 

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

65 

66 # Check if this is a nested dataclass 

67 is_nested_dataclass = dataclasses.is_dataclass(actual_type) 

68 nested_fields = None 

69 

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) 

73 

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 )