Source code for data_model.schema_loader
"""Cached, dependency-neutral loading of schema definitions."""
import logging
from copy import deepcopy
from functools import cache
from pathlib import Path
import simtools.utils.general as gen
from simtools.constants import MODEL_PARAMETER_SCHEMA_PATH, SCHEMA_PATH
from simtools.io import ascii_handler
_logger = logging.getLogger(__name__)
[docs]
def get_model_parameter_schema_files(schema_directory):
"""
Return model-parameter names and schema files from a directory.
Parameters
----------
schema_directory : str or Path
Directory containing model-parameter schema files.
Returns
-------
list
Model-parameter names.
list
Corresponding schema file paths.
Raises
------
FileNotFoundError
If the directory contains no model-parameter schema files.
"""
schema_files = sorted(Path(schema_directory).rglob("*.schema.yml"))
if not schema_files:
raise FileNotFoundError(f"No schema files found in {schema_directory}")
parameters = [
get_schema_for_version(_load_schema_source(schema_file), schema_file, "latest").get("name")
for schema_file in schema_files
]
return parameters, schema_files
[docs]
def load_schema(schema_file, schema_version="latest"):
"""
Load a schema definition from a cached parsed source.
Parameters
----------
schema_file : str or Path
Local path, URL, or name relative to the packaged schemas directory
(for example, ``model_parameters/num_gains.schema.yml``). Local files
take precedence over packaged schemas. Absolute paths are used directly.
schema_version : str
Schema version to return, or ``latest`` for the first document.
Returns
-------
dict
Independent schema definition selected from the cached parsed source.
Raises
------
FileNotFoundError
If the schema cannot be loaded locally or from its URL.
ValueError
If the requested schema version is unavailable.
"""
schema = get_schema_for_version(_load_schema_source(schema_file), schema_file, schema_version)
return deepcopy(schema)
@cache
def _load_schema_source(schema_file):
"""Resolve, parse, and cache all schema definitions from one source."""
schema = None
for path in _get_local_schema_candidates(schema_file):
try:
schema = ascii_handler.collect_data_from_file(file_name=path)
break
except FileNotFoundError:
continue
if schema is None and gen.is_url(str(schema_file)):
try:
schema = ascii_handler.collect_data_from_file(file_name=schema_file)
except FileNotFoundError as exc:
raise FileNotFoundError(f"Schema file not found: {schema_file}") from exc
if schema is None:
raise FileNotFoundError(f"Schema file not found: {schema_file}")
return schema
def _get_local_schema_candidates(schema_file):
"""Build local candidate paths for a schema file reference."""
schema_path = Path(str(schema_file))
candidates = [schema_path, SCHEMA_PATH / schema_path]
if gen.is_url(str(schema_file)):
schema_name = Path(str(schema_file)).name
if schema_name:
candidates.extend(
[
SCHEMA_PATH / schema_name,
MODEL_PARAMETER_SCHEMA_PATH / schema_name,
Path(schema_name),
]
)
return list(dict.fromkeys(candidates))
[docs]
def get_schema_for_version(schema, schema_file, schema_version):
"""
Return a requested version from a parsed schema definition.
Parameters
----------
schema : dict or list
Parsed schema definition or versioned schema documents.
schema_file : str or Path
Schema source used in error and warning messages.
schema_version : str
Requested schema version, or ``latest`` for the first document.
Returns
-------
dict
Selected schema definition.
Raises
------
ValueError
If no version is requested, the schema list is empty, or the requested version is absent.
"""
if schema_version is None:
raise ValueError(f"Schema version not given in {schema_file}.")
if isinstance(schema, list):
if len(schema) == 0:
raise ValueError(f"No schemas found in {schema_file}.")
if schema_version == "latest":
schema_version = schema[0].get("schema_version")
schema = next((doc for doc in schema if doc.get("schema_version") == schema_version), None)
if schema is None:
raise ValueError(f"Schema version {schema_version} not found in {schema_file}.")
if schema_version not in (None, "latest") and schema_version != schema.get("schema_version"):
_logger.warning(
f"Schema version {schema_version} does not match {schema.get('schema_version')}"
)
return schema