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: object

Options that alter validation.

Defaults cover a conventional repository. The path overrides support layouts such as monorepos, where metadata may live below the root.

Parameters:
root: Path = PosixPath('.')#
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.toml in a subdirectory while codemeta.json describes the whole thing.

codemeta: Path | None = None#

Alias for sources["codemeta"], kept because it predates the general form and appears in published workflows.

fail_on: str = 'error'#

error, warning or never.

Type:

Which severity makes the run fail

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 example pyproject.

Returns:

Path or None – The path to use, or None when 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

codemeta accepts 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:

Report

Notes

Validation stops when codemeta.json is missing or unreadable. A companion that fails its own schema is reported but not compared.

Missing optional sources are not findings, but remain in sources to distinguish an absent file from a checked file with no problems.

See also

rs_metadata.profile.validate

The profile checks alone.

rs_metadata.consistency.compare

The 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: object

What write() would create, and what it would leave alone.

Variables:
  • create (dict) – Repository-relative path mapped to the file contents to write.

  • existing (list of str) – Paths that are already present and will not be touched.

  • inferred (list of str) – Human-readable notes about what was filled in, and from where.

Parameters:
create: dict[str, str]#
existing: list[str]#
inferred: list[str]#
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:

Plan

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
rs_metadata.scaffold.write(root, proposed)[source]#

Create the planned files.

Parameters:
  • root (Path) – Repository to write into.

  • proposed (Plan) – The plan from plan().

Returns:

list of str – Paths actually written, in the order they were created.

Return type:

list[str]