diff --git a/asdf/_tests/test_extension.py b/asdf/_tests/test_extension.py index 6adc39a6b..59b37e311 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 @@ -607,6 +608,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 4044f9d5c..80a3ea341 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)):