From 8a1abb5a615c15471efdd104c5d8681de60213b4 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 14 Aug 2024 13:04:01 -0400 Subject: [PATCH 1/3] add _IgnoreCustomTagsLoader and use it in load_yaml --- asdf/_tests/test_util.py | 14 ++++++++++++++ asdf/util.py | 4 ++-- asdf/yamlutil.py | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/asdf/_tests/test_util.py b/asdf/_tests/test_util.py index e45d68eb7..ca64a7a41 100644 --- a/asdf/_tests/test_util.py +++ b/asdf/_tests/test_util.py @@ -145,3 +145,17 @@ def test_load_yaml(tmp_path, input_type, tagged): assert isinstance(tree["a"], asdf.tagged.TaggedDict) else: assert not isinstance(tree["a"], asdf.tagged.TaggedDict) + + +@pytest.mark.parametrize("tagged", [True, False]) +def test_load_yaml_recursion(tmp_path, tagged): + fn = tmp_path / "test.asdf" + tree = {} + tree["d"] = {} + tree["d"]["d"] = tree["d"] + tree["l"] = [] + tree["l"].append(tree["l"]) + asdf.AsdfFile(tree).write_to(fn) + tree = util.load_yaml(fn, tagged=tagged) + assert tree["d"]["d"] is tree["d"] + assert tree["l"][0] is tree["l"] diff --git a/asdf/util.py b/asdf/util.py index bbd873d99..b099213bc 100644 --- a/asdf/util.py +++ b/asdf/util.py @@ -86,12 +86,12 @@ def load_yaml(init, tagged=False): """ from .generic_io import get_file - from .yamlutil import AsdfLoader + from .yamlutil import AsdfLoader, _IgnoreCustomTagsLoader if tagged: loader = AsdfLoader else: - loader = yaml.CBaseLoader if getattr(yaml, "__with_libyaml__", None) else yaml.BaseLoader + loader = _IgnoreCustomTagsLoader with get_file(init, "r") as gf: reader = gf.reader_until( diff --git a/asdf/yamlutil.py b/asdf/yamlutil.py index 63c2d864a..718bfecc2 100644 --- a/asdf/yamlutil.py +++ b/asdf/yamlutil.py @@ -137,6 +137,29 @@ def represent_numpy_str(dumper, data): AsdfDumper.add_representer(np.bytes_, AsdfDumper.represent_binary) +class _IgnoreCustomTagsLoader(_yaml_base_loader): + """ + A specialized YAML loader that ignores tags unknown to the + base (safe) loader. This is used by `asdf.util.load_yaml` + to read the ASDF tree as "basic" objects, ignoring the + custom tags. + """ + + def construct_undefined(self, node): + if isinstance(node, yaml.MappingNode): + return self.construct_mapping(node) + elif isinstance(node, yaml.SequenceNode): + return self.construct_sequence(node) + elif isinstance(node, yaml.ScalarNode): + return self.construct_scalar(node) + return super().construct_undefined(node) + + +# pyyaml will invoke the constructor associated with None when a node's +# tag is not explicitly handled by another constructor. +_IgnoreCustomTagsLoader.add_constructor(None, _IgnoreCustomTagsLoader.construct_undefined) + + class AsdfLoader(_yaml_base_loader): """ A specialized YAML loader that can construct "tagged basic Python From e1cdbbc0fb38bf91b04e71ae7381517d576bbdc3 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 14 Aug 2024 13:06:32 -0400 Subject: [PATCH 2/3] add changelog --- changes/1819.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1819.bugfix.rst diff --git a/changes/1819.bugfix.rst b/changes/1819.bugfix.rst new file mode 100644 index 000000000..25782b574 --- /dev/null +++ b/changes/1819.bugfix.rst @@ -0,0 +1 @@ +Allow ``asdf.util.load_yaml`` to handle recursive objects From 196156fc42e3a258ca6fe5bd27c2d3ef1c90404a Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 19 Aug 2024 16:03:43 -0400 Subject: [PATCH 3/3] fix changelog fragment name --- changes/{1819.bugfix.rst => 1825.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changes/{1819.bugfix.rst => 1825.bugfix.rst} (100%) diff --git a/changes/1819.bugfix.rst b/changes/1825.bugfix.rst similarity index 100% rename from changes/1819.bugfix.rst rename to changes/1825.bugfix.rst