Repository navigation
Resolved values follow every object they were resolved from (3.0.3) - #55
Merged
Merged
Conversation
A resolved snapshot now keeps the objects it was computed from (ResolutionSources): the global config instance of the resolving thread and the object of each state registered above. Every resolved read compares them by identity with the objects the state would be given now. When one differs, the two objects are compared field by field the way field resolution reads a layer, and only the unset fields that can inherit a differing (config type, field) are resolved again. The answer for a pair of objects is kept, so it is computed once per edit, not once per descendant. This replaces the comparison of registered ancestor states from 3.0.2 and covers two inputs it did not (issue #53, cases 1 and 2): a state that is not registered follows edits, saves and restores of its ancestors, and replacing the global config through the setters reaches existing states. The saved snapshot is resolved in full when its sources change (on save, restore, replacement), as the registry already did. The registry's field invalidation no longer resolves the saved snapshot a second time. Case 3 of #53 (a state resolved inside an entered config_context keeps that context's values) is unchanged and stays an expected failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes cases 1 and 2 of #53. Case 3 is unchanged and stays a strict
xfail.What was wrong
3.0.2 made a resolved snapshot record the registered ancestor states it was computed from. Two other inputs were not covered:
set_live_global_config/set_saved_global_configtold no state.The relation
A resolved value is computed from the set of ancestors, what each stores for the field's
(config type, field), and what the global config stores. Each snapshot (live and saved) now keeps exactly that asResolutionSources: the global config instance of the resolving thread and the object each registered ancestor gives to resolution. Every resolved read compares those objects, by identity, with the ones the state would be given now.extract_all_configs), and only the state's unset fields that can inherit a differing(config type, field)are resolved again. The answer for a pair of objects is kept, so it is computed once per edit and not once per descendant.There is one record (
ObjectState._resolved_from), written where a snapshot is computed and compared on read. No caller announces anything, there is no refresh method, and nothing references a state that is not registered. The registry still prompts registered states so their subscribers hear at once; that prompt and a plain read run the same code.Cost (78-field states, one ancestor, min of alternating runs on a loaded machine, ms)
get_resolved_valueget_provenanceThe read-path check costs about 1 us per resolved read. Comparing two 78-field ancestor objects costs 88 us, which is why the answer is kept per pair (0.2 us afterwards).
Limits (documented in
docs/state_propagation.rst)to_object()returns the same function object for a function-backed state, so states below one follow its edits only when they are registered.config_contexttakes that context's layers into its snapshot. Because source changes are now found on read, a pending change first read inside an entered context is resolved there.Tests
tests/test_resolved_values_follow_registered_ancestors.py: the twoxfails pass; added unregistered edit/save/restore, undo and redo, subscribers of an unregistered state told on its next read, an unregistered state is not kept alive, only the edited field is resolved again, a field the descendant sets itself is not resolved again, an unchanged read resolves nothing, an edit two levels up (every registration combination), global config replaced (live, saved, both; registered or not), replaced with equal values resolves nothing, replaced then an ancestor edited, a global state edit two levels down, another thread reading or setting its own global config.Local: 371 passed, 1 xfailed on Python 3.12 and 3.11 in fresh venvs.
🤖 Generated with Claude Code