Repository navigation
Context stacks: with-only build_context_stack, scoped ancestors only, no layer skipped or substituted (#31, #34 items 1-2) - #41
Merged
Conversation
…skipped or substituted build_context_stack is a context manager and takes scoped ancestors only. config_context, merge_configs, build_context_stack, resolve_with_provenance and the lazy resolver method raise where they used to continue with a layer missing. Uncalled helpers that substituted layers are deleted. Refs #34 (items 1, 2), #31. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
Merged
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 #31. Refs #34 (items 1 and 2; item 3,
TimeTravelChangeSet.legacy_entries, is handled separately, so #34 stays open).What changed
build_context_stackis a@contextmanager. It sets nothing until thewithblock is entered and leaves every layer on exit, including when a layer fails halfway or the block raises.ancestor_objectsparameter and its unscoped branch are deleted. Every ancestor layer now carries a scope id.NoActiveContextError(incontext_manager.py) names the "no context active" state.Acceptance: #34 items 1 and 2
build_context_stackis decorated as a context manager@contextmanagerincontext_manager.py;test_build_context_stack_sets_nothing_until_entered_and_resets_on_exit,test_build_context_stack_resets_when_the_block_raiseswithobject_state.pyarewith build_context_stack(...):; the example insrc/objectstate/docs/context_system.rstis rewritten to the real signatureancestor_objects=insrc/objectstatetest_build_context_stack_rejects_ancestors_without_scopesDEPRECATEDinsrc/objectstatelegacyinsrc/objectstatelegacy_entriesinobject_state_registry.py:139,:1889(item 3) and one stale comment atlazy_factory.py:1082, outside the sites this PR may editAcceptance: #31
config_context,replace_rawfailstest_layer_that_cannot_be_merged_raisesbuild_context_stack,obj_type()failstest_global_type_that_cannot_be_constructed_raisesresolve_with_provenance,extract_all_configs(layer)failstest_layer_whose_configs_cannot_be_extracted_raises_resolve_field_value, no contextNoActiveContextErrortest_resolver_method_without_active_context_raises_inject_context_layer,_get_global_context_layer,_find_live_globalNo
except Exceptionremains incontext_manager.pyordual_axis_resolver.py. Seven of the eight new tests fail onmain1fcdf12; the eighth (reset when the block raises) pins behaviour that already held.Two more
except Exceptionblocks incontext_manager.pywere not in the issue's table but fall under its acceptance line, so they are handled too:merge_configs(returned the base unchanged on failure; now raises,test_merge_configs_that_cannot_be_applied_raises) andextract_from_object_attributes(deleted, no caller).Deviation: no layer type for the partial-form case
The issue asks for the
SimpleNamespacesubstitute in_inject_context_layerto become its own layer type. I did not add one, because nothing reaches that case:_inject_context_layerhas had no caller sincebuild_context_stackwas reduced to three parameters (06abcca). I searchedsrc/,tests/,docs/, and the OpenHCS, pyqt-reactive and formbridge checkouts for_inject_context_layer,_get_global_context_layer,_find_live_global,_find_live_values_for_type,extract_from_object_attributesandextract_from_dataclass_fields; all six are uncalled. A type with no producer and no consumer would be a second dead path, so the six functions are deleted. If a form that lacks constructor arguments needs to enter a context again, it should arrive as a declared layer type passed toconfig_context, not through a caught constructor failure.Behaviour changes for callers (pyqt-reactive, OpenHCS)
Situations that used to continue and now raise:
build_context_stack(..., ancestor_objects=[...])raisesTypeError(unknown keyword). pyqt-reactiveservices/widget_service.py:313does this and must switch toObjectStateRegistry.get_ancestor_objects_with_scopes(...)/ancestor_objects_with_scopes=.build_context_stack(...)withoutwithno longer sets any context. Code that relied on the returnedExitStackbeing already entered (or called.close()on it) now resolves outside the stack or fails on.close().stack = build_context_stack(...); with stack:still works.config_context(obj)whose overrides the base config's constructor rejects (for example a__post_init__validation, or an unexpected field): used to log a warning and run the block on the base config withoutobj's values; now the constructor's exception propagates.build_context_stack(global_config_instance)when the global config type cannot be built with no arguments: used to run with no global layer; now raises the constructor'sTypeError. Global config types need defaults for every field.resolve_with_provenancewith a layer thatextract_all_configsrejects (a field declared as one config type holding another, or two instances of one config type in a layer): the layer used to be skipped and the value came from another layer; now theTypeErrorpropagates. This reachesObjectStatelive recomputation andObjectState.resolve_saved_object.lazy_instance._resolve_field_value(name)with no active context: used to returnNone; now raisesNoActiveContextError. Plain attribute access on lazy instances is unchanged. OpenHCS and formbridge only test for the method's presence.merge_configs(base, overrides)with overrides the constructor rejects: used to log and returnbase; now raises.Paper results
config_contextand the debug line in_resolve_field_valueare replaced by raising.resolve_with_provenancecould not tell a value from the right layer from one found after a skip. The failure now leaves through the declared channel (an exception).context_layer_stack, so their values were attributed to another scope or to none; every layer now has a scope id.Tests
pytest -n 2: 248 passed.ruff check src/ --select E9,F63,F7,F82: clean.scripts/check_ruff_added_lines.py --base origin/main: no diagnostics on added lines.🤖 Generated with Claude Code