Coverage for src/objectstate/global_config.py: 100%

34 statements  

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

1"""The global config: the value every resolution starts from. 

2 

3There is one global config per config type per process, read in two ways: live 

4(with unsaved edits) and saved. One thing holds it: 

5 

6- the state registered at the global scope (``scope_id=""``), when its object is 

7 of that type. The live global config is the object its parameters describe and 

8 the saved global config is the object it last saved. Editing, saving, restoring 

9 or time-travelling that state is editing, saving, restoring or time-travelling 

10 the global config; nothing else has to be told. 

11- otherwise the object given to ``set_global_config``. Nothing edits it, so its 

12 live and saved reads are the same object. A process that registers no state 

13 (a compile worker, an execution server, a script) uses only this. 

14 

15``set_global_config`` is the one way to replace the global config from outside. 

16While a state holds it, the call replaces that state's object 

17(``ObjectState.update_object_instance``), so the state, its descendants and their 

18subscribers all follow; a stored object the state would disagree with cannot be 

19created. A stored object is not read while a state holds the global config, and is 

20read again, unchanged, once that state is no longer registered. 

21 

22Threads play no part: the registry is one per process and so is the stored 

23object, so every thread reads the same global config. 

24""" 

25 

26from typing import Any 

27 

28from objectstate.scope_key import ScopeKey 

29 

30# What ``set_global_config`` stored while no state held the global config. 

31_stored_global_configs: dict[type, Any] = {} 

32 

33 

34_registry_class: Any = None 

35 

36 

37def _registry(): 

38 """``ObjectStateRegistry``, imported on first use (its module imports this one's users).""" 

39 global _registry_class 

40 if _registry_class is None: 

41 from objectstate.object_state_registry import ObjectStateRegistry 

42 

43 _registry_class = ObjectStateRegistry 

44 return _registry_class 

45 

46 

47def _state_holding(config_type: type): 

48 return _registry().global_config_state(config_type) 

49 

50 

51def set_global_config(config_type: type, config: Any) -> None: 

52 """Make ``config`` the global config of ``config_type``, live and saved. 

53 

54 With a state of that type registered at the global scope this replaces the 

55 state's object and its unsaved edits; without one it stores ``config``. Either 

56 way the registered states that inherit from it are told at once. 

57 """ 

58 if not isinstance(config_type, type): 

59 raise TypeError(f"Global config key must be a type, got {config_type!r}.") 

60 if not isinstance(config, config_type): 

61 raise TypeError( 

62 f"Global config value for {config_type.__name__} must be an " 

63 f"instance of that type, got {type(config).__name__}." 

64 ) 

65 state = _state_holding(config_type) 

66 if state is None: 

67 _stored_global_configs[config_type] = config 

68 _registry().resolve_again_below((ScopeKey(""),)) 

69 else: 

70 state.update_object_instance(config) 

71 

72 

73def forget_stored_global_config(config_type: type) -> None: 

74 """Store no global config of ``config_type`` any more (a session reset, a test). 

75 

76 This is the stored object only. A state that holds the global config is 

77 unregistered through the registry, not here. 

78 """ 

79 if _stored_global_configs.pop(config_type, None) is not None: 

80 _registry().resolve_again_below((ScopeKey(""),)) 

81 

82 

83def get_live_global_config(config_type: type) -> Any | None: 

84 """The global config with unsaved edits, or ``None`` when there is none.""" 

85 state = _state_holding(config_type) 

86 if state is None: 

87 return _stored_global_configs.get(config_type) 

88 return state.object_for_resolution() 

89 

90 

91def get_saved_global_config(config_type: type) -> Any | None: 

92 """The global config as last saved, or ``None`` when there is none.""" 

93 state = _state_holding(config_type) 

94 if state is None: 

95 return _stored_global_configs.get(config_type) 

96 return state.object_for_resolution(use_saved=True)