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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
1"""Typed UI visibility markers for configuration classes."""
3from __future__ import annotations
5import inspect
6from dataclasses import dataclass
7from typing import ClassVar, TypeVar
10T = TypeVar("T", bound=type)
13class UIVisibilityRegistry:
14 """Exact-class registry for config types hidden from UI form navigation."""
16 _hidden_types: ClassVar[set[type]] = set()
18 @classmethod
19 def register_hidden(cls, config_type: T) -> T:
20 cls._hidden_types.add(config_type)
21 return config_type
23 @classmethod
24 def is_hidden(cls, config_type: type) -> bool:
25 return config_type in cls._hidden_types
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)
34class UISpecialFieldRegistry:
35 """Exact-owner registry for fields rendered by custom UI editors."""
37 _fields_by_owner: ClassVar[dict[type, frozenset[str]]] = {}
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
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())
60@dataclass(frozen=True)
61class UIParameterVisibilityRequest:
62 """Visibility request for one parameter field."""
64 owner_type: type | None
65 field_name: str
66 field_type_candidate: object
67 field_metadata_hidden: bool = False
70class UIParameterVisibilityPolicy:
71 """Authority for deciding whether a parameter is omitted from generic UI forms."""
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 )
82def mark_ui_hidden_config(config_type: T) -> T:
83 """Register a config class as hidden from UI form/navigation surfaces."""
85 return UIVisibilityRegistry.register_hidden(config_type)
88def is_ui_hidden_config_type(config_type: type) -> bool:
89 """Return True when a config class is explicitly hidden from UI surfaces."""
91 return UIVisibilityRegistry.is_hidden(config_type)
94def is_ui_hidden_config_candidate(candidate: object) -> bool:
95 """Return True when an object is an explicitly hidden config class."""
97 return UIVisibilityRegistry.is_hidden_candidate(candidate)
100def mark_ui_special_fields(*field_names: str):
101 """Decorator registering fields handled by custom UI editors."""
103 def decorator(owner_type: T) -> T:
104 return UISpecialFieldRegistry.register(owner_type, field_names)
106 return decorator
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."""
112 return UISpecialFieldRegistry.has_special_editor(owner_type, field_name)
115def should_hide_ui_parameter(request: UIParameterVisibilityRequest) -> bool:
116 """Return True when a parameter should be omitted from generic UI forms."""
118 return UIParameterVisibilityPolicy.should_hide(request)