objectstate 1.2.0__tar.gz → 1.3.1__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-1.2.0 → objectstate-1.3.1}/.github/workflows/publish.yml +11 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/PKG-INFO +2 -2
- objectstate-1.3.1/docs/performance/canonical-context-type-resolution-20261005.json +45 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/pyproject.toml +1 -1
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/__init__.py +1 -1
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/dual_axis_resolver.py +31 -33
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/object_state.py +15 -55
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/parameter_structure.py +15 -2
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_construction_history.py +2 -1
- {objectstate-1.2.0 → objectstate-1.3.1}/.github/badges/coverage.svg +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/.github/workflows/ci.yml +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/.github/workflows/coverage-pages.yml +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/.gitignore +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/.readthedocs.yaml +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/BADGE_SETUP.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/LICENSE +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/README.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/.gitignore +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/Makefile +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/api/modules.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/architectural_fixes.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/architecture.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/archive/DOCUMENTATION_AUDIT.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/archive/DOCUMENTATION_FIXES.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/archive/README.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/conf.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/examples/atomic_operations.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/examples/basic.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/examples/dual_axis.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/examples/index.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/examples/ui.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/index.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/make.bat +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/provenance.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/quickstart.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/state_management.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/state_propagation.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/docs/undo_redo.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/paper.bib +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/paper.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/plans/n_dimensional_inheritance.md +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/scripts/check_ruff_added_lines.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/scripts/release.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/scripts/update_and_release.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/scripts/update_version.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/scripts/verify_release_ready.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/collection_containers.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/config.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/construction_binding.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/context_manager.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/docs/configuration_framework.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/docs/context_system.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/docs/object_state_lifecycle.rst +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/edit_session.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/field_access.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/global_config.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/history_migration.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/lazy_factory.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/live_context_resolver.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/object_state_metadata.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/object_state_registry.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/parameter_owner.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/parametric_axes.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/placeholder.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/reified_generics.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/snapshot_model.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/subfield_semantics.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/time_travel_profile.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/token_cache.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/transaction_checkpoint.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/ui_visibility.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/src/objectstate/value_semantics.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/__init__.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/conftest.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_callable_baseline.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_config.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_context_manager.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_field_access.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_global_config.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_history_persistence.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_integration.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_lazy_factory.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_object_state_metadata_contracts.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_object_state_restore.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_object_state_update_instance.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_parameter_owner.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_parametric_axes.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_placeholder.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_registry_subscription.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_reified_generics.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_resolved_change_notifications.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_subfield_semantics.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_timeline_projection.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_transaction_checkpoint.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_ui_visible_field_projection.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_undo_redo_cascade.py +0 -0
- {objectstate-1.2.0 → objectstate-1.3.1}/tests/test_value_semantics.py +0 -0
|
@@ -4,6 +4,12 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags:
|
|
6
6
|
- 'v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
release_tag:
|
|
10
|
+
description: Existing release tag to publish
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
7
13
|
|
|
8
14
|
permissions:
|
|
9
15
|
contents: write
|
|
@@ -13,6 +19,8 @@ jobs:
|
|
|
13
19
|
build-and-publish:
|
|
14
20
|
name: Build and publish to PyPI
|
|
15
21
|
runs-on: ubuntu-latest
|
|
22
|
+
env:
|
|
23
|
+
RELEASE_TAG: ${{ inputs.release_tag || github.ref_name }}
|
|
16
24
|
environment:
|
|
17
25
|
name: pypi
|
|
18
26
|
url: https://pypi.org/p/objectstate
|
|
@@ -20,6 +28,8 @@ jobs:
|
|
|
20
28
|
steps:
|
|
21
29
|
- name: Checkout code
|
|
22
30
|
uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
ref: refs/tags/${{ env.RELEASE_TAG }}
|
|
23
33
|
|
|
24
34
|
- name: Set up Python
|
|
25
35
|
uses: actions/setup-python@v5
|
|
@@ -42,6 +52,7 @@ jobs:
|
|
|
42
52
|
- name: Create GitHub Release
|
|
43
53
|
uses: softprops/action-gh-release@v2
|
|
44
54
|
with:
|
|
55
|
+
tag_name: ${{ env.RELEASE_TAG }}
|
|
45
56
|
files: dist/*
|
|
46
57
|
generate_release_notes: true
|
|
47
58
|
env:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: objectstate
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
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
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
21
21
|
Classifier: Topic :: Utilities
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Requires-Dist: dill>=0.4.0
|
|
24
|
-
Requires-Dist: python-introspect
|
|
24
|
+
Requires-Dist: python-introspect<0.3,>=0.2.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: black==26.5.1; extra == 'dev'
|
|
27
27
|
Requires-Dist: mypy==2.3.0; extra == 'dev'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "PASS_PRIVATE_SAVED_CONSUMER_ONLY",
|
|
3
|
+
"baseline": "Installed ObjectState 1.3.0 from merged 7db151907a52a95743fbeae5a64220b28bc56690, PythonIntrospect 0.2.0",
|
|
4
|
+
"change": "resolve_with_provenance queries the existing canonical type-keyed maps admitted by extract_all_configs instead of scanning every value and reconstructing each owner type.",
|
|
5
|
+
"controls": {
|
|
6
|
+
"passed": 27,
|
|
7
|
+
"command": "PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src python -m pytest -q -o addopts= -p no:cacheprovider tests/test_context_manager.py tests/test_object_state_restore.py tests/test_callable_baseline.py tests/test_placeholder.py",
|
|
8
|
+
"laws": "Canonical map admission, duplicate/invalid declarations, MRO/None/provenance, real constructors/default factories, callable identity, detached mutable alias graphs"
|
|
9
|
+
},
|
|
10
|
+
"one_pair": [
|
|
11
|
+
{
|
|
12
|
+
"case": "Illumination Example3",
|
|
13
|
+
"first_job": true,
|
|
14
|
+
"steps": 10,
|
|
15
|
+
"baseline_seconds": 0.08565246313810349,
|
|
16
|
+
"candidate_seconds": 0.0676199309527874,
|
|
17
|
+
"saved_seconds": 0.018032532185316086
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"case": "Original 3D monolayer",
|
|
21
|
+
"first_job": false,
|
|
22
|
+
"steps": 31,
|
|
23
|
+
"baseline_seconds": 0.2247260957956314,
|
|
24
|
+
"candidate_seconds": 0.19058690778911114,
|
|
25
|
+
"saved_seconds": 0.03413918800652027
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"parity": "Exact typed configuration/steps, provenance, canonical source and ancestor scopes PASS",
|
|
29
|
+
"guards": "Before/after source, actual eight installed dependency trees and wheel metadata, four native ABIs, authored input sources and helper hashes PASS; all owned processes terminal",
|
|
30
|
+
"preserved": "Context constructors/merges and extraction epochs, selected live field reads, same-type scope precedence, MRO class-first precedence, None inheritance, AttributeError behavior, fallback provenance and direct-owner default factories",
|
|
31
|
+
"limits": [
|
|
32
|
+
"ONE saved-owner pair, not a whole compiler, ordinary runtime or total speedup measurement.",
|
|
33
|
+
"Normal full Registry READY, document parsing, orchestrator initialization, graph/provider admission and execution were outside the clocks.",
|
|
34
|
+
"3D follows Illumination in each process; it is not an independent cold first-job observation.",
|
|
35
|
+
"This generalized cleanup is not the dominant performance route; the first-case 18 ms saving does not close the remaining total target gap.",
|
|
36
|
+
"Arbitrary corruption of the admitted map/type keys or in-operation instance __class__ rebinding was not established equivalent. Mutable field values remain live.",
|
|
37
|
+
"The public resolver accepting manually supplied maps is unchanged. No new cache, value snapshot or installed dependency mutation."
|
|
38
|
+
],
|
|
39
|
+
"custody": {
|
|
40
|
+
"private_packet": "20261005/canonical-context-type-resolution-v1",
|
|
41
|
+
"baseline_resolver_sha256": "17fcda62912050bdcdd018bee2fca5f81c2f4940c5f27f2af3dc19b7691e1fb0",
|
|
42
|
+
"candidate_resolver_sha256": "e8e939bfb97ad2d07cb0f4893e43229fc30926274e7bdc7a4491264feeaeae97",
|
|
43
|
+
"version_change": "1.3.1 is a separate patch release; immutable 1.3.0 tags and installed wheel are untouched."
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -324,24 +324,23 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
|
|
|
324
324
|
if _debug and field_name == 'well_filter':
|
|
325
325
|
logger.debug(f"🔍 Phase 1 - Layer scope={scope_id!r}, checking same-type only (inner to outer)")
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
continue
|
|
327
|
+
# extract_all_configs admits one instance per canonical owner type.
|
|
328
|
+
config_instance = layer_configs.get(container_base)
|
|
329
|
+
if config_instance is None:
|
|
330
|
+
continue
|
|
331
|
+
try:
|
|
332
|
+
value = object.__getattribute__(config_instance, field_name)
|
|
333
|
+
if _debug and field_name in ('well_filter', 'enabled'):
|
|
334
|
+
logger.debug(f"🔍 {container_base.__name__}.{field_name} @ scope={scope_id} = {value!r} (from object.__getattribute__)")
|
|
335
|
+
if value is not None:
|
|
336
|
+
# Found concrete value in hierarchy - return immediately
|
|
337
|
+
if _debug and field_name in ('well_filter', 'enabled'):
|
|
338
|
+
logger.debug(f"🔍 FOUND concrete value in hierarchy at scope={scope_id!r}, returning {value!r}")
|
|
339
|
+
return value, scope_id, container_base
|
|
340
|
+
# Don't set fallback here - let Phase 2 walk MRO to find
|
|
341
|
+
# the highest type that defines this field
|
|
342
|
+
except AttributeError:
|
|
343
|
+
continue
|
|
345
344
|
|
|
346
345
|
# PHASE 2: MRO fallback - no concrete value in hierarchy, try MRO inheritance
|
|
347
346
|
#
|
|
@@ -367,25 +366,24 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
|
|
|
367
366
|
|
|
368
367
|
# Walk scopes from inner→outer for this MRO type
|
|
369
368
|
for scope_id, layer_configs in reversed(all_layer_configs):
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
continue
|
|
369
|
+
config_instance = layer_configs.get(mro_type)
|
|
370
|
+
if config_instance is None:
|
|
371
|
+
continue
|
|
374
372
|
|
|
375
|
-
|
|
376
|
-
|
|
373
|
+
saw_type_anywhere = True
|
|
374
|
+
last_scope_for_type = scope_id # update as we walk inner→outer (last = outermost)
|
|
377
375
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
376
|
+
try:
|
|
377
|
+
value = object.__getattribute__(config_instance, field_name)
|
|
378
|
+
except AttributeError:
|
|
379
|
+
continue
|
|
382
380
|
|
|
383
|
-
|
|
384
|
-
|
|
381
|
+
if _debug and field_name == 'well_filter':
|
|
382
|
+
logger.debug(f"🔍 MRO: {mro_type.__name__}.{field_name} @ {scope_id!r} = {value!r}")
|
|
385
383
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
384
|
+
if value is not None:
|
|
385
|
+
# Found MRO-inherited value
|
|
386
|
+
return value, scope_id, mro_type
|
|
389
387
|
|
|
390
388
|
# No non-None found for this MRO type across any scope.
|
|
391
389
|
# Record fallback for the FIRST MRO type that exists anywhere.
|
|
@@ -529,7 +529,7 @@ class ObjectState:
|
|
|
529
529
|
Dictionary mapping dotted parameter paths to their descriptions (value may be None).
|
|
530
530
|
E.g., {'well_filter_config.well_filter': 'Filter wells by...'}
|
|
531
531
|
"""
|
|
532
|
-
return
|
|
532
|
+
return self._parameter_structure.descriptions
|
|
533
533
|
|
|
534
534
|
def type_for_path(self, field_path: str) -> ParameterOwner:
|
|
535
535
|
"""Return the ObjectState-recorded type for a dotted field path.
|
|
@@ -769,27 +769,13 @@ class ObjectState:
|
|
|
769
769
|
|
|
770
770
|
Returns dict mapping param_name -> info object with .param_type, .default_value, and .description attributes.
|
|
771
771
|
"""
|
|
772
|
-
from types import SimpleNamespace
|
|
773
|
-
|
|
774
|
-
exclude_params = exclude_params or []
|
|
775
|
-
result = {}
|
|
776
|
-
|
|
777
772
|
# NOTE: python_introspect is a required dependency; fail loud if missing.
|
|
778
773
|
# UnifiedParameterAnalyzer is responsible for correctness guarantees:
|
|
779
774
|
# - defaults come from type/signature (never from instance)
|
|
780
775
|
# - current instance values are accessed in a lazy-safe way (if needed)
|
|
781
776
|
from python_introspect import UnifiedParameterAnalyzer
|
|
782
777
|
|
|
783
|
-
|
|
784
|
-
for name, info in ua_info.items():
|
|
785
|
-
if name in exclude_params:
|
|
786
|
-
continue
|
|
787
|
-
result[name] = SimpleNamespace(
|
|
788
|
-
param_type=getattr(info, "param_type", Any),
|
|
789
|
-
default_value=getattr(info, "default_value", None),
|
|
790
|
-
description=getattr(info, "description", None),
|
|
791
|
-
)
|
|
792
|
-
return result
|
|
778
|
+
return UnifiedParameterAnalyzer.analyze(obj, exclude_params=exclude_params)
|
|
793
779
|
|
|
794
780
|
@staticmethod
|
|
795
781
|
def _get_nested_dataclass_type(param_type: Any) -> type | None:
|
|
@@ -1219,7 +1205,10 @@ class ObjectState:
|
|
|
1219
1205
|
raw_value = self.parameters.get(path.value, MISSING)
|
|
1220
1206
|
resolved_value = self.get_resolved_value(path.value)
|
|
1221
1207
|
saved_resolved_value = self.get_saved_resolved_value(path.value)
|
|
1222
|
-
|
|
1208
|
+
declaration = self._parameter_structure.declarations.get(path.value)
|
|
1209
|
+
signature_default_value = (
|
|
1210
|
+
declaration.default_value if declaration is not None else MISSING
|
|
1211
|
+
)
|
|
1223
1212
|
|
|
1224
1213
|
return build_subfield_semantic_index(
|
|
1225
1214
|
owner_field_path=path,
|
|
@@ -1557,22 +1546,6 @@ class ObjectState:
|
|
|
1557
1546
|
) -> None:
|
|
1558
1547
|
self._parameter_structure.direct_paths = value
|
|
1559
1548
|
|
|
1560
|
-
@property
|
|
1561
|
-
def _signature_defaults(self) -> dict[str, Any]:
|
|
1562
|
-
return self._parameter_structure.defaults
|
|
1563
|
-
|
|
1564
|
-
@_signature_defaults.setter
|
|
1565
|
-
def _signature_defaults(self, value: dict[str, Any]) -> None:
|
|
1566
|
-
self._parameter_structure.defaults = value
|
|
1567
|
-
|
|
1568
|
-
@property
|
|
1569
|
-
def _parameter_descriptions(self) -> dict[str, str | None]:
|
|
1570
|
-
return self._parameter_structure.descriptions
|
|
1571
|
-
|
|
1572
|
-
@_parameter_descriptions.setter
|
|
1573
|
-
def _parameter_descriptions(self, value: dict[str, str | None]) -> None:
|
|
1574
|
-
self._parameter_structure.descriptions = value
|
|
1575
|
-
|
|
1576
1549
|
@property
|
|
1577
1550
|
def _exclude_param_names(self) -> list[str]:
|
|
1578
1551
|
return self._parameter_structure.exclusions
|
|
@@ -1893,16 +1866,17 @@ class ObjectState:
|
|
|
1893
1866
|
|
|
1894
1867
|
# Use signature defaults (CLASS defaults), not instance values
|
|
1895
1868
|
# This ensures reset goes back to None for lazy fields, not saved concrete values
|
|
1896
|
-
|
|
1869
|
+
declaration = self._parameter_structure.declarations.get(param_name)
|
|
1870
|
+
default_value = declaration.default_value if declaration is not None else None
|
|
1897
1871
|
self.update_parameter(param_name, default_value)
|
|
1898
1872
|
|
|
1899
1873
|
def signature_default(self, param_name: str) -> Any:
|
|
1900
1874
|
"""Return the signature default recorded for one flat parameter path."""
|
|
1901
|
-
if param_name not in self.
|
|
1875
|
+
if param_name not in self._parameter_structure.declarations:
|
|
1902
1876
|
raise KeyError(
|
|
1903
1877
|
f"No signature default recorded for parameter {param_name!r}."
|
|
1904
1878
|
)
|
|
1905
|
-
return self.
|
|
1879
|
+
return self._parameter_structure.declarations[param_name].default_value
|
|
1906
1880
|
|
|
1907
1881
|
def get_current_values(self) -> dict[str, Any]:
|
|
1908
1882
|
"""
|
|
@@ -2014,14 +1988,13 @@ class ObjectState:
|
|
|
2014
1988
|
"""Compute signature-diff set from parameters vs defaults.
|
|
2015
1989
|
|
|
2016
1990
|
Any field that differs from its signature default is included.
|
|
2017
|
-
|
|
2018
|
-
|
|
1991
|
+
Defaults come from the same declarations used for reset, including
|
|
1992
|
+
declared nested containers.
|
|
2019
1993
|
"""
|
|
2020
1994
|
result = set()
|
|
2021
1995
|
for k, v in self.parameters.items():
|
|
2022
|
-
if k in self.
|
|
2023
|
-
|
|
2024
|
-
sig_default = self._signature_defaults[k]
|
|
1996
|
+
if k in self._parameter_structure.declarations:
|
|
1997
|
+
sig_default = self._parameter_structure.declarations[k].default_value
|
|
2025
1998
|
is_diff = v != sig_default
|
|
2026
1999
|
if is_diff:
|
|
2027
2000
|
result.add(k)
|
|
@@ -2577,10 +2550,7 @@ class ObjectState:
|
|
|
2577
2550
|
except AttributeError:
|
|
2578
2551
|
current_value = info.default_value
|
|
2579
2552
|
|
|
2580
|
-
|
|
2581
|
-
# parameter has no description, ensure the dotted key exists so
|
|
2582
|
-
# callers can rely on presence of the full path (value may be None).
|
|
2583
|
-
structure.descriptions[dotted_path] = getattr(info, 'description', None)
|
|
2553
|
+
structure.declarations[dotted_path] = info
|
|
2584
2554
|
|
|
2585
2555
|
# Check if this is a nested dataclass
|
|
2586
2556
|
# First try from type annotation, then fall back to checking actual value
|
|
@@ -2599,13 +2569,6 @@ class ObjectState:
|
|
|
2599
2569
|
# Store the nested dataclass instance in parameters (needed for UI rendering)
|
|
2600
2570
|
parameters[dotted_path] = current_value
|
|
2601
2571
|
|
|
2602
|
-
# Container resets follow the same declared-default authority as
|
|
2603
|
-
# leaf resets. Lazy dataclass fields retain their intentional
|
|
2604
|
-
# ``None`` default, while callable parameters with concrete
|
|
2605
|
-
# dataclass defaults can be restored after an authored override
|
|
2606
|
-
# is removed.
|
|
2607
|
-
structure.defaults[dotted_path] = info.default_value
|
|
2608
|
-
|
|
2609
2572
|
# Recurse into nested dataclass for child fields
|
|
2610
2573
|
cls._extract_all_parameters_flat(
|
|
2611
2574
|
current_value, parameters, structure, prefix=dotted_path, exclude_params=[]
|
|
@@ -2615,9 +2578,6 @@ class ObjectState:
|
|
|
2615
2578
|
parameters[dotted_path] = current_value
|
|
2616
2579
|
# Store the owner target that has this field.
|
|
2617
2580
|
structure.owner_paths[dotted_path] = owner_target
|
|
2618
|
-
# Store signature default for reset functionality (flattened)
|
|
2619
|
-
# info.default_value is now guaranteed to be the CLASS signature default
|
|
2620
|
-
structure.defaults[dotted_path] = info.default_value
|
|
2621
2581
|
|
|
2622
2582
|
def _replace_parameter_structure(
|
|
2623
2583
|
self,
|
|
@@ -6,6 +6,8 @@ import copy
|
|
|
6
6
|
from dataclasses import dataclass, field, fields, is_dataclass, replace
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from python_introspect import ParameterInfo
|
|
10
|
+
|
|
9
11
|
from objectstate.field_access import DottedFieldPath
|
|
10
12
|
from objectstate.parameter_owner import ParameterOwner
|
|
11
13
|
|
|
@@ -18,11 +20,22 @@ class ParameterStructure:
|
|
|
18
20
|
direct_paths: dict[DottedFieldPath, tuple[DottedFieldPath, ...]] = field(
|
|
19
21
|
default_factory=dict
|
|
20
22
|
)
|
|
21
|
-
|
|
22
|
-
descriptions: dict[str, str | None] = field(default_factory=dict)
|
|
23
|
+
declarations: dict[str, ParameterInfo] = field(default_factory=dict)
|
|
23
24
|
exclusions: list[str] = field(default_factory=list)
|
|
24
25
|
excluded_values: dict[str, Any] = field(default_factory=dict)
|
|
25
26
|
|
|
27
|
+
@property
|
|
28
|
+
def defaults(self) -> dict[str, Any]:
|
|
29
|
+
"""Derive editor reset values from admitted parameter declarations."""
|
|
30
|
+
|
|
31
|
+
return {path: info.default_value for path, info in self.declarations.items()}
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def descriptions(self) -> dict[str, str | None]:
|
|
35
|
+
"""Request presentation help only when a consumer reads it."""
|
|
36
|
+
|
|
37
|
+
return {path: info.description for path, info in self.declarations.items()}
|
|
38
|
+
|
|
26
39
|
def is_container_parameter(
|
|
27
40
|
self, name: str, value: Any, parameters: dict[str, Any]
|
|
28
41
|
) -> bool:
|
|
@@ -148,7 +148,8 @@ def test_same_scope_callable_owner_and_schema_restore_without_replacing_state():
|
|
|
148
148
|
assert ObjectStateRegistry.get_by_scope("callable") is state
|
|
149
149
|
assert state.object_instance is function
|
|
150
150
|
assert set(state.parameters) == parameters
|
|
151
|
-
|
|
151
|
+
for name in parameters:
|
|
152
|
+
state.signature_default(name)
|
|
152
153
|
assert set(state._path_to_type) == parameters
|
|
153
154
|
|
|
154
155
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|