Source code for rs_metadata.vocabulary.documents

"""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 codemeta_versions() -> dict[str, Any]: """Which ``@context`` values are recognized, and what 2.0 terms became. Returns ------- dict ``current``, ``currentMajor``, and a ``contexts`` map from context URL to version. Examples -------- Every 3.x context denotes the current vocabulary — 3.0 and 3.1 resolve to byte-identical documents, so a 3.0 record is not out of date: >>> versions = codemeta_versions() >>> versions["contexts"]["https://w3id.org/codemeta/3.0"] '3.0' >>> versions["currentMajor"] '3' Earlier CodeMeta is not listed, and is reported as an unrecognized context: >>> any(v.startswith("2") for v in versions["contexts"].values()) False """ return 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 biotools_schema() -> dict[str, Any]: """The official bio.tools JSON Schema.""" return load(DATA_PACKAGE, "biotools.schema.json")
[docs] @cache def codemeta_crosswalks() -> dict[str, Any]: """CodeMeta's own crosswalk tables, keyed by the mapping id that uses them. Each entry is ``{"source", "format", "properties"}``, where ``properties`` maps a CodeMeta property to the source fields CodeMeta says correspond to it. Vendored so a mapping's claim about its own provenance can be checked rather than believed. """ document = load(DATA_PACKAGE, "codemeta-crosswalks.json") return cast(dict[str, Any], document["crosswalks"])
[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 format_hints() -> dict[str, Any]: """How other packaging ecosystems spell things rs-metadata maps. Returns ------- dict ``rLicenseAliases`` mapping CRAN license strings to SPDX identifiers, and ``pyprojectUrlRoles`` mapping CodeMeta concepts to the PEP 621 ``[project.urls]`` labels that denote them. Examples -------- >>> format_hints()["rLicenseAliases"]["aliases"]["gpl-3"] 'GPL-3.0-only' >>> "repository" in format_hints()["pyprojectUrlRoles"]["labels"]["source"] True """ return load(DATA_PACKAGE, "format-hints.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"]), }