objectstate 1.1.8__tar.gz → 1.2.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 (97) hide show
  1. {objectstate-1.1.8 → objectstate-1.2.0}/.github/badges/coverage.svg +1 -1
  2. {objectstate-1.1.8 → objectstate-1.2.0}/PKG-INFO +18 -1
  3. {objectstate-1.1.8 → objectstate-1.2.0}/README.md +17 -0
  4. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/__init__.py +1 -1
  5. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/context_manager.py +17 -6
  6. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/lazy_factory.py +5 -0
  7. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/object_state.py +157 -176
  8. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/parameter_structure.py +11 -1
  9. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_callable_baseline.py +81 -1
  10. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_context_manager.py +52 -0
  11. objectstate-1.2.0/tests/test_object_state_restore.py +180 -0
  12. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_object_state_update_instance.py +39 -0
  13. objectstate-1.1.8/tests/test_object_state_restore.py +0 -45
  14. {objectstate-1.1.8 → objectstate-1.2.0}/.github/workflows/ci.yml +0 -0
  15. {objectstate-1.1.8 → objectstate-1.2.0}/.github/workflows/coverage-pages.yml +0 -0
  16. {objectstate-1.1.8 → objectstate-1.2.0}/.github/workflows/publish.yml +0 -0
  17. {objectstate-1.1.8 → objectstate-1.2.0}/.gitignore +0 -0
  18. {objectstate-1.1.8 → objectstate-1.2.0}/.readthedocs.yaml +0 -0
  19. {objectstate-1.1.8 → objectstate-1.2.0}/BADGE_SETUP.md +0 -0
  20. {objectstate-1.1.8 → objectstate-1.2.0}/LICENSE +0 -0
  21. {objectstate-1.1.8 → objectstate-1.2.0}/docs/.gitignore +0 -0
  22. {objectstate-1.1.8 → objectstate-1.2.0}/docs/Makefile +0 -0
  23. {objectstate-1.1.8 → objectstate-1.2.0}/docs/api/modules.rst +0 -0
  24. {objectstate-1.1.8 → objectstate-1.2.0}/docs/architectural_fixes.rst +0 -0
  25. {objectstate-1.1.8 → objectstate-1.2.0}/docs/architecture.rst +0 -0
  26. {objectstate-1.1.8 → objectstate-1.2.0}/docs/archive/DOCUMENTATION_AUDIT.md +0 -0
  27. {objectstate-1.1.8 → objectstate-1.2.0}/docs/archive/DOCUMENTATION_FIXES.md +0 -0
  28. {objectstate-1.1.8 → objectstate-1.2.0}/docs/archive/README.md +0 -0
  29. {objectstate-1.1.8 → objectstate-1.2.0}/docs/conf.py +0 -0
  30. {objectstate-1.1.8 → objectstate-1.2.0}/docs/examples/atomic_operations.rst +0 -0
  31. {objectstate-1.1.8 → objectstate-1.2.0}/docs/examples/basic.rst +0 -0
  32. {objectstate-1.1.8 → objectstate-1.2.0}/docs/examples/dual_axis.rst +0 -0
  33. {objectstate-1.1.8 → objectstate-1.2.0}/docs/examples/index.rst +0 -0
  34. {objectstate-1.1.8 → objectstate-1.2.0}/docs/examples/ui.rst +0 -0
  35. {objectstate-1.1.8 → objectstate-1.2.0}/docs/index.rst +0 -0
  36. {objectstate-1.1.8 → objectstate-1.2.0}/docs/make.bat +0 -0
  37. {objectstate-1.1.8 → objectstate-1.2.0}/docs/provenance.rst +0 -0
  38. {objectstate-1.1.8 → objectstate-1.2.0}/docs/quickstart.rst +0 -0
  39. {objectstate-1.1.8 → objectstate-1.2.0}/docs/state_management.rst +0 -0
  40. {objectstate-1.1.8 → objectstate-1.2.0}/docs/state_propagation.rst +0 -0
  41. {objectstate-1.1.8 → objectstate-1.2.0}/docs/undo_redo.rst +0 -0
  42. {objectstate-1.1.8 → objectstate-1.2.0}/paper.bib +0 -0
  43. {objectstate-1.1.8 → objectstate-1.2.0}/paper.md +0 -0
  44. {objectstate-1.1.8 → objectstate-1.2.0}/plans/n_dimensional_inheritance.md +0 -0
  45. {objectstate-1.1.8 → objectstate-1.2.0}/pyproject.toml +0 -0
  46. {objectstate-1.1.8 → objectstate-1.2.0}/scripts/check_ruff_added_lines.py +0 -0
  47. {objectstate-1.1.8 → objectstate-1.2.0}/scripts/release.py +0 -0
  48. {objectstate-1.1.8 → objectstate-1.2.0}/scripts/update_and_release.py +0 -0
  49. {objectstate-1.1.8 → objectstate-1.2.0}/scripts/update_version.py +0 -0
  50. {objectstate-1.1.8 → objectstate-1.2.0}/scripts/verify_release_ready.py +0 -0
  51. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/collection_containers.py +0 -0
  52. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/config.py +0 -0
  53. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/construction_binding.py +0 -0
  54. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/docs/configuration_framework.rst +0 -0
  55. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/docs/context_system.rst +0 -0
  56. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/docs/object_state_lifecycle.rst +0 -0
  57. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/dual_axis_resolver.py +0 -0
  58. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/edit_session.py +0 -0
  59. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/field_access.py +0 -0
  60. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/global_config.py +0 -0
  61. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/history_migration.py +0 -0
  62. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/live_context_resolver.py +0 -0
  63. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/object_state_metadata.py +0 -0
  64. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/object_state_registry.py +0 -0
  65. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/parameter_owner.py +0 -0
  66. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/parametric_axes.py +0 -0
  67. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/placeholder.py +0 -0
  68. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/reified_generics.py +0 -0
  69. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/snapshot_model.py +0 -0
  70. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/subfield_semantics.py +0 -0
  71. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/time_travel_profile.py +0 -0
  72. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/token_cache.py +0 -0
  73. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/transaction_checkpoint.py +0 -0
  74. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/ui_visibility.py +0 -0
  75. {objectstate-1.1.8 → objectstate-1.2.0}/src/objectstate/value_semantics.py +0 -0
  76. {objectstate-1.1.8 → objectstate-1.2.0}/tests/__init__.py +0 -0
  77. {objectstate-1.1.8 → objectstate-1.2.0}/tests/conftest.py +0 -0
  78. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_config.py +0 -0
  79. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_construction_history.py +0 -0
  80. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_field_access.py +0 -0
  81. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_global_config.py +0 -0
  82. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_history_persistence.py +0 -0
  83. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_integration.py +0 -0
  84. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_lazy_factory.py +0 -0
  85. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_object_state_metadata_contracts.py +0 -0
  86. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_parameter_owner.py +0 -0
  87. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_parametric_axes.py +0 -0
  88. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_placeholder.py +0 -0
  89. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_registry_subscription.py +0 -0
  90. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_reified_generics.py +0 -0
  91. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_resolved_change_notifications.py +0 -0
  92. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_subfield_semantics.py +0 -0
  93. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_timeline_projection.py +0 -0
  94. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_transaction_checkpoint.py +0 -0
  95. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_ui_visible_field_projection.py +0 -0
  96. {objectstate-1.1.8 → objectstate-1.2.0}/tests/test_undo_redo_cascade.py +0 -0
  97. {objectstate-1.1.8 → objectstate-1.2.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: 72.72%"><title>coverage: 72.72%</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="#ea7233"/><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">72.72%</text><text x="865" y="150" fill-opacity=".3" textLength="430">72.72%</text></g><text x="865" y="140" textLength="430">72.72%</text></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 74.21%"><title>coverage: 74.21%</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="#ea7233"/><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">74.21%</text><text x="865" y="150" fill-opacity=".3" textLength="430">74.21%</text></g><text x="865" y="140" textLength="430">74.21%</text></g></g></svg>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: objectstate
3
- Version: 1.1.8
3
+ Version: 1.2.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
@@ -168,6 +168,23 @@ state.restore_saved()
168
168
  ObjectStateRegistry.unregister(state)
169
169
  ```
170
170
 
171
+ Headless compilation can resolve a submitted saved declaration without creating
172
+ or registering an editing state:
173
+
174
+ ```python
175
+ resolved_step, provenance = ObjectState.resolve_saved_object(
176
+ submitted_step,
177
+ scope_id="pipeline::step_0",
178
+ ancestor_objects_with_scopes=(("pipeline", saved_pipeline_config),),
179
+ )
180
+ ```
181
+
182
+ This uses the saved global context and the same scoped inheritance and nominal
183
+ construction as editing states. The caller supplies saved ancestors in order
184
+ from least to most specific. Provenance describes the same saved values; mutable
185
+ results are detached together while callable identities and shared aliases stay
186
+ intact. The existing live editor and saved-baseline APIs retain their lifecycle.
187
+
171
188
  ## Undo/Redo and Time Travel
172
189
 
173
190
  Git-like DAG history with branching timelines:
@@ -132,6 +132,23 @@ state.restore_saved()
132
132
  ObjectStateRegistry.unregister(state)
133
133
  ```
134
134
 
135
+ Headless compilation can resolve a submitted saved declaration without creating
136
+ or registering an editing state:
137
+
138
+ ```python
139
+ resolved_step, provenance = ObjectState.resolve_saved_object(
140
+ submitted_step,
141
+ scope_id="pipeline::step_0",
142
+ ancestor_objects_with_scopes=(("pipeline", saved_pipeline_config),),
143
+ )
144
+ ```
145
+
146
+ This uses the saved global context and the same scoped inheritance and nominal
147
+ construction as editing states. The caller supplies saved ancestors in order
148
+ from least to most specific. Provenance describes the same saved values; mutable
149
+ results are detached together while callable identities and shared aliases stay
150
+ intact. The existing live editor and saved-baseline APIs retain their lifecycle.
151
+
135
152
  ## Undo/Redo and Time Travel
136
153
 
137
154
  Git-like DAG history with branching timelines:
@@ -216,7 +216,7 @@ __all__ = [
216
216
  'ObjectStateEditSession',
217
217
  ]
218
218
 
219
- __version__ = "1.1.8"
219
+ __version__ = "1.2.0"
220
220
  __author__ = 'OpenHCS Team'
221
221
  __description__ = 'Generic configuration framework for lazy dataclass resolution'
222
222
 
@@ -66,10 +66,21 @@ def _merge_nested_dataclass(base, override, mask_with_none: bool = False):
66
66
  if not is_dataclass(base) or not is_dataclass(override):
67
67
  return override
68
68
 
69
+ from objectstate.lazy_factory import get_base_type_for_lazy, replace_raw
70
+
71
+ base_type = get_base_type_for_lazy(type(base)) or type(base)
72
+ override_type = get_base_type_for_lazy(type(override)) or type(override)
73
+ # Lazy overlays of the same owner inherit on the base instance. An authored
74
+ # nominal change instead owns the result, including subtype-only fields.
75
+ result_owner = base if base_type is override_type else override
76
+ base_fields = {field_info.name for field_info in fields(base)}
69
77
  merge_values = {}
70
78
  for field_info in fields(override):
71
79
  field_name = field_info.name
72
80
  override_value = object.__getattribute__(override, field_name)
81
+ if field_name not in base_fields:
82
+ # This field belongs only to the authored nominal owner.
83
+ continue
73
84
  base_value = object.__getattribute__(base, field_name)
74
85
 
75
86
  if override_value is None:
@@ -77,8 +88,9 @@ def _merge_nested_dataclass(base, override, mask_with_none: bool = False):
77
88
  # None overrides base value (masking mode)
78
89
  merge_values[field_name] = None
79
90
  else:
80
- # None means "don't override" - keep base value
81
- continue
91
+ # None means inherit, including when the result owner changes.
92
+ if result_owner is override:
93
+ merge_values[field_name] = base_value
82
94
  elif is_dataclass(override_value):
83
95
  # Recursively merge nested dataclass
84
96
  if base_value is not None and is_dataclass(base_value):
@@ -89,13 +101,12 @@ def _merge_nested_dataclass(base, override, mask_with_none: bool = False):
89
101
  # Concrete value - use override
90
102
  merge_values[field_name] = override_value
91
103
 
92
- # Merge with base using replace_raw to preserve None values
104
+ # Merge on the nominal owner using replace_raw to preserve None values
93
105
  # (dataclasses.replace triggers lazy resolution, baking in resolved values)
94
106
  if merge_values:
95
- from objectstate.lazy_factory import replace_raw
96
- return replace_raw(base, **merge_values)
107
+ return replace_raw(result_owner, **merge_values)
97
108
  else:
98
- return base
109
+ return result_owner
99
110
 
100
111
 
101
112
  @contextmanager
@@ -795,6 +795,11 @@ class LazyMethodBindings:
795
795
  class LazyDataclassFactory:
796
796
  """Generic factory for creating lazy dataclasses with flexible resolution."""
797
797
 
798
+ @staticmethod
799
+ def registered_type_pairs() -> tuple[tuple[type, type], ...]:
800
+ """Project current lazy/public declarations without constructing values."""
801
+ return tuple(_lazy_type_registry.items())
802
+
798
803
 
799
804
 
800
805