Skip to content

Fix Variable.clone() to preserve update_variable-inherited attributes (#502)#511

Draft
anth-volk wants to merge 6 commits into
masterfrom
fix/variable-clone-update-variable-502
Draft

Fix Variable.clone() to preserve update_variable-inherited attributes (#502)#511
anth-volk wants to merge 6 commits into
masterfrom
fix/variable-clone-update-variable-502

Conversation

@anth-volk

@anth-volk anth-volk commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #502

Summary

Variable.clone() re-ran __init__ with baseline_variable=None, dropping every attribute a reform's update_variable merged in from the baseline (value_type, entity, formulas, …) and raising ValueError: Missing attribute 'value_type'. Because TaxBenefitSystem.clone() clones every variable — and the YAML test runner + branch calculations clone the system — any reform using update_variable crashed on clone.

Fix

clone() now mirrors TaxBenefitSystem.clone(): empty_clone + a __dict__ copy, preserving the variable's merged (baseline + override) state instead of re-deriving it. formulas/metadata are copied so the clone stays independent of the original (the prior __init__-based clone produced fresh containers). Not re-running __init__ also avoids a spurious check_computation_modes() re-validation.

Verified safe for all three Variable.clone() callers (get_annualised_variable, get_neutralized_variable, TaxBenefitSystem.clone) — they reassign containers or mutate scalars, never mutate formulas/metadata in place. No callers in policyengine-us/uk.

Commits (TDD)

  1. Characterization tests for clone() (green on unchanged code).
  2. Variable.clone() fails for variables registered via update_variable #502 regression tests as strict xfail (label-only + adds-only overrides).
  3. The fix + un-xfail.
  4. End-to-end: clone a reformed TaxBenefitSystem (the test-runner/branch path) + a runtime-attribute preservation/independence test.
  5. Changelog fragment.

Verification

  • New tests/core/variables/test_variable_clone.py: 7 tests.
  • Full tests/core: 658 passed, 1 skipped, 1 xfailed (no regressions).
  • Issue repro now succeeds:
    system = reform(CountryTaxBenefitSystem())
    system.get_variable("disposable_income").clone()   # was ValueError

🤖 Generated with Claude Code

anth-volk and others added 6 commits July 8, 2026 20:56
Lock the existing clone() contract for normal variables (attributes
preserved, same class, independent mutable containers) before fixing #502.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
)

Cloning a variable registered via a reform's update_variable currently
raises "Missing attribute 'value_type'" because clone() re-runs __init__
without the baseline. Documented as strict xfail (label-only and adds-only
overrides) until the fix lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#502)

clone() re-ran __init__ with baseline_variable=None, dropping every attribute
a reform's update_variable merged in from the baseline (value_type, entity,
formulas, ...) and raising "Missing attribute 'value_type'". Cloning a
reformed tax-benefit system — which the YAML test runner and branch
calculations do for every variable — therefore crashed.

Clone now mirrors TaxBenefitSystem.clone(): empty_clone + __dict__ copy,
preserving the variable's merged state, with formulas/metadata copied so the
clone stays independent. The two #502 regression tests are un-xfailed.

Fixes #502.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an end-to-end test that clones a whole reformed TaxBenefitSystem (the
path the YAML test runner and branch calculations take), plus a test that a
runtime-set attribute (metadata) is preserved by clone() and stays
independent of the original.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Docstring now states that formulas and metadata are copied (adds/subtracts
are shared, as before) and that baseline_variable is retained. Add tests for
the real reform paths that clone under the hood — get_neutralized_variable
and get_annualized_variable of an update_variable variable — plus an Enum
variable clone and an assertion documenting adds sharing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Variable.clone() fails for variables registered via update_variable

1 participant