A data model linter
Project description
AstraLint
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!
Install
AstraLint is published on PyPI. The simplest way to install is with pip:
pip install astralint
Note: PyPI package names are case-insensitive; pip install AstraLint also works. If you prefer using a virtual environment, create and activate one before installing.
Overview
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
Usage
# 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 none
By 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.
Auto-fixing
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
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.
Configuration
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
Quick Start
# Generate a starter config file
astralint config init
# Validate your config file
astralint config validate
# Show resolved configuration (merged from all sources)
astralint config show
Example .astralint.yaml
suite: 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
Example pyproject.toml
[tool.astralint]
suite = "ISTP"
select = ["MandatoryGlobalAttributes"]
[tool.astralint.output]
format = "html"
verbose = true
๐ Full Configuration Reference โ
Architecture
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
File Model
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
Path Navigation
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.
Defining Rules in YAML
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}"
Available Assertions
| 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 โ
Supported File Formats
| Format | Extension | Library |
|---|---|---|
| CDF | .cdf |
pycdfpp |
| FITS | .fits, .fit, .fts |
astropy |
Available Conformance Suites (WIP/Demo)
- 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.)
Extending AstraLint
Adding a New Codec
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.
Adding a New Assertion Type
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.
...
Project Docs
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file astralint-0.9.0.tar.gz.
File metadata
- Download URL: astralint-0.9.0.tar.gz
- Upload date:
- Size: 880.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1cf25c4f1ddc685bff4d0b485d10863b77f9fc9257699ac07ae36bf6772cf40
|
|
| MD5 |
2338dd52068cc13f98cb34b1d6843a35
|
|
| BLAKE2b-256 |
caaef08e8b7f84a1175cc535e2b40a2b517151f624cf5e2c4216b879f75eb606
|
File details
Details for the file astralint-0.9.0-py3-none-any.whl.
File metadata
- Download URL: astralint-0.9.0-py3-none-any.whl
- Upload date:
- Size: 113.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f77db2d9dfa18fe88b8d7121130e96385c93f390cba7cb0cb03a7a84bd913fe
|
|
| MD5 |
26d1408e1073da108428a105da83dfe3
|
|
| BLAKE2b-256 |
8a5de9e43cb648f6e1bb239a7ddfd7aa62f9cb21ed3d530b757f56ff2e44743d
|