diff --git a/asdf/_tests/test_extension.py b/asdf/_tests/test_extension.py index f9b8baf50..4f73dccbe 100644 --- a/asdf/_tests/test_extension.py +++ b/asdf/_tests/test_extension.py @@ -1,3 +1,4 @@ +import collections import fractions import sys @@ -1049,3 +1050,33 @@ class MailboxExtension: converter = extension_manager.get_converter_for_type(mailbox.Mailbox) assert isinstance(converter.delegate, MailboxConverter) + + +def test_named_tuple_extension(): + Point = collections.namedtuple("Point", ["x", "y"]) + + class PointConverter: + tags = ["asdf://example.com/tags/point-1.0.0"] + types = [Point] + + def to_yaml_tree(self, obj, tag, ctx): + return list(obj) + + def from_yaml_tree(self, node, tag, ctx): + return Point(*node) + + class PointExtension: + tags = PointConverter.tags + converters = [PointConverter()] + extension_uri = "asdf://example.com/extensions/point-1.0.0" + + pt = Point(1, 2) + + # without the extension we can't serialize this + with pytest.raises(AsdfSerializationError, match="is not serializable by asdf"): + roundtrip_object(pt) + + with config_context() as cfg: + cfg.add_extension(PointExtension()) + rpt = roundtrip_object(pt) + assert isinstance(rpt, Point) diff --git a/asdf/treeutil.py b/asdf/treeutil.py index fef305398..df013911a 100644 --- a/asdf/treeutil.py +++ b/asdf/treeutil.py @@ -354,7 +354,9 @@ def _handle_immutable_sequence(node, json_id): def _handle_children(node, json_id): if isinstance(node, (dict, lazy_nodes.AsdfDictNode)): result = _handle_mapping(node, json_id) - elif isinstance(node, tuple): + # don't treat namedtuple instances as tuples + # see: https://github.com/python/cpython/issues/52044 + elif isinstance(node, tuple) and not hasattr(node, "_fields"): result = _handle_immutable_sequence(node, json_id) elif isinstance(node, (list, lazy_nodes.AsdfListNode)): result = _handle_mutable_sequence(node, json_id)