Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
exclude: "asdf/(_extern||_jsonschema)/.*"
exclude: "(asdf/(_extern||_jsonschema)/.*)|(.*\\.ambr)"
repos:

- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
24 changes: 24 additions & 0 deletions asdf/_asdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,7 @@ def info(
max_rows=display.DEFAULT_MAX_ROWS,
max_cols=display.DEFAULT_MAX_COLS,
show_values=display.DEFAULT_SHOW_VALUES,
show_blocks=False,
):
"""
Print a rendering of this file's tree to stdout.
Expand All @@ -1190,6 +1191,10 @@ def info(
show_values : bool, optional
Set to False to disable display of primitive values in
the rendered tree.

show_blocks: bool, optional
Set to True to also print a table of block header fields
for each block in the file.
"""
lines = display.render_tree(
self.tree,
Expand All @@ -1201,6 +1206,25 @@ def info(
)
print("\n".join(lines))

if show_blocks:
for i, block in enumerate(self._blocks.blocks):
if block.header["compression"] == b"\0\0\0\0":
compression = "None"
else:
compression = block.header["compression"].rstrip(b"\0").decode("ascii", errors="backslashreplace")
compression = f'"{compression}"'

items = [
("flags", f"{block.header['flags']:#010x}"),
("compression", compression),
("allocated_size", str(block.header["allocated_size"])),
("used_size", str(block.header["used_size"])),
("data_size", str(block.header["data_size"])),
("checksum", block.header["checksum"].hex()),
]
rows = display.render_table(f"Block #{i}", items)
print("\n".join(rows))

def search(self, key=NotSet, type_=NotSet, value=NotSet, filter_=None):
"""
Search this file's tree.
Expand Down
11 changes: 8 additions & 3 deletions asdf/_commands/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ def setup_arguments(cls, subparsers):
)
parser.add_argument("--no-show-values", dest="show_values", action="store_false")
parser.set_defaults(show_values=True)
parser.add_argument(
"--show-blocks",
action="store_true",
help="Display a table of header information for each block in the file.",
)

parser.set_defaults(func=cls.run)

return parser

@classmethod
def run(cls, args):
info(args.filename, args.max_rows, args.max_cols, args.show_values)
info(args.filename, args.max_rows, args.max_cols, args.show_values, args.show_blocks)


def info(filename, max_rows, max_cols, show_values):
def info(filename, max_rows, max_cols, show_values, show_blocks):
with asdf.open(filename) as af:
af.info(max_rows, max_cols, show_values)
af.info(max_rows, max_cols, show_values, show_blocks=show_blocks)
18 changes: 14 additions & 4 deletions asdf/_convenience.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
around _display module code.
"""

import pathlib
from contextlib import contextmanager
from pathlib import Path

from ._asdf import AsdfFile, open_asdf
from ._display import DEFAULT_MAX_COLS, DEFAULT_MAX_ROWS, DEFAULT_SHOW_VALUES

__all__ = ["info"]


def info(node_or_path, max_rows=DEFAULT_MAX_ROWS, max_cols=DEFAULT_MAX_COLS, show_values=DEFAULT_SHOW_VALUES):
def info(
node_or_path,
max_rows=DEFAULT_MAX_ROWS,
max_cols=DEFAULT_MAX_COLS,
show_values=DEFAULT_SHOW_VALUES,
show_blocks=False,
):
"""
Print a rendering of an ASDF tree or sub-tree to stdout.

Expand All @@ -39,14 +45,18 @@ def info(node_or_path, max_rows=DEFAULT_MAX_ROWS, max_cols=DEFAULT_MAX_COLS, sho
show_values : bool, optional
Set to False to disable display of primitive values in
the rendered tree.

show_blocks: bool, optional
Set to True to also print a table of block header fields
for each block in the file.
"""
with _manage_node(node_or_path) as node:
node.info(max_rows=max_rows, max_cols=max_cols, show_values=show_values)
node.info(max_rows=max_rows, max_cols=max_cols, show_values=show_values, show_blocks=show_blocks)


@contextmanager
def _manage_node(node_or_path):
if isinstance(node_or_path, (str, pathlib.Path)):
if isinstance(node_or_path, (str, Path)):
with open_asdf(node_or_path) as af:
yield af

Expand Down
25 changes: 25 additions & 0 deletions asdf/_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"DEFAULT_MAX_COLS",
"DEFAULT_MAX_ROWS",
"DEFAULT_SHOW_VALUES",
"render_table",
"render_tree",
]

