Running a validation#
validate takes an Options and returns a Report. Everything else on this page describes what comes back.
The validation pipeline.
This is the shared core the CLI and the GitHub Action both call. Neither adds
validation behavior of its own, so rs-metadata validate on a laptop and
the Action in CI can never disagree about whether a repository passes.
The pipeline runs in a fixed order:
find codemeta.json
-> validate it against the LUMC profile
-> find and validate the required CITATION.cff
-> auto-detect other supported metadata files
-> parse each with its adapter and map it to CodeMeta concepts
-> compare every meaningfully comparable concept with the anchor
-> report errors, warnings and skipped checks
- class rs_metadata.core.Options(root=PosixPath('.'), sources=<factory>, codemeta=None, fail_on='error')[source]#
Bases:
objectOptions that alter validation.
Defaults cover a conventional repository. The path overrides support layouts such as monorepos, where metadata may live below the root.
- sources: dict[str, Path]#
Explicit paths for metadata files that do not sit at the repository root, keyed by adapter id. A monorepo keeps
pyproject.tomlin a subdirectory whilecodemeta.jsondescribes the whole thing.
- codemeta: Path | None = None#
Alias for
sources["codemeta"], kept because it predates the general form and appears in published workflows.
- path_for(adapter_id)[source]#
The explicit path given for one source, resolved against the root.
- Parameters:
adapter_id (str) – The adapter’s
id, for examplepyproject.- Returns:
Path or None – The path to use, or
Nonewhen no override was given and the adapter should look in the root itself.- Return type:
Path | None
Examples
With no override, the adapter searches as usual:
>>> Options(root=Path("/repo")).path_for("pyproject") is None True
A relative override is resolved against the root:
>>> options = Options(root=Path("/repo"), ... sources={"pyproject": Path("backend/pyproject.toml")}) >>> options.path_for("pyproject").as_posix() '/repo/backend/pyproject.toml'
An absolute one is taken as given. What counts as absolute is the platform’s business — a leading slash is absolute on POSIX but not on Windows, which wants a drive — so this asks the platform rather than assuming:
>>> elsewhere = Path("/elsewhere/CITATION.cff").resolve() >>> Options(sources={"cff": elsewhere}).path_for("cff") == elsewhere True
codemetaaccepts the older spelling too:>>> Options(root=Path("/repo"), codemeta=Path("meta/codemeta.json") ... ).path_for("codemeta").as_posix() '/repo/meta/codemeta.json'
- rs_metadata.core.validate(options)[source]#
Validate a repository’s metadata.
- Parameters:
options (Options) – What to validate, and where. The defaults describe a repository laid out at the repository root, which needs no configuration.
- Returns:
Report – Every source considered and every finding. Use
exit_code()to turn it into a process status, and the renderers to present it.- Return type:
Notes
Validation stops when
codemeta.jsonis missing or unreadable. A companion that fails its own schema is reported but not compared.Missing optional sources are not findings, but remain in
sourcesto distinguish an absent file from a checked file with no problems.See also
rs_metadata.profile.validateThe profile checks alone.
rs_metadata.consistency.compareThe cross-file comparison alone.
Examples
>>> import json, tempfile >>> from pathlib import Path
A repository with nothing in it fails on the anchor, and says why:
>>> with tempfile.TemporaryDirectory() as tmp: ... report = validate(Options(root=Path(tmp))) >>> report.status 'failed' >>> [d.code for d in report.diagnostics] ['source.missing']
Every source is recorded, including the ones that were looked for and not found:
>>> [(s.id, s.status) for s in report.sources] [('codemeta', 'absent')]
With an anchor present, the profile runs and the required companion is reported as missing:
>>> record = {"@context": "https://w3id.org/codemeta/3.1", "name": "MyTool"} >>> with tempfile.TemporaryDirectory() as tmp: ... root = Path(tmp) ... _ = (root / "codemeta.json").write_text( ... json.dumps(record), encoding="utf-8") ... report = validate(Options(root=root)) >>> "source.missing" in {d.code for d in report.diagnostics} True >>> "profile.required-field" in {d.code for d in report.diagnostics} True
Optional formats that are absent are listed, but produce no findings:
>>> from rs_metadata.adapters import companion_adapters >>> absent = {s.id for s in report.sources if s.status == "absent"} >>> absent == {a.id for a in companion_adapters()} True
Creating the metadata files a repository is missing.
rs-metadata init writes codemeta.json, CITATION.cff and a CI
workflow, filling in whatever can be read from packaging metadata and the git
remote. It never overwrites: a file that already exists is left alone and
reported as such.
Fields that cannot be inferred get the placeholder values the validator already
knows about, so rs-metadata validate immediately names each one that still
needs a real value.
- class rs_metadata.scaffold.Plan(create=<factory>, existing=<factory>, inferred=<factory>)[source]#
Bases:
objectWhat
write()would create, and what it would leave alone.- Variables:
- Parameters:
- rs_metadata.scaffold.plan(root)[source]#
Work out which files are missing and what should go in them.
- Parameters:
root (Path) – Repository to inspect.
- Returns:
Plan – The files to create, those already present, and notes on what was inferred.
- Return type:
Examples
In an empty directory, all three files are proposed:
>>> import tempfile >>> from pathlib import Path >>> with tempfile.TemporaryDirectory() as tmp: ... proposed = plan(Path(tmp)) >>> sorted(proposed.create) ['.github/workflows/metadata.yml', 'CITATION.cff', 'codemeta.json']
A file that already exists is reported rather than replaced:
>>> with tempfile.TemporaryDirectory() as tmp: ... _ = (Path(tmp) / "codemeta.json").write_text("{}", encoding="utf-8") ... proposed = plan(Path(tmp)) >>> proposed.existing ['codemeta.json'] >>> "codemeta.json" in proposed.create False