AstraLint is a Python linter for Space Physics data files, validating conformance to standards such as ISTP and PDS4.
π Try it online β no installation required!
AstraLint is published on PyPI. The simplest way to install is with pip:
pip install astralintNote: PyPI package names are case-insensitive; pip install AstraLint also works. If you prefer using a virtual environment, create and activate one before installing.
AstraLint validates data files against conformance suites using a codec-agnostic architecture:
- Codecs transform file formats (e.g., CDF) into a common abstract representation (
File) - Suites define collections of validation rules (e.g., ISTP, PDS4)
- Rules check specific requirements, defined either in Python or YAML
# Lint a file against the default ISTP suite
astralint lint myfile.cdf
# Lint a multiple files at once
astralint lint file1.cdf file2.cdf file3.cdf
# Lint against a specific suite
astralint lint myfile.cdf --suite PDS4
# Select specific rules to run filtering by reference ID or name, regex supported
astralint lint myfile.cdf --suite ISTP --select "ISTP-MD-003" --select ".*GlobalAttributes"
# Ignore specific rules by reference ID or name, regex supported
astralint lint myfile.cdf --suite ISTP --ignore "ISTP-MD-00[0-9]" --ignore "MandatoryGlobalAttributes"
# Show every check, including the ones that passed (full tree)
astralint lint myfile.cdf --show-passed
# Show only failed assertions, pruning passed and skipped from the full tree
astralint lint myfile.cdf --show-passed --failed-only
# List available suites
astralint list-suites
# Strict mode: exit with error on warnings too
astralint lint myfile.cdf --strict
# Auto-fix deterministic ISTP issues and write a corrected copy (<stem>.fixed.cdf)
astralint fix myfile.cdf
# Dry run: list the proposed fixes without writing anything
astralint fix myfile.cdf --apply noneBy default the console output is quiet, like a typical linter: it lists only
the errors and warnings that need attention, sorted by severity, and ends with a
one-line verdict (e.g. β Found 7 problems (3 errors, 4 warnings), plus 13 info findings). Use --show-passed to see the full nested tree of every check, and
--output html for an interactive, filterable report.
AstraLint returns exit code 1 on validation errors (or warnings with --strict), making it suitable for CI/CD pipelines.
AstraLint can propose and apply deterministic fixes for many ISTP issues. When
you lint a CDF, the verdict is followed by a hint pointing at the fix command:
β Found 8 problems (5 errors, 3 warnings)
β 5 auto-fixable, 3 need review β run: astralint fix myfile.cdf
astralint fix runs a convergence loop β validate β propose fixes β apply the safe
ones β re-validate β until the file is clean or no further progress is made, then
writes a corrected copy:
# Apply the safe (auto) fixes; writes <stem>.fixed.cdf
astralint fix myfile.cdf
# Dry run: list every proposed fix (auto + staged) without writing
astralint fix myfile.cdf --apply none
# Choose the output path
astralint fix myfile.cdf --output corrected.cdf
# Respects the same config as `lint`: ignored / deselected rules are not fixed
astralint fix myfile.cdf --ignore "ISTP-VA-019"
astralint fix data.cdf --config-file .astralint.prod.yamlfix reads the same configuration as lint (.astralint.yaml,
pyproject.toml [tool.astralint], and --suite/--select/--ignore overrides):
a rule excluded via ignore/select or demoted below ERROR via
severity_overrides is neither validated nor proposed for fixing.
Every proposed fix carries its source, confidence, and a one-line provenance note, and falls into one of three dispositions:
- auto β deterministic and safe, applied automatically. Examples: a missing
FILLVALset to the ISTP default for the variable's type;VAR_TYPE/DEPEND_0inferred from the variable reference graph;Logical_file_id/Logical_sourcederived from an ISTP-conventional filename; an out-of-rangeFILLVALreset to the standard fill;Generation_datereformatted toyyyymmdd. For the PDS4 suite,astralint fixalso re-saves the CDF uncompressed and contiguous in one lossless step, clearing the CDF-A compression and fragmentation requirements. - staged β computed but lossy or ambiguous, so it is suggested for review and
never applied automatically. Examples: truncating an over-long
CATDESC/FIELDNAM/LABLAXIS; a closest-name suggestion for a danglingDEPEND_i/LABL_PTR_ipointer. - needs your input β irreducibly human and never fabricated (e.g.
UNITS,PI_name,DOI).
Physical/numeric values (UNITS, VALIDMIN/VALIDMAX, β¦) and identity/provenance
attributes (PI_name, DOI, β¦) are never invented β they are only ever
reported, not auto-filled.
AstraLint can be configured via pyproject.toml or .astralint.yaml. Configuration is loaded with the following precedence (highest to lowest):
- CLI arguments
.astralint.yaml(project root)pyproject.toml[tool.astralint]- Built-in defaults
# Generate a starter config file
astralint config init
# Validate your config file
astralint config validate
# Show resolved configuration (merged from all sources)
astralint config showsuite: ISTP
select:
- "MandatoryGlobalAttributes"
- "ISTP-VAR-.*"
ignore:
- "DeprecatedRule"
severity_overrides:
ISTP-VAR-001: WARNING
extra_rules:
- "./custom_rules/"
output:
format: console
verbose: false
show_passed: true[tool.astralint]
suite = "ISTP"
select = ["MandatoryGlobalAttributes"]
[tool.astralint.output]
format = "html"
verbose = trueπ Full Configuration Reference β
flowchart TD
subgraph Input["π₯ Input"]
A[π Data File]
end
subgraph Codecs["π Codecs (pluggable)"]
B1[CDF Codec]
B2[NetCDF Codec]
B3[... more]
end
subgraph Core["βοΈ Core"]
C[π¦ Abstract File Model]
D[π Conformance Suite]
E[β
Rules & Assertions]
end
subgraph Suites["π Suites (pluggable)"]
S1[ISTP]
S2[PDS4]
S3[... more]
end
subgraph Reports["π Reports (pluggable)"]
R1[Console]
R2[JSON]
R3[... more]
end
A --> B1 & B2 & B3
B1 & B2 & B3 --> C
C --> D
S1 & S2 & S3 -.->|loads| D
D --> E
E --> R1 & R2 & R3
style C fill:#fff3e0
style Core fill:#f5f5f5,stroke:#999
style Codecs fill:#e3f2fd,stroke:#1976d2
style Suites fill:#fce4ec,stroke:#c2185b
style Reports fill:#e8f5e9,stroke:#388e3c
The abstract File model is the core data structure that all codecs produce. Rules and assertions operate on this unified representation:
File
βββ filename: str # File name or identifier
βββ extension: str # File extension (e.g., "cdf")
βββ compression: str # e.g., "gzip", "none"
βββ attributes: {name β Attribute} # Global metadata
β βββ "Project" β Attribute
β βββ "PI_name" β Attribute
β βββ ...
βββ variables: {name β Variable} # Data variables
βββ "Epoch" β Variable
β βββ name: str # "Epoch"
β βββ shape: [int] # e.g., [1440]
β βββ compression: str # "gzip", "none"
β βββ data_type: DataType # TT2000, FLOAT64, ...
β βββ record_variance: bool
β βββ attributes: {name β Attribute}
β βββ "CATDESC" β Attribute
β βββ "FILLVAL" β Attribute
β βββ ...
βββ "Temperature" β Variable
βββ ...
Attribute
βββ name: str
βββ data_type: [DataType] # List of data types
βββ shape: [int] # Attribute dimensions
DataType = CHAR | UINT8 | UINT16 | UINT32 | UINT64
| INT8 | INT16 | INT32 | INT64
| FLOAT32 | FLOAT64
| TT2000 | CDFEPOCH | CDFEPOCH16
Rules use /-separated paths with regex support to navigate the model:
| Path Example | Description |
|---|---|
attributes |
Global attributes dictionary |
attributes/Project |
Specific global attribute |
variables |
All variables dictionary |
variables/Epoch |
Specific variable |
variables/.*/attributes |
Attributes of all variables |
variables/Epoch/data_type |
Data type of a specific variable |
variables/Epoch/shape/0 |
First dimension of variable shape |
attributes/Project/data_type/0 |
First data type of attribute |
Lists are accessed using numeric indices: path/to/list/0, path/to/list/1, etc.
Rules can be defined declaratively in YAML files. Example from
MandatoryGlobalAttributes.yaml:
name: MandatoryGlobalAttributes
description: "All mandatory global attributes must be present"
url: "https://..."
reference: "ISTP-MD-003"
severity: ERROR
suite: ISTP
assertions:
- path: "attributes"
check: contains_keys
keys:
- Data_type
- Logical_source
- PI_name
message: "Missing mandatory global attribute: {key}"
- path: "variables/.*/attributes"
check: contains_keys
keys: [ CATDESC, FIELDNAM, FILLVAL ]
message: "Variable missing required attribute: {key}"| Category | Checks |
|---|---|
| Existence | exists, not_exists |
| Value | comparison, range, is_type |
| String | matches |
| Collection | contains_keys, in, not_in, length, not_empty, requires, array_shape |
| Relationship | reference_variable, compare_to |
| Combinators | all_of, any_of, any_match, none_of, not, one_of, at_least, at_most, exactly |
| Conditional | if_then, if_then_else |
π Full Assertions Reference β
| Format | Extension | Library |
|---|---|---|
| CDF | .cdf |
pycdfpp |
| FITS | .fits, .fit, .fts |
astropy |
- ISTP - ISTP Metadata Guidelines
- CDAWeb - CDAWeb ingestion profile: inherits ISTP and promotes the CDAWeb-required attributes (
Instrument_type,Mission_group) to errors, plus CDAWeb-specific entry limits - PDS4 - PDS4 CDF archiving profile (CDF-A): inherits ISTP and adds the constraints from the Guide to Archiving CDF Files in PDS4 β no file/variable compression, CDF version β₯ 3.4, a required
spase_DatasetResourceID, contiguous (non-fragmented) variables, and zVariables only. - SOLARNET - SOLARNET Metadata Recommendations
The PDS4 suite covers every CDF-A structural requirement that applies. Contiguity and
zVariable-only checks use Variable.is_contiguous / is_zvariable from
pycdfpp β₯ 0.11 (older pycdfpp simply skips those two
checks). The single-file requirement is satisfied by construction β AstraLint only loads
single-file CDFs. (See issue #2.)
Create a new codec in src/astralint/codecs/. Subclasses register themselves
automatically via __init_subclass__, so simply defining the class and importing
the module is enough to make the codec available:
from astralint.base import Codec, File
class MyCodec(Codec):
@classmethod
def supported_extensions(cls) -> list[str]:
return ["ext"]
@staticmethod
def load(file_url_or_bytes: str | bytes) -> File | None:
# Transform your file format into the abstract File model
...For remote files, AstraLint provides get_remote_file(url) -> bytes to fetch
remote content and is_remote_file(url) -> bool to detect URLs.
Create a new assertion in src/astralint/base/yaml_rules/assertions/. Subclasses
of BaseAssertion are auto-registered into the discriminated union via the
check field's Literal value:
from typing import Any, Literal
from astralint.base import File, Severity, ValidationResult
from astralint.base.yaml_rules.assertions.base import BaseAssertion
class MyAssertion(BaseAssertion):
check: Literal["my_check"] = "my_check"
# Add custom fields as needed; they are populated from the YAML rule definition.
# expected_value: Any
def single_assertion(
self,
file: File,
path: str,
value: Any,
severity: Severity,
captures: dict[str, str] | None = None,
) -> ValidationResult:
# Called once per (path, value) pair that matched the base assertion's path
# pattern. `captures` holds named groups from {name} placeholders, if any.
...For how to install uv and Python, see installation.md.
For development workflows, see development.md.
For instructions on publishing to PyPI, see publishing.md.
This project was built from simple-modern-uv.
