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
« 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.
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:
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.
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.
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"""
26from typing import Any
28from objectstate.scope_key import ScopeKey
30# What ``set_global_config`` stored while no state held the global config.
31_stored_global_configs: dict[type, Any] = {}
34_registry_class: Any = None
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
43 _registry_class = ObjectStateRegistry
44 return _registry_class
47def _state_holding(config_type: type):
48 return _registry().global_config_state(config_type)
51def set_global_config(config_type: type, config: Any) -> None:
52 """Make ``config`` the global config of ``config_type``, live and saved.
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)
73def forget_stored_global_config(config_type: type) -> None:
74 """Store no global config of ``config_type`` any more (a session reset, a test).
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(""),))
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()
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)