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
38 changes: 32 additions & 6 deletions asdf/_tests/test_lazy_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,20 @@ def test_node_empty_init(NodeClass, base):


@pytest.mark.parametrize(
"node",
"node,base_type",
[
AsdfDictNode({"a": 1, "b": 2}),
AsdfListNode([1, 2, 3]),
AsdfOrderedDictNode({"a": 1, "b": 2}),
(AsdfDictNode({"a": 1, "b": 2}), dict),
(AsdfListNode([1, 2, 3]), list),
(AsdfOrderedDictNode({"a": 1, "b": 2}), collections.OrderedDict),
],
)
@pytest.mark.parametrize("copy_operation", [copy.copy, copy.deepcopy])
def test_copy(node, copy_operation):
def test_copy(node, base_type, copy_operation):
copied_node = copy_operation(node)
assert isinstance(copied_node, type(node))
if copy_operation is copy.copy:
assert type(copied_node) is type(node)
else:
assert type(copied_node) is base_type
assert copied_node == node


Expand Down Expand Up @@ -408,3 +411,26 @@ def test_lazy_generator_converter(tmp_path, lazy_generator_class):

with asdf.open(fn, lazy_tree=True) as af:
assert isinstance(af["obj"].data, dict)


def test_lazy_copy(tmp_path):
"""
Test that copying an AsdfFile instance with a lazy
tree doesn't result in the copy retaining references
to the instance.
"""
fn = tmp_path / "test.asdf"
obj = asdf.tags.core.IntegerType(1)
tree = {"a": {"b": obj}}
# make a recursive structure
tree["a"]["c"] = tree["a"]

asdf.AsdfFile(tree).write_to(fn)

with asdf.open(fn, lazy_tree=True) as af:
af2 = af.copy()
Comment on lines +430 to +431
Copy link
Copy Markdown
Member

@zacharyburnett zacharyburnett May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also test multiple files open in context managers? To replicate the original issue (or was that using file handles)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The original issue can be seen with the following example:

import gc
import roman_datamodels.datamodels as rdm
dst = rdm.ImageModel()
with rdm.open("some_cal.asdf") as src:
    dst.meta = src.meta.copy()
del src
gc.collect(2)
dst.meta.ref_file  # ok since the copy is shallow
dst.meta.ref_file.crds  # fails due to the sub node `crds` not being resolved (made not lazy) during the copy

With this PR that example runs without failure.
What did you have in mind for the multiple files?
I tried to flush out this test with a few "gotchas" (the recursive reference, etc) but it's far from exhaustive. Thanks for the idea!


del af
gc.collect(2)
assert af2["a"]["b"] == obj
assert af2["a"]["c"]["b"] is af2["a"]["b"]
5 changes: 4 additions & 1 deletion asdf/lazy_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import warnings
import weakref

from . import tagged, yamlutil
from . import tagged, treeutil, yamlutil
from .exceptions import AsdfConversionWarning, AsdfLazyReferenceError
from .extension._serialization_context import BlockAccess

Expand Down Expand Up @@ -137,6 +137,9 @@ def tagged(self):
"""
return self.data

def __deepcopy__(self, memo):
return treeutil.walk_and_modify(self, lambda n: n)

def _convert_and_cache(self, value, key):
"""
Convert ``value`` to either:
Expand Down
5 changes: 4 additions & 1 deletion asdf/treeutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Utility functions for managing tree-like data structures.
"""

import collections
import types
from contextlib import contextmanager

Expand Down Expand Up @@ -278,7 +279,9 @@ def _handle_callback(node, json_id):
return _handle_generator(result)

def _handle_mapping(node, json_id):
if isinstance(node, lazy_nodes.AsdfDictNode):
if isinstance(node, lazy_nodes.AsdfOrderedDictNode):
result = collections.OrderedDict()
elif isinstance(node, lazy_nodes.AsdfDictNode):
result = {}
else:
result = node.__class__()
Expand Down
1 change: 1 addition & 0 deletions changes/1922.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix deepcopy of lazy tree.
Loading