objectstate 3.0.4__tar.gz → 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {objectstate-3.0.4 → objectstate-4.0.0}/.github/badges/coverage.svg +1 -1
  2. objectstate-4.0.0/CHANGELOG.md +68 -0
  3. {objectstate-3.0.4 → objectstate-4.0.0}/PKG-INFO +6 -6
  4. {objectstate-3.0.4 → objectstate-4.0.0}/README.md +5 -5
  5. {objectstate-3.0.4 → objectstate-4.0.0}/docs/api/modules.rst +1 -1
  6. {objectstate-3.0.4 → objectstate-4.0.0}/docs/architecture.rst +19 -20
  7. {objectstate-3.0.4 → objectstate-4.0.0}/docs/examples/basic.rst +2 -2
  8. {objectstate-3.0.4 → objectstate-4.0.0}/docs/index.rst +1 -1
  9. {objectstate-3.0.4 → objectstate-4.0.0}/docs/quickstart.rst +4 -4
  10. {objectstate-3.0.4 → objectstate-4.0.0}/docs/state_propagation.rst +40 -19
  11. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/__init__.py +9 -13
  12. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/context_manager.py +11 -23
  13. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/docs/configuration_framework.rst +9 -19
  14. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/docs/context_system.rst +11 -12
  15. objectstate-4.0.0/src/objectstate/global_config.py +96 -0
  16. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/lazy_factory.py +0 -59
  17. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/object_state.py +10 -109
  18. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/object_state_registry.py +21 -26
  19. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/resolution_sources.py +9 -27
  20. {objectstate-3.0.4 → objectstate-4.0.0}/tests/conftest.py +3 -6
  21. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_callable_baseline.py +2 -2
  22. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_construction_history.py +3 -3
  23. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_context_failures_surface.py +3 -3
  24. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_context_manager.py +0 -10
  25. objectstate-4.0.0/tests/test_global_config.py +37 -0
  26. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_object_state_restore.py +6 -6
  27. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_object_state_update_instance.py +2 -2
  28. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_one_field_resolution.py +19 -15
  29. objectstate-4.0.0/tests/test_one_holder_of_the_global_config.py +389 -0
  30. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_resolution_precedence_order.py +3 -3
  31. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_resolved_values_follow_registered_ancestors.py +76 -129
  32. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_scopeless_state_resolution.py +2 -2
  33. objectstate-3.0.4/src/objectstate/global_config.py +0 -174
  34. objectstate-3.0.4/tests/test_global_config.py +0 -68
  35. {objectstate-3.0.4 → objectstate-4.0.0}/.github/workflows/ci.yml +0 -0
  36. {objectstate-3.0.4 → objectstate-4.0.0}/.github/workflows/coverage-pages.yml +0 -0
  37. {objectstate-3.0.4 → objectstate-4.0.0}/.github/workflows/publish.yml +0 -0
  38. {objectstate-3.0.4 → objectstate-4.0.0}/.gitignore +0 -0
  39. {objectstate-3.0.4 → objectstate-4.0.0}/.readthedocs.yaml +0 -0
  40. {objectstate-3.0.4 → objectstate-4.0.0}/BADGE_SETUP.md +0 -0
  41. {objectstate-3.0.4 → objectstate-4.0.0}/LICENSE +0 -0
  42. {objectstate-3.0.4 → objectstate-4.0.0}/docs/.gitignore +0 -0
  43. {objectstate-3.0.4 → objectstate-4.0.0}/docs/Makefile +0 -0
  44. {objectstate-3.0.4 → objectstate-4.0.0}/docs/architectural_fixes.rst +0 -0
  45. {objectstate-3.0.4 → objectstate-4.0.0}/docs/archive/DOCUMENTATION_AUDIT.md +0 -0
  46. {objectstate-3.0.4 → objectstate-4.0.0}/docs/archive/DOCUMENTATION_FIXES.md +0 -0
  47. {objectstate-3.0.4 → objectstate-4.0.0}/docs/archive/README.md +0 -0
  48. {objectstate-3.0.4 → objectstate-4.0.0}/docs/conf.py +0 -0
  49. {objectstate-3.0.4 → objectstate-4.0.0}/docs/examples/atomic_operations.rst +0 -0
  50. {objectstate-3.0.4 → objectstate-4.0.0}/docs/examples/dual_axis.rst +0 -0
  51. {objectstate-3.0.4 → objectstate-4.0.0}/docs/examples/index.rst +0 -0
  52. {objectstate-3.0.4 → objectstate-4.0.0}/docs/examples/ui.rst +0 -0
  53. {objectstate-3.0.4 → objectstate-4.0.0}/docs/make.bat +0 -0
  54. {objectstate-3.0.4 → objectstate-4.0.0}/docs/performance/canonical-context-type-resolution-20261005.json +0 -0
  55. {objectstate-3.0.4 → objectstate-4.0.0}/docs/provenance.rst +0 -0
  56. {objectstate-3.0.4 → objectstate-4.0.0}/docs/state_management.rst +0 -0
  57. {objectstate-3.0.4 → objectstate-4.0.0}/docs/undo_redo.rst +0 -0
  58. {objectstate-3.0.4 → objectstate-4.0.0}/paper.bib +0 -0
  59. {objectstate-3.0.4 → objectstate-4.0.0}/paper.md +0 -0
  60. {objectstate-3.0.4 → objectstate-4.0.0}/plans/n_dimensional_inheritance.md +0 -0
  61. {objectstate-3.0.4 → objectstate-4.0.0}/pyproject.toml +0 -0
  62. {objectstate-3.0.4 → objectstate-4.0.0}/scripts/check_ruff_added_lines.py +0 -0
  63. {objectstate-3.0.4 → objectstate-4.0.0}/scripts/release.py +0 -0
  64. {objectstate-3.0.4 → objectstate-4.0.0}/scripts/update_and_release.py +0 -0
  65. {objectstate-3.0.4 → objectstate-4.0.0}/scripts/update_version.py +0 -0
  66. {objectstate-3.0.4 → objectstate-4.0.0}/scripts/verify_release_ready.py +0 -0
  67. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/codegen.py +0 -0
  68. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/collection_containers.py +0 -0
  69. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/config.py +0 -0
  70. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/construction_binding.py +0 -0
  71. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/docs/object_state_lifecycle.rst +0 -0
  72. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/dual_axis_resolver.py +0 -0
  73. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/edit_session.py +0 -0
  74. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/field_access.py +0 -0
  75. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/inspection.py +0 -0
  76. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/live_context_resolver.py +0 -0
  77. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/object_state_metadata.py +0 -0
  78. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/parameter_owner.py +0 -0
  79. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/parameter_structure.py +0 -0
  80. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/parametric_axes.py +0 -0
  81. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/placeholder.py +0 -0
  82. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/reified_generics.py +0 -0
  83. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/scope_key.py +0 -0
  84. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/snapshot_model.py +0 -0
  85. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/subfield_semantics.py +0 -0
  86. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/time_travel_profile.py +0 -0
  87. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/token_cache.py +0 -0
  88. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/transaction_checkpoint.py +0 -0
  89. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/ui_visibility.py +0 -0
  90. {objectstate-3.0.4 → objectstate-4.0.0}/src/objectstate/value_semantics.py +0 -0
  91. {objectstate-3.0.4 → objectstate-4.0.0}/tests/__init__.py +0 -0
  92. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_atomic_block_context.py +0 -0
  93. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_authored_paths.py +0 -0
  94. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_codegen.py +0 -0
  95. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_config.py +0 -0
  96. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_field_access.py +0 -0
  97. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_history_persistence.py +0 -0
  98. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_inspection.py +0 -0
  99. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_integration.py +0 -0
  100. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_lazy_counterpart_registry.py +0 -0
  101. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_lazy_factory.py +0 -0
  102. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_live_context_resolver_answers.py +0 -0
  103. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_object_state_metadata_contracts.py +0 -0
  104. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_parameter_owner.py +0 -0
  105. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_parametric_axes.py +0 -0
  106. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_placeholder.py +0 -0
  107. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_registry_subscription.py +0 -0
  108. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_reified_generics.py +0 -0
  109. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_release_readiness.py +0 -0
  110. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_resolved_change_notifications.py +0 -0
  111. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_scope_key.py +0 -0
  112. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_subfield_semantics.py +0 -0
  113. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_timeline_projection.py +0 -0
  114. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_transaction_checkpoint.py +0 -0
  115. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_ui_visible_field_projection.py +0 -0
  116. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_undo_redo_cascade.py +0 -0
  117. {objectstate-3.0.4 → objectstate-4.0.0}/tests/test_value_semantics.py +0 -0
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 82.63%"><title>coverage: 82.63%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#67ac09"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="865" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">82.63%</text><text x="865" y="150" fill-opacity=".3" textLength="430">82.63%</text></g><text x="865" y="140" textLength="430">82.63%</text></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 82.78%"><title>coverage: 82.78%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#67ac09"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="865" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">82.78%</text><text x="865" y="150" fill-opacity=".3" textLength="430">82.78%</text></g><text x="865" y="140" textLength="430">82.78%</text></g></g></svg>
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ ## 4.0.0
4
+
5
+ The global config has one holder (#56). While a state whose object is of the
6
+ global config type is registered at the global scope (`scope_id=""`), that state
7
+ holds the global config: the live global config is the object its parameters
8
+ describe and the saved one is the object it last saved. While no such state is
9
+ registered, the object given to `set_global_config` is both. The global config is
10
+ one value per process; threads no longer each have their own.
11
+
12
+ Removed, and what to call instead:
13
+
14
+ - `ensure_global_config_context(config_type, config)` (also
15
+ `objectstate.lazy_factory.ensure_global_config_context`): call
16
+ `set_global_config(config_type, config)`. Not needed at all beside a registered
17
+ global state: registering the state makes its object the global config.
18
+ - `set_global_config_for_editing(config_type, config)`: call
19
+ `set_global_config(config_type, config)`.
20
+ - `set_live_global_config(config_type, config)`: with a registered global state,
21
+ edit the state (`state.update_parameter(path, value)`); the live global config
22
+ follows. Without one there is no separate live value; call `set_global_config`.
23
+ - `set_saved_global_config(config_type, config)`: with a registered global state,
24
+ save it (`state.mark_saved()`); to replace the object, live and saved, call
25
+ `state.update_object_instance(config)` or `set_global_config`. Without one, call
26
+ `set_global_config`.
27
+ - `get_current_global_config(config_type, use_live=...)`: call
28
+ `get_live_global_config(config_type)` or `get_saved_global_config(config_type)`.
29
+ - `clear_current_temp_global()`: nothing; no library code leaves the caller's
30
+ entered context any more. Leave a `config_context` block to leave its context.
31
+ - `objectstate.global_config.GlobalContextValues`: with a registered global state,
32
+ checkpoint the state (`ObjectStateTransactionCheckpoint.capture(state)` /
33
+ `.restore(state)`). Without one, keep `get_live_global_config(config_type)` and
34
+ put it back with `set_global_config`, or `forget_stored_global_config` when it
35
+ was `None`.
36
+ - `ObjectState.prepare_history_global_context()`: nothing; read
37
+ `get_live_global_config` / `get_saved_global_config`.
38
+ - `objectstate.lazy_factory.rebuild_lazy_config_with_new_global_reference(...)`:
39
+ nothing (it had no caller; it copied a lazy config and stored a global config as
40
+ a side effect).
41
+ - `ObjectStateRegistry._resolve_again_below` is now `resolve_again_below`.
42
+
43
+ Added: `set_global_config`, `forget_stored_global_config(config_type)` (a session
44
+ reset or a test that must leave nothing stored), `ObjectStateRegistry.global_config_state(config_type)`;
45
+ `get_live_global_config` and `get_saved_global_config` are exported from
46
+ `objectstate`.
47
+
48
+ Behaviour changes:
49
+
50
+ - `set_global_config` while a global state is registered replaces that state's
51
+ object (`update_object_instance`): unsaved edits of the global state are dropped
52
+ and its descendants are told at once. A call that only repeated what the state
53
+ already held must be deleted, not kept.
54
+ - `set_global_config` without a global state tells registered states at once (the
55
+ setters used to tell nobody until the next read).
56
+ - Registering a global state makes its object the global config at once, also when
57
+ nothing was stored before. Unregistering it makes the global config what
58
+ `set_global_config` last stored while no state held it (or the declared defaults).
59
+ - Every thread reads the same global config. A thread that used to see the declared
60
+ defaults because nothing was stored in it now sees the process's global config.
61
+ - Editing, replacing, restoring or time-travelling the global state no longer
62
+ leaves the caller's entered `config_context`: a block entered before the change
63
+ keeps the layers it was entered with until it is left.
64
+ - A global state that is not registered no longer changes the global config when it
65
+ is edited, saved, restored or given another object.
66
+ - `get_live_global_config` returns the object the registered global state builds
67
+ from its parameters (one object until the next change), not the instance the
68
+ application first passed in. Compare values, not identity.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: objectstate
3
- Version: 3.0.4
3
+ Version: 4.0.0
4
4
  Summary: Generic lazy dataclass configuration framework with dual-axis inheritance and contextvars-based resolution
5
5
  Project-URL: Homepage, https://github.com/OpenHCSDev/objectstate
6
6
  Project-URL: Documentation, https://objectstate.readthedocs.io
@@ -53,7 +53,7 @@ Description-Content-Type: text/markdown
53
53
  - Y-Axis: Sibling inheritance within same context
54
54
  - **Contextvars-Based**: Uses Python's `contextvars` for clean context management
55
55
  - **UI Integration**: Placeholder text generation for configuration forms
56
- - **Thread-Safe**: Thread-local global configuration storage
56
+ - **One global config**: held by the registered global state, or stored where no state exists
57
57
  - **100% Generic**: No application-specific dependencies
58
58
  - **Small dependency surface**: Uses ``python-introspect`` for callable and
59
59
  dataclass analysis
@@ -112,7 +112,7 @@ resolution state.
112
112
  When using the decorator pattern with `auto_create_decorator`, you need to establish the global configuration context for lazy resolution:
113
113
 
114
114
  ```python
115
- from objectstate import ensure_global_config_context
115
+ from objectstate import set_global_config
116
116
 
117
117
  # After creating your global config instance
118
118
  global_config = GlobalPipelineConfig(
@@ -121,15 +121,15 @@ global_config = GlobalPipelineConfig(
121
121
  )
122
122
 
123
123
  # REQUIRED: Establish global config context for lazy resolution
124
- ensure_global_config_context(GlobalPipelineConfig, global_config)
124
+ set_global_config(GlobalPipelineConfig, global_config)
125
125
 
126
126
  # Now lazy configs can resolve from the global context
127
127
  ```
128
128
 
129
129
  **Key differences:**
130
130
  - `set_base_config_type(MyConfig)`: Sets the **type** (class) for the framework
131
- - `ensure_global_config_context(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution
132
- - Call `ensure_global_config_context()` at application startup (GUI) or before pipeline execution
131
+ - `set_global_config(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution
132
+ - Call `set_global_config()` at application startup (GUI) or before pipeline execution
133
133
 
134
134
  ## Installation
135
135
 
@@ -16,7 +16,7 @@
16
16
  - Y-Axis: Sibling inheritance within same context
17
17
  - **Contextvars-Based**: Uses Python's `contextvars` for clean context management
18
18
  - **UI Integration**: Placeholder text generation for configuration forms
19
- - **Thread-Safe**: Thread-local global configuration storage
19
+ - **One global config**: held by the registered global state, or stored where no state exists
20
20
  - **100% Generic**: No application-specific dependencies
21
21
  - **Small dependency surface**: Uses ``python-introspect`` for callable and
22
22
  dataclass analysis
@@ -75,7 +75,7 @@ resolution state.
75
75
  When using the decorator pattern with `auto_create_decorator`, you need to establish the global configuration context for lazy resolution:
76
76
 
77
77
  ```python
78
- from objectstate import ensure_global_config_context
78
+ from objectstate import set_global_config
79
79
 
80
80
  # After creating your global config instance
81
81
  global_config = GlobalPipelineConfig(
@@ -84,15 +84,15 @@ global_config = GlobalPipelineConfig(
84
84
  )
85
85
 
86
86
  # REQUIRED: Establish global config context for lazy resolution
87
- ensure_global_config_context(GlobalPipelineConfig, global_config)
87
+ set_global_config(GlobalPipelineConfig, global_config)
88
88
 
89
89
  # Now lazy configs can resolve from the global context
90
90
  ```
91
91
 
92
92
  **Key differences:**
93
93
  - `set_base_config_type(MyConfig)`: Sets the **type** (class) for the framework
94
- - `ensure_global_config_context(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution
95
- - Call `ensure_global_config_context()` at application startup (GUI) or before pipeline execution
94
+ - `set_global_config(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution
95
+ - Call `set_global_config()` at application startup (GUI) or before pipeline execution
96
96
 
97
97
  ## Installation
98
98
 
@@ -12,7 +12,7 @@ Configuration and lazy types
12
12
  Builds an application-specific configuration decorator and related lazy
13
13
  types.
14
14
 
15
- ``config_context`` and ``ensure_global_config_context``
15
+ ``config_context`` and ``set_global_config``
16
16
  Install concrete configuration used during resolution.
17
17
 
18
18
  ``set_base_config_type``
@@ -487,8 +487,8 @@ How It Works
487
487
  Global Configuration Context
488
488
  -----------------------------
489
489
 
490
- Understanding set_base_config_type vs ensure_global_config_context
491
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
490
+ Understanding set_base_config_type vs set_global_config
491
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
492
492
 
493
493
  The framework provides two related but distinct functions:
494
494
 
@@ -507,29 +507,27 @@ Sets the **type** (class) for the framework:
507
507
  * Required for type validation and registry
508
508
  * Does not set any concrete values
509
509
 
510
- **ensure_global_config_context(config_type: Type, instance: Any)**
510
+ **set_global_config(config_type: Type, instance: Any)**
511
511
 
512
512
  Sets the **instance** (concrete values) for resolution:
513
513
 
514
514
  .. code-block:: python
515
515
 
516
- from objectstate import ensure_global_config_context
516
+ from objectstate import set_global_config
517
517
 
518
518
  global_config = GlobalPipelineConfig(
519
519
  num_workers=8,
520
520
  output_dir="/data"
521
521
  )
522
522
 
523
- ensure_global_config_context(GlobalPipelineConfig, global_config)
523
+ set_global_config(GlobalPipelineConfig, global_config)
524
524
 
525
- * Call after creating global config instance
526
- * Uses thread-local storage for thread safety
527
- * Required for lazy resolution to work
528
- * Internally calls ``set_global_config_for_editing()``
529
- * Should be called at application startup (GUI) or before pipeline execution
530
- * Not needed after an edit or a save made through the ``ObjectState`` of the global
531
- config: that state stores the live and the saved instance itself
532
- (see :doc:`state_propagation`)
525
+ * Call after creating global config instance, in a process that registers no
526
+ ``ObjectState`` for it (a script, a compile worker, an execution server)
527
+ * One value per process: every thread reads it
528
+ * Not needed when an ``ObjectState`` of the global config is registered at scope
529
+ ``""``: that state's object is the global config, live and saved
530
+ (see :doc:`state_propagation`). Calling it then replaces that state's object.
533
531
 
534
532
  **Key Differences:**
535
533
 
@@ -539,7 +537,7 @@ Sets the **instance** (concrete values) for resolution:
539
537
 
540
538
  * - Aspect
541
539
  - set_base_config_type
542
- - ensure_global_config_context
540
+ - set_global_config
543
541
  * - What it sets
544
542
  - Type (class)
545
543
  - Instance (concrete values)
@@ -549,17 +547,18 @@ Sets the **instance** (concrete values) for resolution:
549
547
  * - Purpose
550
548
  - Type registration
551
549
  - Enable lazy resolution
552
- * - Thread safety
553
- - Global registry
554
- - Thread-local storage
550
+ * - Scope
551
+ - One per process
552
+ - One per process
555
553
 
556
554
  Thread Safety
557
555
  -------------
558
556
 
559
- The framework is fully thread-safe through:
557
+ Threads see:
560
558
 
561
- 1. **Thread-local storage** for global configs
559
+ 1. **One global config per process**, read by every thread
562
560
  2. **ContextVars** for temporary contexts
563
561
  3. **Immutable frozen dataclasses** (when using ``frozen=True``)
564
562
 
565
- This makes it safe to use in multi-threaded applications, including web servers and async applications.
563
+ Entered contexts are per thread or task. The global config and the registry are shared
564
+ and not synchronised: keep edits in one thread.
@@ -174,7 +174,7 @@ Using the decorator pattern for automatic field injection and lazy class generat
174
174
  .. code-block:: python
175
175
 
176
176
  from dataclasses import dataclass
177
- from objectstate import auto_create_decorator, ensure_global_config_context
177
+ from objectstate import auto_create_decorator, set_global_config
178
178
 
179
179
  # Create global config with auto_create_decorator
180
180
  @auto_create_decorator
@@ -213,7 +213,7 @@ Using the decorator pattern for automatic field injection and lazy class generat
213
213
  )
214
214
 
215
215
  # REQUIRED: Establish global context for lazy resolution
216
- ensure_global_config_context(GlobalPipelineConfig, global_config)
216
+ set_global_config(GlobalPipelineConfig, global_config)
217
217
 
218
218
  # Now you can access injected configs
219
219
  print(f"Database: {global_config.database_config.host}:{global_config.database_config.port}")
@@ -19,7 +19,7 @@ Key Features
19
19
 
20
20
  * **Contextvars-Based**: Uses Python's ``contextvars`` for clean context management
21
21
  * **UI Integration**: Placeholder text generation for configuration forms
22
- * **Thread-Safe**: Thread-local global configuration storage
22
+ * **One global config**: held by the registered global state, or stored where no state exists
23
23
  * **100% Generic**: No application-specific dependencies
24
24
  * **Small dependency surface**: Uses ``python-introspect`` for signature and
25
25
  dataclass analysis
@@ -174,7 +174,7 @@ When using the decorator pattern with ``auto_create_decorator``, you need to est
174
174
 
175
175
  from objectstate import (
176
176
  auto_create_decorator,
177
- ensure_global_config_context,
177
+ set_global_config,
178
178
  )
179
179
  from dataclasses import dataclass
180
180
 
@@ -192,7 +192,7 @@ When using the decorator pattern with ``auto_create_decorator``, you need to est
192
192
  )
193
193
 
194
194
  # REQUIRED: Establish global config context
195
- ensure_global_config_context(GlobalPipelineConfig, global_config)
195
+ set_global_config(GlobalPipelineConfig, global_config)
196
196
 
197
197
  # Now lazy configs can resolve from the global context
198
198
 
@@ -200,8 +200,8 @@ Understanding the Difference
200
200
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
201
201
 
202
202
  * ``set_base_config_type(MyConfig)``: Sets the **type** (class) for the framework
203
- * ``ensure_global_config_context(GlobalConfig, instance)``: Sets the **instance** (concrete values) for resolution
204
- * Call ``ensure_global_config_context()`` at application startup (GUI) or before pipeline execution
203
+ * ``set_global_config(GlobalConfig, instance)``: Sets the **instance** (concrete values) for resolution
204
+ * Call ``set_global_config()`` at application startup (GUI) or before pipeline execution
205
205
 
206
206
  Complete Example
207
207
  ----------------
@@ -132,7 +132,7 @@ registered state than before, every field is.
132
132
 
133
133
  So no caller announces a change. Registering, unregistering or replacing an ancestor,
134
134
  editing, saving, restoring, undoing or redoing an ancestor's value, and replacing the
135
- global config with ``set_live_global_config`` / ``set_saved_global_config`` all reach
135
+ global config with ``set_global_config`` all reach
136
136
  a state on its next read, whether the state is registered or not. A registered state
137
137
  is also told at once, so its subscribers hear of the change without a read; a state
138
138
  that is not registered is referenced by nothing and tells its subscribers on its next
@@ -144,26 +144,47 @@ Limits:
144
144
  ``ObjectState.to_object`` returns the same function object for a state that wraps a
145
145
  plain function, so states registered below a function-backed state follow its edits
146
146
  only through the registry (that is, when they are registered themselves).
147
- - The global config is stored per thread. A snapshot follows the global config of the
148
- thread that resolved it; a read from another thread answers from the snapshot and
149
- does not resolve it again for that thread's global config.
147
+ - Nothing is synchronised. Every thread reads the same registry and the same global
148
+ config, so a thread that edits while another reads must be kept apart by the
149
+ application (in a GUI: edit in the GUI thread).
150
150
 
151
151
  The state of the global config
152
152
  ------------------------------
153
153
 
154
- The global config is stored per thread, once as edited (live) and once as saved. A
155
- state whose object is a global config writes that store when its own object changes:
156
- ``update_parameter`` applies the edit to the live global config,
157
- ``mark_saved`` stores the saved object as the saved global config, ``restore_saved``
158
- stores the saved object as the live one again, ``update_object_instance`` and time
159
- travel store both. Each is stored before any descendant resolves again, so saving the
160
- global state is one call: its registered descendants resolve their saved values
161
- against the object just saved and are told at once.
162
-
163
- ``set_live_global_config``, ``set_saved_global_config`` and
164
- ``ensure_global_config_context`` (both at once) store a global config where no state
165
- does it: at startup before a state exists, and in a thread or process that resolves
166
- plain lazy objects without a registry, such as a compile worker. Calling one with the
167
- values the global state has just stored, before or after ``mark_saved``, changes
168
- nothing. A setter tells no state; states find the replaced object on their next read.
154
+ The global config is the value every resolution starts from. There is one per config
155
+ type per process, read as edited (live) and as saved, and one thing holds it
156
+ (``objectstate.global_config``):
157
+
158
+ - The state registered at the global scope (``scope_id=""``) whose object is of that
159
+ type. The live global config is the object its parameters describe
160
+ (``object_for_resolution()``) and the saved global config is the object it last
161
+ saved. Nothing copies them anywhere: ``get_live_global_config``,
162
+ ``get_saved_global_config``, ``config_context`` and every state's resolution ask the
163
+ registered state. So an edit, a save, a restore, a replaced object, an undo or a
164
+ redo of that state is that change of the global config, and its registered
165
+ descendants are told at once.
166
+ - While no such state is registered: the object given to ``set_global_config``. Nothing
167
+ edits it, so the live and the saved read are the same object. A process without
168
+ states (a compile worker, an execution server, a script) uses only this.
169
+
170
+ Registering the global state is all it takes to make its object the global config; no
171
+ ``set_global_config`` call is needed beside it. Unregistering it (or travelling to a
172
+ point in history where it was not registered) ends that at once: the global config is
173
+ then again what ``set_global_config`` last stored while no state held it, or the
174
+ declared defaults.
175
+
176
+ ``set_global_config(config_type, config)`` is the one way to replace the global config
177
+ from outside. While a state holds the global config the call is
178
+ ``state.update_object_instance(config)``: the state takes the new object as its live
179
+ and saved value (unsaved edits are dropped) and its descendants and their subscribers
180
+ follow. A stored object that disagrees with a registered global state cannot be made.
181
+
182
+ Threads play no part. The registry is one per process and so is the stored object, so
183
+ a worker thread reads the global config the GUI thread edits, with or without
184
+ ``spawn_thread_with_context`` (which copies the entered ``config_context`` layers, not
185
+ the global config, into the new thread).
186
+
187
+ A ``config_context`` block entered before the global config changes keeps the layers
188
+ it was entered with until it is left.
189
+
169
190
 
@@ -9,7 +9,7 @@ Key Features:
9
9
  - Dual-axis inheritance (context hierarchy + sibling inheritance)
10
10
  - Contextvars-based context management
11
11
  - Placeholder text generation for UI
12
- - Thread-local global configuration storage
12
+ - One global config per process, held by the registered global state
13
13
 
14
14
  Quick Start:
15
15
  >>> from objectstate import (
@@ -47,7 +47,7 @@ Modules:
47
47
  - dual_axis_resolver: Dual-axis inheritance resolver
48
48
  - context_manager: Contextvars-based context management
49
49
  - placeholder: Placeholder text generation for UI
50
- - global_config: Thread-local global configuration storage
50
+ - global_config: The global config and the one thing that holds it
51
51
  - config: Framework configuration (pluggable types and behaviors)
52
52
  """
53
53
 
@@ -58,7 +58,6 @@ from objectstate.lazy_factory import (
58
58
  register_lazy_type_mapping,
59
59
  get_base_type_for_lazy,
60
60
  get_lazy_type_for_base,
61
- ensure_global_config_context,
62
61
  # Constructor patching for code execution
63
62
  patch_lazy_constructors,
64
63
  # Global config type checking
@@ -86,7 +85,6 @@ from objectstate.dual_axis_resolver import (
86
85
  from objectstate.context_manager import (
87
86
  config_context,
88
87
  get_current_temp_global,
89
- clear_current_temp_global,
90
88
  merge_configs,
91
89
  extract_all_configs,
92
90
  get_base_global_config,
@@ -111,10 +109,8 @@ from objectstate.placeholder import LazyDefaultPlaceholderService
111
109
 
112
110
  # Global config
113
111
  from objectstate.global_config import (
114
- get_current_global_config,
115
- set_global_config_for_editing,
116
- set_saved_global_config,
117
- set_live_global_config,
112
+ set_global_config,
113
+ forget_stored_global_config,
118
114
  get_saved_global_config,
119
115
  get_live_global_config,
120
116
  )
@@ -155,7 +151,6 @@ __all__ = [
155
151
  'register_lazy_type_mapping',
156
152
  'get_base_type_for_lazy',
157
153
  'get_lazy_type_for_base',
158
- 'ensure_global_config_context',
159
154
  # Constructor patching for code execution
160
155
  'patch_lazy_constructors',
161
156
  # Global config type checking
@@ -172,7 +167,6 @@ __all__ = [
172
167
  # Context
173
168
  'config_context',
174
169
  'get_current_temp_global',
175
- 'clear_current_temp_global',
176
170
  'merge_configs',
177
171
  'extract_all_configs',
178
172
  'get_base_global_config',
@@ -182,8 +176,10 @@ __all__ = [
182
176
  # Placeholder
183
177
  'LazyDefaultPlaceholderService',
184
178
  # Global config
185
- 'get_current_global_config',
186
- 'set_global_config_for_editing',
179
+ 'set_global_config',
180
+ 'forget_stored_global_config',
181
+ 'get_live_global_config',
182
+ 'get_saved_global_config',
187
183
  # Configuration
188
184
  'set_base_config_type',
189
185
  'get_base_config_type',
@@ -210,7 +206,7 @@ __all__ = [
210
206
  'ObjectStateEditSession',
211
207
  ]
212
208
 
213
- __version__ = "3.0.4"
209
+ __version__ = "4.0.0"
214
210
  __author__ = 'OpenHCS Team'
215
211
  __description__ = 'Generic configuration framework for lazy dataclass resolution'
216
212
 
@@ -182,7 +182,7 @@ def config_context(
182
182
  mask_with_none: If True, None values override/mask base config values, and
183
183
  field resolution does not look at any layer outside this one.
184
184
  If False (default), None values are ignored (normal inheritance).
185
- Use True when editing GlobalPipelineConfig to mask thread-local
185
+ Use True when editing GlobalPipelineConfig to mask the
186
186
  loaded instance with static class defaults.
187
187
  use_live_global: If True (default), use LIVE global config (UI sees unsaved edits).
188
188
  If False, use SAVED global config (compiler sees saved values).
@@ -665,20 +665,21 @@ def get_base_global_config(use_live: bool = True):
665
665
  """
666
666
  Get the global config a context starts from when none is active.
667
667
 
668
- This is the config set with ensure_global_config_context(), or an instance of the
669
- base config type holding its declared defaults when none was set. Raises when no
670
- base config type is set or its defaults cannot be constructed.
668
+ This is the global config of the base config type (see ``objectstate.global_config``),
669
+ or an instance of that type holding its declared defaults when there is none.
670
+ Raises when no base config type is set or its defaults cannot be constructed.
671
671
 
672
672
  Args:
673
673
  use_live: If True (default), return LIVE config (UI sees unsaved edits).
674
674
  If False, return SAVED config (compiler sees saved values).
675
675
  """
676
676
  from objectstate.config import get_base_config_type
677
- from objectstate.global_config import get_current_global_config
677
+ from objectstate.global_config import get_live_global_config, get_saved_global_config
678
678
 
679
679
  base_config_type = get_base_config_type()
680
680
 
681
- current_global = get_current_global_config(base_config_type, use_live=use_live)
681
+ read = get_live_global_config if use_live else get_saved_global_config
682
+ current_global = read(base_config_type)
682
683
  if current_global is not None:
683
684
  return current_global
684
685
 
@@ -698,17 +699,6 @@ def get_current_temp_global():
698
699
  return current_temp_global.get(None)
699
700
 
700
701
 
701
- def clear_current_temp_global():
702
- """
703
- Leave the active context without restoring an outer one.
704
-
705
- After this no context is active: lazy fields fall back to their declared
706
- defaults until the next ``config_context`` is entered.
707
- """
708
- current_temp_global.set(None)
709
- context_layer_stack.set(NO_LAYERS)
710
-
711
-
712
702
  # Utility functions for debugging and introspection
713
703
 
714
704
  def get_context_info() -> Dict[str, Any]:
@@ -899,12 +889,10 @@ def spawn_thread_with_context(
899
889
  ):
900
890
  """Spawn a thread with current context propagated.
901
891
 
902
- This ensures thread-local context (like GlobalPipelineConfig from
903
- config framework) is accessible in spawned thread.
904
-
905
- Python's threading module creates isolated thread-local storage, so contextvars
906
- don't automatically propagate to spawned threads. This utility wraps
907
- threading.Thread to propagate the current context using contextvars.copy_context().
892
+ A new thread starts with no ``config_context`` entered. This starts one with the
893
+ layers the caller has entered (``contextvars.copy_context()``), so plain lazy
894
+ objects read in it what they read in the caller. The global config and the
895
+ registry are one per process and need no copying.
908
896
 
909
897
  Args:
910
898
  target: The function to run in thread
@@ -126,7 +126,7 @@ Usage Example
126
126
 
127
127
  from objectstate import (
128
128
  auto_create_decorator, config_context,
129
- ensure_global_config_context, LazyDataclassFactory
129
+ set_global_config, LazyDataclassFactory
130
130
  )
131
131
 
132
132
  # Create global config with concrete values
@@ -139,7 +139,7 @@ Usage Example
139
139
  )
140
140
 
141
141
  # Establish global context
142
- ensure_global_config_context(GlobalAppConfig, global_config)
142
+ set_global_config(GlobalAppConfig, global_config)
143
143
 
144
144
  # Create app config with lazy configs for inheritance
145
145
  app_config = AppConfig(
@@ -169,28 +169,18 @@ The ``objectstate`` package contains:
169
169
  UI placeholder generation showing inherited values
170
170
 
171
171
  **global_config.py**
172
- Thread-local storage for global configuration
172
+ The global config and the one thing that holds it
173
173
 
174
174
  **object_state.py**
175
175
  ObjectState and ObjectStateRegistry for UI state management
176
176
 
177
- Thread-Local Context Synchronization
178
- ------------------------------------
177
+ Global Config and Editing
178
+ -------------------------
179
179
 
180
- The configuration framework maintains thread-local context for global configuration to support lazy placeholder resolution across the application.
181
-
182
- **Context Lifecycle**
183
-
184
- 1. **Initialization**: Context set during application startup
185
- 2. **Live Updates**: Context synchronized with form edits in real-time
186
- 3. **Restoration**: Original context restored on cancel
187
- 4. **Cleanup**: Context cleared on window close
188
-
189
- **UI Integration**
190
-
191
- The configuration window implements special handling for global configuration to ensure thread-local context stays synchronized with form edits. Each field change triggers immediate context update, ensuring placeholder values in other windows update immediately.
192
-
193
- When users cancel config editing, the original context is restored to prevent context pollution from experimental changes.
180
+ The global config is one value per process. While an ``ObjectState`` of it is
181
+ registered at scope ``""`` that state holds it: a form edit is an edit of the state
182
+ and is the live global config at once, cancel is ``restore_saved`` and save is
183
+ ``mark_saved``. Nothing else is synchronised or restored.
194
184
 
195
185
  **Implementation Details**
196
186