Source code for sammi.validation
import pathlib
import requests
from typing import List
[docs]
class CDFValidator:
"""
Python class to leverage the Science Physics Data Facility (SPDF)'s API for validation of International Solar-Terrestrial Physics (ISTP) guidelines.
Parameters
----------
api_url : `str`, optional
The URL of the SPDF validation API. Default is "https://skteditor.heliophysics.net/cgi-bin/checkcdf.cgi".
"""
def __init__(
self,
api_url: str = "https://skteditor.heliophysics.net/cgi-bin/checkcdf.cgi",
):
self.api_url = api_url
[docs]
def validate(self, cdf_path: pathlib.Path) -> List[str]:
"""
Function to validate a CDF file against the ISTP guidelines using the SPDF validation API.
Parameters
----------
cdf_path : `pathlib.Path`
The path to the local CDF file to validate.
Returns
-------
`List[str]`
A list of error messages from the validation process.
"""
try:
raw_response = self.validate_raw(cdf_path)
return self._parse_errors(raw_response)
except Exception as e:
return [f"Validation failed: {str(e)}"]
[docs]
def validate_raw(self, cdf_path: pathlib.Path) -> str:
"""
Function to validate a CDF file against the ISTP guidelines using the SPDF validation API.
Parameters
----------
cdf_path : `pathlib.Path`
The path to the local CDF file to validate.
Returns
-------
`str`
The raw response from the SPDF validation API.
"""
try:
with open(cdf_path, "rb") as cdf_to_upload:
response = requests.post(
self.api_url, files={"file": (cdf_path.name, cdf_to_upload)}
)
response.raise_for_status()
return response.content.decode("utf-8")
except requests.RequestException as e:
return f"API request failed: {str(e)}"
def _parse_errors(self, raw_response: str) -> List[str]:
"""
Parses the raw response from the SPDF validation API to extract error messages.
Args:
raw_response (str): The raw string response from the SPDF validation API.
Returns:
List[str]: A list of error messages extracted from the raw response.
"""
errors = []
current_section = None
current_variable = None
if raw_response.startswith("API request failed:"):
return [raw_response]
for line in raw_response.splitlines():
if "Global errors:" in line:
current_section = "Global errors"
current_variable = None
elif "The following variables are not ISTP-compliant" in line:
current_section = "Variable"
current_variable = None
# Two tabs for each new variable name, 4 tabs for each error for that variable.
elif (
current_section == "Variable"
and line.startswith("\t")
and not line.startswith("\t\t")
):
current_variable = line.strip()
elif current_section and line.strip() and "Warning" not in line:
if current_section == "Variable" and current_variable:
errors.append(f"{current_variable}:: {line.strip()}")
else:
errors.append(f"{current_section}: {line.strip()}")
elif current_section and not line.strip():
current_section = None
current_variable = None
# Filter out any entries that are just section headers without actual errors
errors = [error for error in errors if not error.endswith(":")]
return errors