Coverage for src/objectstate/ui_visibility.py: 63%

60 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-17 07:22 +0000

1"""Typed UI visibility markers for configuration classes.""" 

2 

3from __future__ import annotations 

4 

5import inspect 

6from dataclasses import dataclass 

7from typing import ClassVar, TypeVar 

8 

9 

10T = TypeVar("T", bound=type) 

11 

12 

13class UIVisibilityRegistry: 

14 """Exact-class registry for config types hidden from UI form navigation.""" 

15 

16 _hidden_types: ClassVar[set[type]] = set() 

17 

18 @classmethod 

19 def register_hidden(cls, config_type: T) -> T: 

20 cls._hidden_types.add(config_type) 

21 return config_type 

22 

23 @classmethod 

24 def is_hidden(cls, config_type: type) -> bool: 

25 return config_type in cls._hidden_types 

26 

27 @classmethod 

28 def is_hidden_candidate(cls, candidate: object) -> bool: 

29 if not inspect.isclass(candidate): 

30 return False 

31 return cls.is_hidden(candidate) 

32 

33 

34class UISpecialFieldRegistry: 

35 """Exact-owner registry for fields rendered by custom UI editors.""" 

36 

37 _fields_by_owner: ClassVar[dict[type, frozenset[str]]] = {} 

38 

39 @classmethod 

40 def register(cls, owner_type: T, field_names: tuple[str, ...]) -> T: 

41 if not field_names: 

42 raise ValueError("UI special field registration requires at least one field") 

43 declared = frozenset(field_names) 

44 registered = cls._fields_by_owner.get(owner_type) 

45 if registered is not None and registered != declared: 

46 raise ValueError( 

47 f"{owner_type.__qualname__} already declares UI special fields " 

48 f"{sorted(registered)!r}; it cannot also declare {sorted(declared)!r}." 

49 ) 

50 cls._fields_by_owner[owner_type] = declared 

51 return owner_type 

52 

53 @classmethod 

54 def has_special_editor(cls, owner_type: type | None, field_name: str) -> bool: 

55 if owner_type is None: 

56 return False 

57 return field_name in cls._fields_by_owner.get(owner_type, frozenset()) 

58 

59 

60@dataclass(frozen=True) 

61class UIParameterVisibilityRequest: 

62 """Visibility request for one parameter field.""" 

63 

64 owner_type: type | None 

65 field_name: str 

66 field_type_candidate: object 

67 field_metadata_hidden: bool = False 

68 

69 

70class UIParameterVisibilityPolicy: 

71 """Authority for deciding whether a parameter is omitted from generic UI forms.""" 

72 

73 @classmethod 

74 def should_hide(cls, request: UIParameterVisibilityRequest) -> bool: 

75 return ( 

76 UISpecialFieldRegistry.has_special_editor(request.owner_type, request.field_name) 

77 or request.field_metadata_hidden 

78 or UIVisibilityRegistry.is_hidden_candidate(request.field_type_candidate) 

79 ) 

80 

81 

82def mark_ui_hidden_config(config_type: T) -> T: 

83 """Register a config class as hidden from UI form/navigation surfaces.""" 

84 

85 return UIVisibilityRegistry.register_hidden(config_type) 

86 

87 

88def is_ui_hidden_config_type(config_type: type) -> bool: 

89 """Return True when a config class is explicitly hidden from UI surfaces.""" 

90 

91 return UIVisibilityRegistry.is_hidden(config_type) 

92 

93 

94def is_ui_hidden_config_candidate(candidate: object) -> bool: 

95 """Return True when an object is an explicitly hidden config class.""" 

96 

97 return UIVisibilityRegistry.is_hidden_candidate(candidate) 

98 

99 

100def mark_ui_special_fields(*field_names: str): 

101 """Decorator registering fields handled by custom UI editors.""" 

102 

103 def decorator(owner_type: T) -> T: 

104 return UISpecialFieldRegistry.register(owner_type, field_names) 

105 

106 return decorator 

107 

108 

109def has_ui_special_field(owner_type: type | None, field_name: str) -> bool: 

110 """Return True when a field is owned by a custom UI editor.""" 

111 

112 return UISpecialFieldRegistry.has_special_editor(owner_type, field_name) 

113 

114 

115def should_hide_ui_parameter(request: UIParameterVisibilityRequest) -> bool: 

116 """Return True when a parameter should be omitted from generic UI forms.""" 

117 

118 return UIParameterVisibilityPolicy.should_hide(request)