objectstate 3.0.2__tar.gz → 3.0.4__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.
- {objectstate-3.0.2 → objectstate-3.0.4}/.github/badges/coverage.svg +1 -1
- {objectstate-3.0.2 → objectstate-3.0.4}/PKG-INFO +1 -1
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/architecture.rst +3 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/provenance.rst +3 -1
- objectstate-3.0.4/docs/state_propagation.rst +169 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/__init__.py +1 -1
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/context_manager.py +20 -5
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/docs/context_system.rst +15 -12
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/object_state.py +169 -96
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/object_state_registry.py +13 -35
- objectstate-3.0.4/src/objectstate/resolution_sources.py +176 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/scope_key.py +12 -4
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/transaction_checkpoint.py +3 -0
- objectstate-3.0.4/tests/test_resolved_values_follow_registered_ancestors.py +971 -0
- objectstate-3.0.2/docs/state_propagation.rst +0 -96
- objectstate-3.0.2/tests/test_resolved_values_follow_registered_ancestors.py +0 -341
- {objectstate-3.0.2 → objectstate-3.0.4}/.github/workflows/ci.yml +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/.github/workflows/coverage-pages.yml +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/.github/workflows/publish.yml +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/.gitignore +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/.readthedocs.yaml +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/BADGE_SETUP.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/LICENSE +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/README.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/.gitignore +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/Makefile +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/api/modules.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/architectural_fixes.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/archive/DOCUMENTATION_AUDIT.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/archive/DOCUMENTATION_FIXES.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/archive/README.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/conf.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/examples/atomic_operations.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/examples/basic.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/examples/dual_axis.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/examples/index.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/examples/ui.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/index.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/make.bat +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/performance/canonical-context-type-resolution-20261005.json +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/quickstart.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/state_management.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/docs/undo_redo.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/paper.bib +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/paper.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/plans/n_dimensional_inheritance.md +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/pyproject.toml +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/scripts/check_ruff_added_lines.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/scripts/release.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/scripts/update_and_release.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/scripts/update_version.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/scripts/verify_release_ready.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/codegen.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/collection_containers.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/config.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/construction_binding.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/docs/configuration_framework.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/docs/object_state_lifecycle.rst +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/dual_axis_resolver.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/edit_session.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/field_access.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/global_config.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/inspection.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/lazy_factory.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/live_context_resolver.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/object_state_metadata.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/parameter_owner.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/parameter_structure.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/parametric_axes.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/placeholder.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/reified_generics.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/snapshot_model.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/subfield_semantics.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/time_travel_profile.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/token_cache.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/ui_visibility.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/src/objectstate/value_semantics.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/__init__.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/conftest.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_atomic_block_context.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_authored_paths.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_callable_baseline.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_codegen.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_config.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_construction_history.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_context_failures_surface.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_context_manager.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_field_access.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_global_config.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_history_persistence.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_inspection.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_integration.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_lazy_counterpart_registry.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_lazy_factory.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_live_context_resolver_answers.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_object_state_metadata_contracts.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_object_state_restore.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_object_state_update_instance.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_one_field_resolution.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_parameter_owner.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_parametric_axes.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_placeholder.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_registry_subscription.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_reified_generics.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_release_readiness.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_resolution_precedence_order.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_resolved_change_notifications.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_scope_key.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_scopeless_state_resolution.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_subfield_semantics.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_timeline_projection.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_transaction_checkpoint.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_ui_visible_field_projection.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/tests/test_undo_redo_cascade.py +0 -0
- {objectstate-3.0.2 → objectstate-3.0.4}/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:
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: objectstate
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.4
|
|
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
|
|
@@ -527,6 +527,9 @@ Sets the **instance** (concrete values) for resolution:
|
|
|
527
527
|
* Required for lazy resolution to work
|
|
528
528
|
* Internally calls ``set_global_config_for_editing()``
|
|
529
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`)
|
|
530
533
|
|
|
531
534
|
**Key Differences:**
|
|
532
535
|
|
|
@@ -119,7 +119,9 @@ A ``ResolvedField`` named tuple:
|
|
|
119
119
|
|
|
120
120
|
* ``value``: The resolved field value (may be ``None``)
|
|
121
121
|
* ``source_scope``: The scope id of the layer that supplied the value. ``None`` for a layer
|
|
122
|
-
entered with ``config_context(obj)`` without a ``scope_id``.
|
|
122
|
+
entered with ``config_context(obj)`` without a ``scope_id``. ``ObjectState.get_provenance``
|
|
123
|
+
never reports such a layer: a state resolves against the global config and its registered
|
|
124
|
+
ancestors only (:ref:`state-resolution-inputs`).
|
|
123
125
|
* ``source_type``: The type the value was read from: the container type or one of its bases.
|
|
124
126
|
|
|
125
127
|
When no layer holds a value, ``value`` is the declared default and the source names the
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
State Propagation and Parent Notification
|
|
2
|
+
=========================================
|
|
3
|
+
|
|
4
|
+
``ObjectState.forward_to_parent_state()`` lets one independently scoped state
|
|
5
|
+
notify a parent state that a conceptual field changed. It is a callback bridge,
|
|
6
|
+
not a second mutation or persistence path.
|
|
7
|
+
|
|
8
|
+
Scope hierarchy is not dataclass hierarchy
|
|
9
|
+
-------------------------------------------
|
|
10
|
+
|
|
11
|
+
Nested dataclasses are flattened inside one ``ObjectState`` and updated with a
|
|
12
|
+
dotted path. They do not need parent forwarding:
|
|
13
|
+
|
|
14
|
+
.. code-block:: python
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
from objectstate import ObjectState
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Palette:
|
|
22
|
+
background: str = "black"
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class DisplayConfig:
|
|
26
|
+
palette: Palette = field(default_factory=Palette)
|
|
27
|
+
|
|
28
|
+
state = ObjectState(DisplayConfig(), scope_id="display")
|
|
29
|
+
state.update_parameter("palette.background", "white")
|
|
30
|
+
|
|
31
|
+
By contrast, two objects with distinct lifecycles may have independent states
|
|
32
|
+
whose scopes form a parent/child relationship. The child can use its
|
|
33
|
+
``parent_state`` for ancestor context and can notify the parent when the parent
|
|
34
|
+
view owns a summary or preview of that child.
|
|
35
|
+
|
|
36
|
+
Explicit parent notification
|
|
37
|
+
----------------------------
|
|
38
|
+
|
|
39
|
+
.. code-block:: python
|
|
40
|
+
|
|
41
|
+
from dataclasses import dataclass
|
|
42
|
+
|
|
43
|
+
from objectstate import ObjectState
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class Collection:
|
|
47
|
+
summary: str = ""
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Entry:
|
|
51
|
+
value: int = 0
|
|
52
|
+
|
|
53
|
+
parent = ObjectState(Collection(), scope_id="collection")
|
|
54
|
+
child = ObjectState(
|
|
55
|
+
Entry(),
|
|
56
|
+
scope_id="collection::entry_0",
|
|
57
|
+
parent_state=parent,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
parent.on_resolved_changed(lambda paths: print(paths))
|
|
61
|
+
child.forward_to_parent_state("summary")
|
|
62
|
+
# {'summary'}
|
|
63
|
+
|
|
64
|
+
The method requires a parent state. It guards against reentrant forwarding and
|
|
65
|
+
calls the parent's resolved-change callbacks with the chosen path. It does not
|
|
66
|
+
write to the parent's ``parameters`` or advance either saved baseline.
|
|
67
|
+
|
|
68
|
+
Field-path selection
|
|
69
|
+
--------------------
|
|
70
|
+
|
|
71
|
+
The notification path is selected in this order:
|
|
72
|
+
|
|
73
|
+
1. the explicit ``field_path`` argument;
|
|
74
|
+
2. the child's internal parent-field binding, when its composing owner set one;
|
|
75
|
+
3. the last ``scope_id`` segment, with a numeric suffix removed.
|
|
76
|
+
|
|
77
|
+
Prefer an explicit field path at public composition boundaries. The scope-based
|
|
78
|
+
fallback is useful for conventional child scopes such as ``entry_0``, but scope
|
|
79
|
+
naming is not structural dataclass ownership.
|
|
80
|
+
|
|
81
|
+
Navigation and flashing
|
|
82
|
+
-----------------------
|
|
83
|
+
|
|
84
|
+
Parent resolved-change callbacks may refresh a summary, request navigation, or
|
|
85
|
+
flash a parent-owned target. Those are view-layer reactions. ObjectState owns
|
|
86
|
+
only the notification and reentrancy contract; UI code remains responsible for
|
|
87
|
+
mapping the reported field path to widgets.
|
|
88
|
+
|
|
89
|
+
Use parent forwarding when all of the following are true:
|
|
90
|
+
|
|
91
|
+
* child and parent are independently scoped states;
|
|
92
|
+
* the parent presents information derived from the child; and
|
|
93
|
+
* an ordinary child callback cannot update that parent presentation directly.
|
|
94
|
+
|
|
95
|
+
For a nested dataclass field in the same state, call ``update_parameter()`` on
|
|
96
|
+
the dotted path instead.
|
|
97
|
+
|
|
98
|
+
.. _state-resolution-inputs:
|
|
99
|
+
|
|
100
|
+
What a resolved value is computed from
|
|
101
|
+
--------------------------------------
|
|
102
|
+
|
|
103
|
+
A resolved value of a state depends only on the state's place in the registered
|
|
104
|
+
hierarchy. It is computed from three things: which states are registered at the
|
|
105
|
+
scopes above it, what each of them stores for the field's config type and field name,
|
|
106
|
+
and what the global config stores. Nothing else is an input. In particular a
|
|
107
|
+
``config_context`` that the caller has entered is not: a state built, read or resolved
|
|
108
|
+
again inside such a block has the values, saved values, reported sources, dirty
|
|
109
|
+
fields and notifications it has outside it, during the block and after it. The state
|
|
110
|
+
resolves against a layer stack of its own (``build_context_stack``) that starts from
|
|
111
|
+
the global config and holds the registered ancestors and the state's object; the
|
|
112
|
+
caller's context is set aside while it does and is active again afterwards. So the
|
|
113
|
+
reported source of a resolved value is always a scope where a state is registered, or
|
|
114
|
+
``""`` for the global config.
|
|
115
|
+
|
|
116
|
+
This is the only statement of the rule. A lazy config object that no state owns is a
|
|
117
|
+
different route: plain attribute access on it resolves against whatever
|
|
118
|
+
``config_context`` is entered (see ``context_system.rst`` in the package docs), which
|
|
119
|
+
is how a compiler resolves a pipeline's objects.
|
|
120
|
+
|
|
121
|
+
Each resolved snapshot (live
|
|
122
|
+
and saved) keeps the objects it was computed from (``ResolutionSources``): the global
|
|
123
|
+
config instance of the thread that resolved it and the object of every state
|
|
124
|
+
registered above. Every resolved read (``get_resolved_value``,
|
|
125
|
+
``get_saved_resolved_value``, ``get_provenance``, ``to_resolved_object``,
|
|
126
|
+
``state.fields``, ``dirty_fields``) first compares those objects with the ones the state would be given
|
|
127
|
+
now. When they are the same objects the read answers from the snapshot. When one is
|
|
128
|
+
another object, the two are compared field by field, the way field resolution reads a
|
|
129
|
+
context layer, and only the fields of the state that are unset and can inherit a
|
|
130
|
+
differing ``(config type, field)`` are resolved again. When other scopes have a
|
|
131
|
+
registered state than before, every field is.
|
|
132
|
+
|
|
133
|
+
So no caller announces a change. Registering, unregistering or replacing an ancestor,
|
|
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
|
|
136
|
+
a state on its next read, whether the state is registered or not. A registered state
|
|
137
|
+
is also told at once, so its subscribers hear of the change without a read; a state
|
|
138
|
+
that is not registered is referenced by nothing and tells its subscribers on its next
|
|
139
|
+
read.
|
|
140
|
+
|
|
141
|
+
Limits:
|
|
142
|
+
|
|
143
|
+
- Objects are compared by identity. A source that is changed in place is not seen.
|
|
144
|
+
``ObjectState.to_object`` returns the same function object for a state that wraps a
|
|
145
|
+
plain function, so states registered below a function-backed state follow its edits
|
|
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.
|
|
150
|
+
|
|
151
|
+
The state of the global config
|
|
152
|
+
------------------------------
|
|
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.
|
|
169
|
+
|
|
@@ -585,13 +585,20 @@ def build_context_stack(
|
|
|
585
585
|
use_live: bool = True,
|
|
586
586
|
):
|
|
587
587
|
"""
|
|
588
|
-
Enter
|
|
588
|
+
Enter the complete layer stack that resolves ``object_instance``; leave it on exit.
|
|
589
|
+
|
|
590
|
+
The stack is built from the arguments and the global config alone. A context the
|
|
591
|
+
caller has entered is set aside for the duration of the block and is active again
|
|
592
|
+
after it: none of its layers is below, between or above the layers entered here.
|
|
593
|
+
This is how an ``ObjectState`` resolves, so a state's resolved values depend on
|
|
594
|
+
its object, the states registered above it and the global config, and never on
|
|
595
|
+
where the read was made. A lazy object that no state owns is resolved by plain
|
|
596
|
+
attribute access against whatever ``config_context`` is entered.
|
|
589
597
|
|
|
590
598
|
Layers, outermost first:
|
|
591
|
-
1. The global config, under scope ``""``. ``
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
defaults is entered above it, also under scope ``""``, and masks it.
|
|
599
|
+
1. The global config, under scope ``""``. When ``object_instance`` is itself a
|
|
600
|
+
global config, a fresh instance of its type holding the declared defaults is
|
|
601
|
+
entered above it, also under scope ``""``, and masks it.
|
|
595
602
|
2. Each ancestor, under its own scope id. Global-config ancestors are left out
|
|
596
603
|
because layer 1 already stands for them.
|
|
597
604
|
3. ``object_instance``, under ``current_scope_id``.
|
|
@@ -613,6 +620,14 @@ def build_context_stack(
|
|
|
613
620
|
)
|
|
614
621
|
|
|
615
622
|
with ExitStack() as stack:
|
|
623
|
+
# With no context active, the first layer entered starts from the global config.
|
|
624
|
+
for variable, no_context in (
|
|
625
|
+
(current_temp_global, None),
|
|
626
|
+
(context_type_stack, ()),
|
|
627
|
+
(context_layer_stack, NO_LAYERS),
|
|
628
|
+
):
|
|
629
|
+
stack.callback(variable.reset, variable.set(no_context))
|
|
630
|
+
|
|
616
631
|
if isinstance(object_instance, GlobalConfigBase):
|
|
617
632
|
enter(stack, type(object_instance)(), "", mask=True)
|
|
618
633
|
|
|
@@ -173,16 +173,13 @@ Global config uses thread-local storage for stability:
|
|
|
173
173
|
_global_config_storage: Dict[Type, threading.local] = {}
|
|
174
174
|
|
|
175
175
|
def ensure_global_config_context(config_type: Type, config_instance: Any):
|
|
176
|
-
"""
|
|
177
|
-
# Store in thread-local
|
|
176
|
+
"""Store the instance as both the live and the saved global config of this thread."""
|
|
178
177
|
set_global_config_for_editing(config_type, config_instance)
|
|
179
|
-
|
|
180
|
-
# Also inject into contextvars base
|
|
181
|
-
with config_context(config_instance):
|
|
182
|
-
# Global context now available
|
|
183
|
-
pass
|
|
184
178
|
|
|
185
|
-
|
|
179
|
+
The global config is the base in thread-local storage; contextvars hold the layers
|
|
180
|
+
stacked on it. When a registered ``ObjectState`` holds the global config, that state
|
|
181
|
+
writes this storage on edit, save and restore (``docs/state_propagation.rst``, "The
|
|
182
|
+
state of the global config").
|
|
186
183
|
|
|
187
184
|
Resolution Integration
|
|
188
185
|
---------------------
|
|
@@ -268,8 +265,13 @@ No manual cleanup needed - Python's context manager protocol handles it.
|
|
|
268
265
|
Framework-Agnostic Context Stack Building
|
|
269
266
|
-----------------------------------------
|
|
270
267
|
|
|
271
|
-
``build_context_stack()`` is a context manager that enters
|
|
272
|
-
|
|
268
|
+
``build_context_stack()`` is a context manager that enters the complete layer stack
|
|
269
|
+
that resolves one object, and leaves it again when the block ends. It is how an
|
|
270
|
+
``ObjectState`` resolves. The stack is built from the arguments and the global config
|
|
271
|
+
alone: a context the caller has entered is set aside for the duration of the block and
|
|
272
|
+
is active again after it. ``docs/state_propagation.rst`` ("What a resolved value is
|
|
273
|
+
computed from") states the rule this implements; plain attribute access on a lazy
|
|
274
|
+
object, everywhere else on this page, reads the entered context.
|
|
273
275
|
|
|
274
276
|
.. code-block:: python
|
|
275
277
|
|
|
@@ -292,5 +294,6 @@ Layers are entered outermost first:
|
|
|
292
294
|
|
|
293
295
|
Every layer carries a scope id, so ``resolve_with_provenance()`` can name the scope
|
|
294
296
|
that supplied a value. The global layer is the one ``config_context()`` puts below the
|
|
295
|
-
first layer entered while no context is active
|
|
296
|
-
|
|
297
|
+
first layer entered while no context is active, which is always the case inside this
|
|
298
|
+
block. Nothing is set until the ``with`` block is entered. A layer that cannot be built
|
|
299
|
+
raises; the block never runs with a layer missing.
|