From d02fc79caca7e5ad35e970589d4c439be02769e0 Mon Sep 17 00:00:00 2001 From: Sydney Duckworth Date: Mon, 30 Mar 2026 10:53:36 -0400 Subject: [PATCH 1/4] Added block information to `AsdfFile.info` method. * `info` now optionally prints a table containing block header metadata for each block in the source file * Added `show_blocks` parameter to enable/disable block tables which defaults to `False` --- asdf/_asdf.py | 29 ++++++++++++++++++++++++++--- asdf/_display.py | 25 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/asdf/_asdf.py b/asdf/_asdf.py index 32a3bf09c..33ac8309d 100644 --- a/asdf/_asdf.py +++ b/asdf/_asdf.py @@ -1164,9 +1164,10 @@ def schema_info(self, key="description", path=None, preserve_list=True): def info( self, - max_rows=display.DEFAULT_MAX_ROWS, - max_cols=display.DEFAULT_MAX_COLS, - show_values=display.DEFAULT_SHOW_VALUES, + max_rows: int | tuple[int, ...] | None = display.DEFAULT_MAX_ROWS, + max_cols: int | None = display.DEFAULT_MAX_COLS, + show_values: bool = display.DEFAULT_SHOW_VALUES, + show_blocks: bool = False, ): """ Print a rendering of this file's tree to stdout. @@ -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, @@ -1201,6 +1206,24 @@ 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"].decode() + + 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. diff --git a/asdf/_display.py b/asdf/_display.py index f1d02cf60..3015770fb 100644 --- a/asdf/_display.py +++ b/asdf/_display.py @@ -16,6 +16,7 @@ "DEFAULT_MAX_COLS", "DEFAULT_MAX_ROWS", "DEFAULT_SHOW_VALUES", + "render_table", "render_tree", ] @@ -25,6 +26,30 @@ DEFAULT_SHOW_VALUES = True +def render_table( + title: str, + rows: list[tuple[str, str]], +) -> list[str]: + """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, From 8655a4a9b224c005d5d7e931ca3404aa658290c9 Mon Sep 17 00:00:00 2001 From: Sydney Duckworth Date: Wed, 1 Apr 2026 09:13:50 -0400 Subject: [PATCH 2/4] Added `--show-blocks` flag to `info` CLI command --- asdf/_commands/info.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/asdf/_commands/info.py b/asdf/_commands/info.py index 91fef2697..5f0aed0cb 100644 --- a/asdf/_commands/info.py +++ b/asdf/_commands/info.py @@ -38,6 +38,11 @@ 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) @@ -45,9 +50,9 @@ def setup_arguments(cls, subparsers): @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) From 1249b1f3cd422935461b7e20397b83e484ceaa55 Mon Sep 17 00:00:00 2001 From: Sydney Duckworth Date: Wed, 1 Apr 2026 09:30:52 -0400 Subject: [PATCH 3/4] Added unit tests for block display --- asdf/_tests/commands/test_info.py | 27 +++++++++++++++++++++++++++ asdf/_tests/test_info.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/asdf/_tests/commands/test_info.py b/asdf/_tests/commands/test_info.py index f7490dcc8..12064cada 100644 --- a/asdf/_tests/commands/test_info.py +++ b/asdf/_tests/commands/test_info.py @@ -21,3 +21,30 @@ 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 + + +def test_info_command_blocks_show(capsys, test_data_path): + """Verify blocks are printed when `--show-blocks` is passed.""" + + file_path = test_data_path / "ndarray0.asdf" + assert main.main_from_args(["info", "--show-blocks", str(file_path)]) == 0 + captured = capsys.readouterr() + assert "Block #0" in captured.out + + +def test_info_command_blocks_hide(capsys, test_data_path): + """Verify no block output is shown by default when the file contains blocks.""" + + file_path = test_data_path / "ndarray0.asdf" + assert main.main_from_args(["info", str(file_path)]) == 0 + captured = capsys.readouterr() + assert "Block" not in captured.out + + +def test_info_command_no_blocks(capsys, test_data_path): + """Verify no block output is shown even with `--show-blocks` if file contains no blocks.""" + + file_path = test_data_path / "simple_inline_array0.asdf" + assert main.main_from_args(["info", "--show-blocks", str(file_path)]) == 0 + captured = capsys.readouterr() + assert "Block" not in captured.out diff --git a/asdf/_tests/test_info.py b/asdf/_tests/test_info.py index 21952008c..5fe363e19 100644 --- a/asdf/_tests/test_info.py +++ b/asdf/_tests/test_info.py @@ -832,3 +832,33 @@ def test_info_no_infinite_loop(capsys): af.info() captured = capsys.readouterr() assert "recursive" in captured.out + + +def test_info_blocks_show(capsys, test_data_path): + """Verify blocks are printed when `show_blocks=True`.""" + + file_path = test_data_path / "ndarray0.asdf" + with asdf.open(file_path) as af: + af.info(show_blocks=True) + captured = capsys.readouterr() + assert "Block #0" in captured.out + + +def test_info_blocks_hide(capsys, test_data_path): + """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: + af.info() + captured = capsys.readouterr() + assert "Block" not in captured.out + + +def test_info_no_blocks(capsys, test_data_path): + """Verify no block output is shown even with `show_blocks=True` if file contains no blocks.""" + + file_path = test_data_path / "simple_inline_array0.asdf" + with asdf.open(file_path) as af: + af.info(show_blocks=True) + captured = capsys.readouterr() + assert "Block" not in captured.out From b6650484a9bf314ebc621e39cecd4080fb46b48a Mon Sep 17 00:00:00 2001 From: Sydney Duckworth Date: Wed, 1 Apr 2026 09:36:35 -0400 Subject: [PATCH 4/4] Added changelog entry --- changes/2014.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2014.feature.rst diff --git a/changes/2014.feature.rst b/changes/2014.feature.rst new file mode 100644 index 000000000..24fb140fd --- /dev/null +++ b/changes/2014.feature.rst @@ -0,0 +1 @@ +Added optional block info table to ``asdftool info`` and ``AsdfFile.info``.