Skip to content

Commit 69bc46a

Browse files
authored
Merge pull request #1952 from braingram/asdftool_validate
add asdftool validate
2 parents 3356445 + 13351f2 commit 69bc46a

5 files changed

Lines changed: 147 additions & 0 deletions

File tree

asdf/_commands/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .search import search
88
from .tags import list_tags
99
from .to_yaml import to_yaml
10+
from .validate import validate
1011

1112
__all__ = [
1213
"defragment",
@@ -19,4 +20,5 @@
1920
"list_tags",
2021
"search",
2122
"to_yaml",
23+
"validate",
2224
]

asdf/_commands/validate.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Commands for validating ASDF files
3+
"""
4+
5+
import asdf
6+
7+
from .main import Command
8+
9+
__all__ = ["validate"]
10+
11+
12+
class Validate(Command):
13+
@classmethod
14+
def setup_arguments(cls, subparsers):
15+
parser = subparsers.add_parser(
16+
"validate",
17+
help="Validates an ASDF file.",
18+
description=(
19+
"Validates tree against all tagged schemas (and optionally a custom schema) "
20+
"and blocks with stored checksums."
21+
),
22+
)
23+
24+
parser.add_argument("filename", help="path to ASDF file")
25+
parser.add_argument(
26+
"--custom-schema",
27+
type=str,
28+
help="path or URI of custom schema",
29+
)
30+
parser.add_argument(
31+
"--skip-block-validation",
32+
default=False,
33+
action="store_true",
34+
help="Do not compare blocks against stored checksums.",
35+
)
36+
parser.set_defaults(func=cls.run)
37+
38+
return parser
39+
40+
@classmethod
41+
def run(cls, args):
42+
validate(args.filename, args.custom_schema, args.skip_block_validation)
43+
44+
45+
def validate(filename, custom_schema, skip_block_validation):
46+
# if we are skipping checksums we can lazy load, otherwise don't
47+
with asdf.open(
48+
filename,
49+
custom_schema=custom_schema,
50+
validate_checksums=not skip_block_validation,
51+
lazy_load=skip_block_validation,
52+
) as af: # noqa: F841
53+
msg = f"{filename} is valid"
54+
if custom_schema:
55+
msg += f", conforms to {custom_schema}"
56+
if not skip_block_validation:
57+
msg += ", and block checksums match contents"
58+
print(msg)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import numpy as np
2+
import pytest
3+
4+
import asdf
5+
from asdf._commands import main
6+
7+
8+
@pytest.fixture()
9+
def valid_file_path(tmp_path):
10+
path = tmp_path / "valid_file.asdf"
11+
asdf.dump({"foo": 42, "arr": np.arange(42)}, path)
12+
return path
13+
14+
15+
@pytest.fixture()
16+
def invalid_file_path(tmp_path):
17+
# don't use asdf as we're intentionally making an invalid file
18+
contents = b"""#ASDF 1.0.0
19+
#ASDF_STANDARD 1.6.0
20+
%YAML 1.1
21+
%TAG ! tag:stsci.edu:asdf/
22+
--- !core/asdf-1.1.0
23+
arr: !core/ndarray-1.1.0
24+
data:
25+
nested: error
26+
..."""
27+
path = tmp_path / "invalid_file.asdf"
28+
with path.open("wb") as f:
29+
f.write(contents)
30+
return path
31+
32+
33+
@pytest.fixture()
34+
def custom_schema_path(tmp_path):
35+
contents = """%YAML 1.1
36+
---
37+
id: "http://example.com/schemas/your-custom-schema"
38+
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
39+
type: object
40+
properties:
41+
foo:
42+
type: string"""
43+
path = tmp_path / "custom_schema.yaml"
44+
with path.open("w") as f:
45+
f.write(contents)
46+
return path
47+
48+
49+
@pytest.fixture()
50+
def bad_blocks_file_path(tmp_path):
51+
to_replace = b"REPLACE"
52+
replacement = b"ABCDEFG"
53+
buff = asdf.dumps({"arr": np.frombuffer(to_replace, np.uint8)})
54+
buff = buff.replace(to_replace, replacement)
55+
path = tmp_path / "bad_block.asdf"
56+
with path.open("wb") as f:
57+
f.write(buff)
58+
return path
59+
60+
61+
def test_valid(capsys, valid_file_path):
62+
assert main.main_from_args(["validate", str(valid_file_path)]) == 0
63+
64+
captured = capsys.readouterr()
65+
assert "valid" in captured.out
66+
67+
68+
def test_invalid(invalid_file_path):
69+
with pytest.raises(asdf.ValidationError):
70+
main.main_from_args(["validate", str(invalid_file_path)])
71+
72+
73+
def test_custom_schema(valid_file_path, custom_schema_path):
74+
with pytest.raises(asdf.ValidationError):
75+
main.main_from_args(["validate", str(valid_file_path), "--custom-schema", str(custom_schema_path)])
76+
77+
78+
def test_block_checksum(bad_blocks_file_path):
79+
with pytest.raises(ValueError, match="does not match given checksum"):
80+
main.main_from_args(["validate", str(bad_blocks_file_path)])
81+
82+
83+
def test_skip_block_validation(bad_blocks_file_path):
84+
assert main.main_from_args(["validate", str(bad_blocks_file_path), "--skip-block-validation"]) == 0

changes/1952.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ``asdftool validate`` to validate ASDF files.

docs/asdf/asdf_tool.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ useful operations:
3030
- ``to_yaml``: Inline all of the data in an ASDF file so that it is
3131
pure YAML.
3232

33+
- ``validate``: Validate an ASDF file's blocks and schema.
34+
3335
Run ``asdftool --help`` for more information.

0 commit comments

Comments
 (0)