Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .verification/lazy-field-read/delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FieldProxy follows ParameterStructure's existing admitted paths instead of re-reading dataclass annotations during each attribute access. Containers remain proxies; only leaves resolve through ObjectState. get_resolved_value uses the same extracted child topology instead of scanning every resolved key. No extra value cache.

Native application succeeded. One direct native ObjectState control read a nested leaf, edited it through update_parameter, then read its updated value. First attempt omitted set_base_config_type and failed during setup; corrected application initialization passed. No suite, profile, or speed measurement. Toad must consume state.fields instead of reconstructing an object.
31 changes: 31 additions & 0 deletions .verification/lazy-field-read/fix.plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"recipes": [
{
"recipe_id": "read-extracted-field-topology",
"operations": [
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P192--objectstate/src/objectstate/object_state.py",
"target_qualname": "FieldProxy.__getattr__",
"replacements": [
{
"old_source": " def __getattr__(self, name: str) -> Any:\n \"\"\"Get field value or nested FieldProxy.\n\n Args:\n name: Field name to access\n\n Returns:\n FieldProxy for nested dataclass fields, or resolved value for leaf fields\n \"\"\"\n new_path = f'{self._path}.{name}' if self._path else name\n\n # Get field info from the type\n if not is_dataclass(self._type):\n type_name = getattr(self._type, '__name__', str(self._type))\n raise AttributeError(f\"{type_name} is not a dataclass\")\n\n field_info = None\n for f in dataclass_fields(self._type):\n if f.name == name:\n field_info = f\n break\n\n if field_info is None:\n type_name = getattr(self._type, '__name__', str(self._type))\n raise AttributeError(f\"{type_name} has no field '{name}'\")\n\n # Check if field is a nested dataclass\n field_type = field_info.type\n\n # Handle Optional[DataclassType]\n from typing import Union, get_args, get_origin\n origin = get_origin(field_type)\n if origin is Union:\n args = get_args(field_type)\n if len(args) == 2 and type(None) in args:\n inner_type = next(arg for arg in args if arg is not type(None))\n if is_dataclass(inner_type) and isinstance(inner_type, type):\n return FieldProxy(self._state, new_path, inner_type)\n\n # Handle direct dataclass type\n if isinstance(field_type, type) and is_dataclass(field_type):\n return FieldProxy(self._state, new_path, field_type)\n\n # Leaf field - get resolved value\n return self._state.get_resolved_value(new_path)",
"new_source": " def __getattr__(self, name: str) -> Any:\n \"\"\"Follow the state's extracted paths; resolve only the requested leaf.\"\"\"\n new_path = f'{self._path}.{name}' if self._path else name\n if new_path not in self._state.parameters:\n raise AttributeError(f\"ObjectState has no field {new_path!r}\")\n structure = self._state.parameter_structure\n if structure.direct_paths.get(DottedFieldPath(new_path)):\n return FieldProxy(self._state, new_path, structure.owner_paths[new_path])\n return self._state.get_resolved_value(new_path)"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P192--objectstate/src/objectstate/object_state.py",
"target_qualname": "ObjectState.get_resolved_value",
"replacements": [
{
"old_source": "prefix = f\"{param_name}.\"\n has_subfields = any(key.startswith(prefix) for key in self._live_resolved.keys())",
"new_source": "has_subfields = bool(self.parameter_structure.direct_paths.get(DottedFieldPath(param_name)))"
}
]
}
]
}
]
}
51 changes: 7 additions & 44 deletions src/objectstate/object_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,49 +67,13 @@ def __init__(self, state: 'ObjectState', path: str, field_type: type):
object.__setattr__(self, '_type', field_type)

def __getattr__(self, name: str) -> Any:
"""Get field value or nested FieldProxy.

Args:
name: Field name to access

Returns:
FieldProxy for nested dataclass fields, or resolved value for leaf fields
"""
"""Follow the state's extracted paths; resolve only the requested leaf."""
new_path = f'{self._path}.{name}' if self._path else name

# Get field info from the type
if not is_dataclass(self._type):
type_name = getattr(self._type, '__name__', str(self._type))
raise AttributeError(f"{type_name} is not a dataclass")

field_info = None
for f in dataclass_fields(self._type):
if f.name == name:
field_info = f
break

if field_info is None:
type_name = getattr(self._type, '__name__', str(self._type))
raise AttributeError(f"{type_name} has no field '{name}'")

# Check if field is a nested dataclass
field_type = field_info.type

# Handle Optional[DataclassType]
from typing import Union, get_args, get_origin
origin = get_origin(field_type)
if origin is Union:
args = get_args(field_type)
if len(args) == 2 and type(None) in args:
inner_type = next(arg for arg in args if arg is not type(None))
if is_dataclass(inner_type) and isinstance(inner_type, type):
return FieldProxy(self._state, new_path, inner_type)

# Handle direct dataclass type
if isinstance(field_type, type) and is_dataclass(field_type):
return FieldProxy(self._state, new_path, field_type)

# Leaf field - get resolved value
if new_path not in self._state.parameters:
raise AttributeError(f"ObjectState has no field {new_path!r}")
structure = self._state.parameter_structure
if structure.direct_paths.get(DottedFieldPath(new_path)):
return FieldProxy(self._state, new_path, structure.owner_paths[new_path])
return self._state.get_resolved_value(new_path)

def __setattr__(self, name: str, value: Any) -> None:
Expand Down Expand Up @@ -1198,8 +1162,7 @@ def get_resolved_value(self, param_name: str) -> Any:
assert self._live_resolved is not None # Guaranteed by _ensure_live_resolved

# Check if this is a container/dataclass field (has subfields in _live_resolved)
prefix = f"{param_name}."
has_subfields = any(key.startswith(prefix) for key in self._live_resolved.keys())
has_subfields = bool(self.parameter_structure.direct_paths.get(DottedFieldPath(param_name)))

if has_subfields:
# This is a container field - reconstruct's dataclass from live resolved values
Expand Down
Loading