Repository navigation
fix: make Data.__hash__ safe during pydantic deepcopy - #378
Conversation
TM.report() deep-copies model elements. When a Data object is carried
by more than one Dataflow there is a reference cycle
(Data -> carriedBy -> Dataflow -> data (DataSet) -> Data). Pydantic v2's
BaseModel.__deepcopy__ registers the new instance in the memo before its
fields are copied, so rebuilding the DataSet hashes a Data that has no
attributes yet and raises:
AttributeError: 'Data' object has no attribute 'name'
Read the hashed fields with getattr() so hashing a partially built
instance does not raise, and add regression tests for deepcopy and
TM.report().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Failing under Python 3.12: __________________ TestData.test_deepcopy_with_dataflow_cycle __________________
E assert False
|
test_deepcopy_with_dataflow_cycle checked that the copied Dataflow's data is the very same copy of the Data. That holds only from pydantic 2.14, whose __deepcopy__ memoizes the new instance before copying its fields (which is also what triggers the hashing bug). With the pydantic 2.12.5 in poetry.lock, the cycle produces a second copy, so the test failed in CI. Check instead that the flow carries a copy of the token, not the original. The test still fails without the fix on pydantic 2.14 and passes on 2.12, 2.13 and 2.14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for catching this. The failure comes from the pydantic version rather than Python 3.12: CI fails on every Python version because The bug itself only happens from pydantic 2.14, whose f9f6b91 changes the test to check that the flow carries a copy of the token rather than the original, which holds on every version. The test suite passes with pydantic 2.12.5, 2.13.5 and 2.14.3, and the regression test still fails without the fix on 2.14.3. |
Fixes #377
Summary
TM.report()raisedAttributeError: 'Data' object has no attribute 'name'for any model where oneDataobject is carried by more than oneDataflow.The cause is a reference cycle,
Data -> carriedBy -> Dataflow -> data (DataSet) -> Data. Pydantic v2'sBaseModel.__deepcopy__registers the new instance in the memo before copying its fields, so theDataSetcopy hashed aDatawith no attributes yet.Changes
pytm/data.py:Data.__hash__now reads its fields withgetattr(..., None), so hashing a partially built instance cannot raise.tests/test_pydantic_models.py: two regression tests:test_deepcopy_with_dataflow_cycle: callscopy.deepcopyon aDatathat is part of acarriedBy↔Dataflow.datacycle.test_report_with_data_carried_by_multiple_flows: end-to-end test ofcheck()→resolve()→report()with oneDataon two flows.Testing
masterwith theAttributeErrorand pass with this change.pytest tests→ 263 passed.Known limitation
In the copied graph, the copied
Datais stored in the copiedDataSetunder the hash it had while its fields were empty. Iterating the set works, butinchecks on the copy returnFalse. Report rendering doesn't depend on this; details are in the linked issue.🤖 Generated with Claude Code