Expand All @@ -25,6 +26,30 @@
DEFAULT_SHOW_VALUES = True


def render_table(
Comment thread
braingram marked this conversation as resolved.
title,
rows,
):
"""Generate a simple 2 column table with title header."""
# TODO: table formatting will break if the terminal is narrower than the content.
# We could consider using shutil.get_terminal size to adjust the width of the table.

# TODO: this doesn't account for unicode glyphs that aren't 1 column wide
key_width = max(len(str(row[0])) for row in rows) + 2
val_width = max(len(str(row[1])) for row in rows) + 2
inner_width = key_width + val_width + 1

# Format each row with key left-aligned and value centered
content = [f"┃ {key:<{key_width-2}} │ {value:^{val_width-2}} ┃" for key, value in rows]
return [
"┏" + "━" * inner_width + "┓",
f"┃{title:^{inner_width}s}┃", # Centered table title
"┣" + "━" * key_width + "┯" + "━" * val_width + "┫",
*content,
"┗" + "━" * key_width + "┷" + "━" * val_width + "┛",
]


def render_tree(
node,
max_rows=DEFAULT_MAX_ROWS,
Expand Down
55 changes: 55 additions & 0 deletions asdf/_tests/__snapshots__/test_info.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# serializer version: 1
# name: test_info_blocks_hide
'''
Some nodes not shown.

'''
# ---
# name: test_info_blocks_show[ndarray0.asdf]
'''
Some nodes not shown.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #0 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ None ┃
┃ allocated_size │ 16 ┃
┃ used_size │ 16 ┃
┃ data_size │ 16 ┃
┃ checksum │ 6e5a5ddda2f6c38efe929c54a1ab80bd ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

'''
# ---
# name: test_info_blocks_show[ndarray2.asdf]
'''
Some nodes not shown.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #0 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ "lz4" ┃
┃ allocated_size │ 8192 ┃
┃ used_size │ 22 ┃
┃ data_size │ 16 ┃
┃ checksum │ 6e5a5ddda2f6c38efe929c54a1ab80bd ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #1 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ "bzp2" ┃
┃ allocated_size │ 8192 ┃
┃ used_size │ 42 ┃
┃ data_size │ 24 ┃
┃ checksum │ aa341a15f5ade44faafbe190f98c2587 ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

'''
# ---
# name: test_info_blocks_show[simple_inline_array0.asdf]
'''
Some nodes not shown.

'''
# ---
55 changes: 55 additions & 0 deletions asdf/_tests/commands/__snapshots__/test_info.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# serializer version: 1
# name: test_info_command_blocks_hide
'''
Some nodes not shown.

'''
# ---
# name: test_info_command_blocks_show[ndarray0.asdf]
'''
Some nodes not shown.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #0 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ None ┃
┃ allocated_size │ 16 ┃
┃ used_size │ 16 ┃
┃ data_size │ 16 ┃
┃ checksum │ 6e5a5ddda2f6c38efe929c54a1ab80bd ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

'''
# ---
# name: test_info_command_blocks_show[ndarray2.asdf]
'''
Some nodes not shown.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #0 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ "lz4" ┃
┃ allocated_size │ 8192 ┃
┃ used_size │ 22 ┃
┃ data_size │ 16 ┃
┃ checksum │ 6e5a5ddda2f6c38efe929c54a1ab80bd ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Block #1 ┃
┣━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ flags │ 0x00000000 ┃
┃ compression │ "bzp2" ┃
┃ allocated_size │ 8192 ┃
┃ used_size │ 42 ┃
┃ data_size │ 24 ┃
┃ checksum │ aa341a15f5ade44faafbe190f98c2587 ┃
┗━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

'''
# ---
# name: test_info_command_blocks_show[simple_inline_array0.asdf]
'''
Some nodes not shown.

'''
# ---
23 changes: 23 additions & 0 deletions asdf/_tests/commands/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,26 @@ def test_info_command(capsys, test_data_path):
assert "frames" in captured.out
new_len = len(captured.out.split("\n"))
assert new_len < original_len


@pytest.mark.parametrize("filename", ["ndarray0.asdf", "ndarray2.asdf", "simple_inline_array0.asdf"])
def test_info_command_blocks_show(capsys, test_data_path, filename, snapshot):
"""Verify block output for files with different numbers and types of blocks."""

file_path = test_data_path / filename
# Set max_rows to zero to ignore any changes in tree formatting when computing snapshots
assert main.main_from_args(["info", "--max-rows=0", "--show-blocks", str(file_path)]) == 0
captured = capsys.readouterr()
# Run `pytest --snapshot-update` to update stored snapshot
assert captured.out == snapshot


def test_info_command_blocks_hide(capsys, test_data_path, snapshot):
"""Verify no block output is shown by default when the file contains blocks."""

file_path = test_data_path / "ndarray0.asdf"
# Set max_rows to zero to ignore any changes in tree formatting when computing snapshots
assert main.main_from_args(["info", "--max-rows=0", str(file_path)]) == 0
captured = capsys.readouterr()
# Run `pytest --snapshot-update` to update stored snapshot
assert captured.out == snapshot
Binary file added asdf/_tests/data/ndarray2.asdf
Comment thread
braingram marked this conversation as resolved.
Binary file not shown.
37 changes: 37 additions & 0 deletions asdf/_tests/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,3 +832,40 @@ def test_info_no_infinite_loop(capsys):
af.info()
captured = capsys.readouterr()
assert "recursive" in captured.out


def _get_block_output(capsys, af, *args, **kwargs) -> str:
"""Passes provided arguments to both `asdf.info` and `AsdfFile.info` and verifies the outputs match.

Returns the unified captured output.
"""
# Set max_rows to zero to ignore any changes in tree formatting when computing snapshots
af.info(*args, max_rows=0, **kwargs)
method_cap = capsys.readouterr()

asdf.info(af, *args, max_rows=0, **kwargs)
mod_cap = capsys.readouterr()

assert method_cap.out == mod_cap.out, "AsdfFile.info and asdf.info outputs do not match"
return method_cap.out


@pytest.mark.parametrize("filename", ["ndarray0.asdf", "ndarray2.asdf", "simple_inline_array0.asdf"])
def test_info_blocks_show(capsys, test_data_path, filename, snapshot):
"""Verify block output for files with different numbers and types of blocks."""

file_path = test_data_path / filename
with asdf.open(file_path) as af:
out = _get_block_output(capsys, af, show_blocks=True)
# Run `pytest --snapshot-update` to update stored snapshot
assert out == snapshot


def test_info_blocks_hide(capsys, test_data_path, snapshot):
"""Verify no block output is shown by default when the file contains blocks."""

file_path = test_data_path / "ndarray0.asdf"
with asdf.open(file_path) as af:
out = _get_block_output(capsys, af)
# Run `pytest --snapshot-update` to update stored snapshot
assert out == snapshot
1 change: 1 addition & 0 deletions changes/2014.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added optional block info table to ``asdftool info`` and ``AsdfFile.info``.
Comment thread
sydduckworth marked this conversation as resolved.
2 changes: 1 addition & 1 deletion pyproject.toml
Comment thread
sydduckworth marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ docs = [
]
http = ["fsspec[http]>=2022.8.2"]
lz4 = ["lz4>=0.10"]
tests = ["asdf[all]", "psutil", "pytest>=8"]
tests = ["asdf[all]", "psutil", "pytest>=8", "syrupy>=5.1"]

benchmark = ["asdf[tests]", "pytest-benchmark"]
test = ["asdf[tests]"]
Expand Down
Loading