From 0956ef3230a209326b1b6553269735a28ec4778e Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 18 Jul 2023 10:05:14 -0400 Subject: [PATCH] fix Converter type indexing Prior to #1561 the types supported by a converter that doesn't support any tags that are supported by the extnesion were ignored. The modifications in that PR checked if the converter implemented 'select_tag' and if not ignored the types. However if the converter implemented 'select_tag' the converter types were indexed (as the converter could return None from 'select_tag' and defer to a new converter). If a converter inherits from Converter it gains the select_tag method from the parent Converter class (which provides documentation of the method but doesn't implement functionality that is otherwise covered by ConverterProxy). This creates an issue when indexing the converter tags/types if a converter is included in an extension that doesn't list support for the tags supported by the converter. The changes included here check if 'select_tag' is implemented by a subclass of Converter. If overwritten, the types of the converter are indexed (to allow the converter to defer conversion). However, if 'select_tag' is merely inherited by Converter, the types supported by the converter are ignored (agreeing with behavior prior to #1561). --- asdf/_tests/test_extension.py | 33 +++++++++++++++++++++++++++++++++ asdf/extension/_converter.py | 23 ++++++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/asdf/_tests/test_extension.py b/asdf/_tests/test_extension.py index d92d0041b..ef472aa36 100644 --- a/asdf/_tests/test_extension.py +++ b/asdf/_tests/test_extension.py @@ -1,5 +1,6 @@ import pytest from packaging.specifiers import SpecifierSet +from yaml.representer import RepresenterError from asdf import AsdfFile, config_context from asdf._tests._helpers import assert_extension_correctness @@ -599,6 +600,38 @@ def test_converter_proxy(): ConverterProxy(MinimumConverter(tags=[extension.tags[0].tag_uri], types=[object()]), extension) +def test_converter_subclass_with_no_supported_tags(): + """ + Adding a Converter to an Extension that doesn't list support for the tags + associated with the Converter should not index the types listed for the + Converter. + """ + + class Foo: + pass + + class FooConverterWithSubclass(Converter): + tags = ["asdf://somewhere.org/tags/foo-1.0.0"] + types = [Foo] + + def to_yaml_tree(self, *args): + pass + + def from_yaml_tree(self, *args): + pass + + class FooExtension(Extension): + tags = [] + converters = [FooConverterWithSubclass()] + extension_uri = "asdf://somewhere.org/extensions/foo-1.0.0" + + tree = {"obj": Foo()} + with config_context() as cfg: + cfg.add_extension(FooExtension()) + with pytest.raises(RepresenterError, match=r"cannot represent an object"): + roundtrip_object(tree) + + def test_get_cached_asdf_extension_list(): extension = LegacyExtension() extension_list = get_cached_asdf_extension_list([extension]) diff --git a/asdf/extension/_converter.py b/asdf/extension/_converter.py index 1966df0a6..cb98b2644 100644 --- a/asdf/extension/_converter.py +++ b/asdf/extension/_converter.py @@ -3,6 +3,7 @@ types. Will eventually replace the `asdf.types` module. """ import abc +import inspect from asdf.util import get_class_name, uri_match @@ -211,9 +212,25 @@ def __init__(self, delegate, extension): self._types = [] - if not len(self._tags) and not hasattr(delegate, "select_tag"): - # this converter supports no tags so don't inspect the types - return + if not len(self._tags): + # this converter supports no tags supported by the extension + # before indexing it's types we need to check select_tag + + # check if it implements select_tag (and not just because it + # subclasses Converter) + if not hasattr(delegate, "select_tag"): + return + + # find which class implements select_tag + for class_ in inspect.getmro(delegate.__class__): + if "select_tag" in class_.__dict__: + if class_ is not Converter: + # a non-Converter class implements select_tag so consider the types for this Converter + break + else: + # this converter implements select_tag only because it inherits from Converter + # don't index it's types + return for typ in delegate.types: if isinstance(typ, (str, type)):