"""Schemas, vocabularies and rule data read at runtime.
Two kinds of file live in ``data/``. Vendored ones have an upstream, a
checksum and a refresh script. Project-owned ones are maintained by
hand and the refresh script never touches them; a contract test keeps
the two sets disjoint.
"""
from __future__ import annotations
from functools import cache
from typing import Any, cast
from .loader import DATA_PACKAGE, SCHEMA_PACKAGE, load
from .properties import codemeta_terms
[docs]
@cache
def cff_schema() -> dict[str, Any]:
"""The official Citation File Format 1.2.0 JSON Schema."""
return load(DATA_PACKAGE, "cff-1.2.0.schema.json")
[docs]
@cache
def profile_schema() -> dict[str, Any]:
"""The LUMC CodeMeta profile schema."""
return load(SCHEMA_PACKAGE, "codemeta-lumc.schema.json")
[docs]
@cache
def report_schema() -> dict[str, Any]:
"""The JSON schema describing rs-metadata's own report format."""
return load(SCHEMA_PACKAGE, "rs-metadata-report.schema.json")
[docs]
@cache
def spdx_licenses() -> dict[str, Any]:
"""SPDX identifiers mapped to name, deprecation status and OSI approval."""
return load(DATA_PACKAGE, "spdx-licenses.json")
[docs]
@cache
def vocabularies() -> dict[str, Any]:
"""Controlled vocabularies with no stable machine-readable upstream form."""
return load(DATA_PACKAGE, "vocabularies.json")
[docs]
@cache
def provenance() -> dict[str, Any]:
"""Where each vendored file came from, and when."""
return load(DATA_PACKAGE, "provenance.json")
# --------------------------------------------------------------------------
# Project-owned rule data.
#
# Everything above is vendored from an upstream source and regenerated by
# ``scripts/vendor_reference_data.py``. Everything below is rs-metadata's own,
# maintained by hand: no upstream publishes it, and the vendoring script never
# writes it. Both live in ``data/`` because both are read the same way, and
# ``test_contracts`` asserts the two sets stay disjoint so a hand-written file
# can never be silently overwritten.
# --------------------------------------------------------------------------
#: Files in ``data/`` that rs-metadata maintains itself.
PROJECT_OWNED_DATA = ("placeholders.json",)
[docs]
@cache
def concept_order() -> tuple[str, ...]:
"""The order concepts are reported in.
Returns
-------
tuple of str
Mandatory fields first, in the order the profile declares them, then
recommended ones. Concepts absent from both sort after these,
alphabetically, so adding a property never makes the report unstable.
Notes
-----
Derived from the profile rather than kept in a file of its own: the profile
already lists its fields in a deliberate order, identity before provenance
before technical detail, and repeating that order elsewhere is one more
thing to keep in step.
Examples
--------
>>> concept_order()[:3]
('name', 'description', 'version')
"""
policy = profile_field_policy()
return (*policy["mandatory"], *policy["recommended"])
[docs]
@cache
def placeholders() -> dict[str, Any]:
"""Template values that get left behind, and how to recognize them.
Returns
-------
dict
``orcid``, ``doiPrefix``, ``hosts``, and ``patterns`` carrying regex
sources compiled by :mod:`rs_metadata.profile.placeholders`.
Examples
--------
>>> placeholders()["orcid"]
'0000-0000-0000-0000'
>>> "example.com" in placeholders()["hosts"]
True
"""
return load(DATA_PACKAGE, "placeholders.json")
[docs]
@cache
def spdx_index() -> dict[str, str]:
"""Case-insensitive lookup from an SPDX identifier to its canonical case.
SPDX identifiers are case-sensitive by specification, but people write
``apache-2.0`` constantly. Recognising the value and reporting the
canonical spelling is more useful than rejecting it outright.
"""
return {
identifier.casefold(): identifier for identifier in spdx_licenses()["licenses"]
}
[docs]
@cache
def profile_field_policy() -> dict[str, list[str]]:
"""The mandatory and recommended field lists, read from the profile schema.
The schema is the single source of truth. Duplicating these lists in
Python would let documentation, validator and schema drift apart.
Returns
-------
dict
``mandatory``, ``recommended`` and ``featureListAliases`` lists.
Examples
--------
>>> policy = profile_field_policy()
>>> len(policy["mandatory"])
10
>>> "license" in policy["mandatory"]
True
>>> "keywords" in policy["recommended"]
True
The mandatory list matches the schema's own ``required`` keyword, which a
contract test enforces:
>>> set(policy["mandatory"]) <= set(profile_schema()["required"])
True
"""
block = profile_schema()["x-lumc-profile"]
return {
"mandatory": list(block["mandatory"]),
"recommended": list(block["recommended"]),
"featureListAliases": list(block["featureListAliases"]),
}