Skip to content

Commit

Permalink
Merge pull request #988 from eslavich/AL-524-asdftool-new-extension-api
Browse files Browse the repository at this point in the history
Update asdftool tags and asdftool extensions for new extension API
  • Loading branch information
eslavich authored May 5, 2021
2 parents 00d4f5a + ae8658f commit 7e8acee
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 64 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
filenames without recognizable extensions. Remove the ``asdf.asdf.is_asdf_file``
function. [#978]

- Update ``asdftool extensions`` and ``asdftool tags`` to incorporate
the new extension API. [#988]

2.7.5 (unreleased)
------------------

Expand Down
79 changes: 47 additions & 32 deletions asdf/commands/extension.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""
Implementation of command for reporting information about installed extensions.
"""

import sys
from pkg_resources import iter_entry_points

from .main import Command
from ..entry_points import get_extensions


__all__ = ['find_extensions']


class QueryExtension(Command): # pragma: no cover
"""This class is the plugin implementation for the asdftool runner."""
@classmethod
Expand All @@ -17,10 +16,11 @@ def setup_arguments(cls, subparsers):
"extensions", help="Show information about installed extensions",
description="""Reports information about installed ASDF extensions""")

parser.add_argument(
display_group = parser.add_mutually_exclusive_group()
display_group.add_argument(
"-s", "--summary", action="store_true",
help="Display only the installed extensions themselves")
parser.add_argument(
display_group.add_argument(
"-t", "--tags-only", action="store_true",
help="Display tags from installed extensions, but no other information")

Expand All @@ -29,43 +29,58 @@ def setup_arguments(cls, subparsers):

@classmethod
def run(cls, args):
if args.summary and args.tags_only:
sys.stderr.write(
"ERROR: Options -s/--summary and -t/--tags-only are not compatible\n")
return 1

return find_extensions(args.summary, args.tags_only)


def _format_entry_point(ep):
extension_class = "{}.{}".format(ep.module_name, ep.attrs[0])
return "Extension Name: '{}' (from {}) Class: {}".format(
ep.name, ep.dist, extension_class)

def _format_extension(ext):
if ext.extension_uri is None:
uri = "(none)"
else:
uri = f"'{ext.extension_uri}'"

def _format_type_name(typ):
return "{}.{}".format(typ.__module__, typ.__name__)
return "Extension URI: {} package: {} ({}) class: {}".format(
uri, ext.package_name, ext.package_version, ext.class_name
)


def _tag_comparator(a, b):
return _format_type_name(a) < _format_type_name(b)
def _format_type_name(typ):
if isinstance(typ, str):
return typ
else:
return "{}.{}".format(typ.__module__, typ.__name__)


def _print_extension_details(ext, tags_only):
for typ in sorted(ext.types, key=lambda x: _format_type_name(x)):
if typ.name is not None:
print("- " + _format_type_name(typ))
if not tags_only:
print(" implements: {}".format(typ.make_yaml_tag(typ.name)))
if typ.types:
print(" serializes:")
for name in typ.types:
print(" - {}".format(_format_type_name(name)))
tag_uris = [t.tag_uri for t in ext.tags]
for typ in ext.types:
if isinstance(typ.name, list):
for name in typ.name:
tag_uris.append(typ.make_yaml_tag(name))
elif typ.name is not None:
tag_uris.append(typ.make_yaml_tag(typ.name))

if len(tag_uris) > 0:
print("tags:")
for tag_uri in sorted(tag_uris):
print(" - " + tag_uri)

if not tags_only:
types = []
for converter in ext.converters:
for typ in converter.types:
types.append(typ)
for typ in ext.types:
types.extend(typ.types)

if len(types) > 0:
print("types:")
for typ in sorted(types, key=_format_type_name):
print(" - " + _format_type_name((typ)))


def find_extensions(summary, tags_only):
for ep in iter_entry_points(group='asdf_extensions'):
print(_format_entry_point(ep))
for ext in get_extensions():
print(_format_extension(ext))
if not summary:
_print_extension_details(ep.load()(), tags_only)
_print_extension_details(ext, tags_only)
print()
21 changes: 15 additions & 6 deletions asdf/commands/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,26 @@ def setup_arguments(cls, subparsers):
def run(cls, args):
return list_tags(display_classes=args.display_classes)

def _qualified_name(_class):
return "{}.{}".format(_class.__module__, _class.__name__)

def _format_type(typ):
if isinstance(typ, str):
return typ
else:
return "{}.{}".format(typ.__module__, typ.__name__)


def list_tags(display_classes=False, iostream=sys.stdout):
"""Function to list tags"""
af = AsdfFile()
type_by_tag = af.type_index._type_by_tag
tags = sorted(type_by_tag.keys())

for tag in tags:
tag_pairs = []
for tag in af.extension_manager._tag_defs_by_tag:
tag_pairs.append((tag, af.extension_manager.get_converter_for_tag(tag).types))
for tag in af.type_index._type_by_tag:
tag_pairs.append((tag, [af.type_index._type_by_tag[tag]]))

for tag, types in sorted(tag_pairs, key=lambda pair: pair[0]):
string = str(tag)
if display_classes:
string += ": " + _qualified_name(type_by_tag[tag])
string += ": " + ", ".join(_format_type(t) for t in types)
iostream.write(string + '\n')
16 changes: 16 additions & 0 deletions asdf/commands/tests/test_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest

from .. import find_extensions


@pytest.mark.parametrize("summary", [True, False])
@pytest.mark.parametrize("tags_only", [True, False])
def test_parameter_combinations(summary, tags_only):
# Just confirming no errors:
find_extensions(summary, tags_only)


def test_builtin_extension_included(capsys):
find_extensions(True, False)
captured = capsys.readouterr()
assert "asdf.extension.BuiltinExtension" in captured.out
41 changes: 15 additions & 26 deletions asdf/commands/tests/test_tags.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
import io

import pytest

from ... import AsdfFile
from .. import list_tags

def _get_tags(display_classes):
iostream = io.StringIO()
list_tags(display_classes=display_classes, iostream=iostream)
iostream.seek(0)
return [line.strip() for line in iostream.readlines()]

def _class_to_string(_class):
return "{}.{}".format(_class.__module__, _class.__name__)

def test_list_schemas():
obs_tags = _get_tags(False)

af = AsdfFile()
exp_tags = sorted(af.type_index._type_by_tag.keys())
@pytest.mark.parametrize("display_classes", [True, False])
def test_parameter_combinations(display_classes):
# Just confirming no errors:
list_tags(display_classes)

for exp, obs in zip(exp_tags, obs_tags):
assert exp == obs

def test_list_schemas_and_tags():
tag_lines = _get_tags(True)
def test_all_tags_present():
iostream = io.StringIO()
list_tags(iostream=iostream)
iostream.seek(0)
tags = {line.strip() for line in iostream.readlines()}

af = AsdfFile()
type_by_tag = af.type_index._type_by_tag
exp_tags = sorted(type_by_tag.keys())

for exp_tag, line in zip(exp_tags, tag_lines):
tag_name, tag_class = line.split(": ")
assert tag_name == exp_tag

exp_class = _class_to_string(type_by_tag[exp_tag])
assert tag_class == exp_class
for tag in af.type_index._type_by_tag:
assert tag in tags
for tag in af.extension_manager._tag_defs_by_tag:
assert tag in tags

0 comments on commit 7e8acee

Please sign in to comment.