The report#
One object carries every finding, and one schema describes it. See the report contract for the JSON form.
The validation report: rs-metadata’s single output contract.
Everything the validator learns ends up in a Report. The CLI, the
GitHub Action and any institutional tooling render this model; none of them
re-derive validation behavior. That is what keeps rs-metadata validate on
a laptop and the Action in CI from ever disagreeing.
The serialized form is specified by schema/rs-metadata-report.schema.json
and is validated against that schema in the test suite, so the schema cannot
drift away from the code.
- rs_metadata.report.MAX_VALUE_CHARS = 300#
Values longer than this are truncated when echoed back in a report, so a pasted-in abstract cannot make a diagnostic unreadable.
- class rs_metadata.report.MappingRef(id, origin, version=None, source=None, retrieved=None)[source]#
Bases:
objectWhich crosswalk related a source to CodeMeta, and where it came from.
Carried on every consistency finding so a reader can tell a mapping taken from the upstream CodeMeta crosswalk tables apart from one rs-metadata introduced itself.
- class rs_metadata.report.Location(file, path=None, line=None, column=None, value=None)[source]#
Bases:
objectWhere in a file a finding applies.
lineandcolumnare resolved from the original file text rather than reconstructed from the parsed value, so CI annotations land on the offending line instead of at the top of the file.- describe()[source]#
Render as
file:line, or justfilewhen no line is known.- Returns:
str – A clickable-looking location for terminal output.
- Return type:
Examples
>>> Location(file="codemeta.json", line=12).describe() 'codemeta.json:12'
A missing line is normal because source positions are best-effort. In that case, return the filename rather than a misleading line 0:
>>> Location(file="codemeta.json").describe() 'codemeta.json'
- class rs_metadata.report.Diagnostic(code, severity, message, property=None, location=None, anchor=None, source=None, strategy=None, rule=None, mapping=None, suggestion=None)[source]#
Bases:
objectOne finding. Never generic: it names the metadata, the value and the fix.
- Parameters:
- mapping: MappingRef | None = None#
- primary_location()[source]#
The location an editor or CI annotation should point at.
- Returns:
Location or None – The single most relevant place, or
Nonefor a finding about the repository as a whole.- Return type:
Location | None
Notes
Consistency findings point to
codemeta.jsonbecause it is the canonical record a maintainer is expected to correct.Examples
A profile finding points at where it was found:
>>> from rs_metadata.diagnostics import Severity >>> finding = Diagnostic( ... code="profile.required-field", severity=Severity.ERROR, ... message="x", location=Location(file="codemeta.json", line=3), ... ) >>> finding.primary_location().describe() 'codemeta.json:3'
A consistency finding prefers the anchor over the source:
>>> finding = Diagnostic( ... code="consistency.mismatch", severity=Severity.ERROR, ... message="x", ... anchor=Location(file="codemeta.json", line=10), ... source=Location(file="CITATION.cff", line=8), ... ) >>> finding.primary_location().describe() 'codemeta.json:10'
- class rs_metadata.report.Source(id, format, role, status, file=None, format_version=None, mapping=None)[source]#
Bases:
objectA metadata file that was considered, whether or not it was found.
Absent sources are recorded too. A report that lists
pyproject.tomlasabsenttells the reader something a silent report does not: that rs-metadata looked, and there was nothing there.- Parameters:
- mapping: MappingRef | None = None#
- class rs_metadata.report.Report(root='.', sources=<factory>, diagnostics=<factory>, checks=0)[source]#
Bases:
objectThe complete outcome of one validation run.
- diagnostics: list[Diagnostic]#
- add(diagnostic)[source]#
- Parameters:
diagnostic (Diagnostic)
- Return type:
None
- property errors: list[Diagnostic]#
- property warnings: list[Diagnostic]#
- property status: str#
Overall outcome, describing the findings rather than the build.
- Returns:
{“passed”, “passed-with-warnings”, “failed”} –
failedif any error was found,passed-with-warningsif only warnings were,passedotherwise.
Notes
Independent of
exit_code(). A run can reportpassed-with-warningsand still exit non-zero under--fail-on warning, because the status describes the metadata and the exit code describes the policy.Examples
>>> from rs_metadata.diagnostics import Severity >>> def finding(severity): ... return Diagnostic(code="internal.error", severity=severity, ... message="x") >>> Report().status 'passed' >>> Report(diagnostics=[finding(Severity.INFO)]).status 'passed' >>> Report(diagnostics=[finding(Severity.WARNING)]).status 'passed-with-warnings' >>> Report(diagnostics=[finding(Severity.ERROR), ... finding(Severity.WARNING)]).status 'failed'
- exit_code(fail_on='error')[source]#
Map the report onto a process exit status.
- Parameters:
fail_on ({"error", "warning", "never"}, optional) – Lowest severity that should fail the run.
- Returns:
int –
1if the run should fail,0otherwise.- Return type:
Notes
Severity is a property of a finding; turning severity into a build failure is policy, and lives here rather than in the checks. That separation is what lets one report drive a strict institutional gate and a lenient local check without re-running anything.
Examples
>>> from rs_metadata.diagnostics import Severity >>> def finding(severity): ... return Diagnostic(code="internal.error", severity=severity, ... message="x")
Warnings are non-fatal by default, which is what makes recommendations safe to ship:
>>> warned = Report(diagnostics=[finding(Severity.WARNING)]) >>> warned.exit_code() 0 >>> warned.exit_code("warning") 1
Errors fail unless failures are switched off, which is useful when adopting the standard across existing repositories:
>>> failed = Report(diagnostics=[finding(Severity.ERROR)]) >>> failed.exit_code(), failed.exit_code("never") (1, 0)
Informational findings never fail a run:
>>> Report(diagnostics=[finding(Severity.INFO)]).exit_code("warning") 0
The catalog of stable diagnostic codes.
Every finding rs-metadata reports carries a code from this module. The codes are a public interface: they appear in the JSON report, in CI annotations and in documentation links, and downstream tooling is expected to filter on them. Within a major version a code never changes meaning. Retiring a code is a breaking change; adding one is not.
Codes are namespaced by the layer that produced them, which tells a reader immediately where to look:
profile.*The anchor record does not satisfy the LUMC profile.
recommendation.*The record is valid but could be more useful to registries and archives.
source.*A metadata file could not be found, read, or validated against its own format’s schema.
consistency.*Two metadata files describe the same concept differently.
internal.*rs-metadata itself failed. Always a bug report.
- class rs_metadata.diagnostics.Severity(*values)[source]#
Bases:
StrEnumHow much a finding matters.
Severity is a property of the finding, not a policy decision. Whether a warning fails a build is decided separately by
--fail-on, so the same report can drive a strict institutional gate and a lenient local check.- ERROR = 'error'#
- WARNING = 'warning'#
- INFO = 'info'#
- class rs_metadata.diagnostics.Namespace(*values)[source]#
Bases:
StrEnumThe layer a diagnostic code came from.
Every code is
<namespace>.<name>, and the namespace tells a reader immediately where to look. Kept here rather than in the documentation generator so the prose and the codes cannot describe different sets.- PROFILE = 'profile'#
- RECOMMENDATION = 'recommendation'#
- SOURCE = 'source'#
- CONSISTENCY = 'consistency'#
- INTERNAL = 'internal'#
- class rs_metadata.diagnostics.DiagnosticCode(code, title, severity, description, remediation)[source]#
Bases:
objectDocumentation for one diagnostic code, surfaced by
explain.- severity: Severity#
The most severe level this code is ever reported at. A few codes are reported less severely where the check is less certain of itself — a value that cannot be right is an error, one that merely looks wrong is a warning — so this is a ceiling rather than a promise.
test_contractspins it to what the code actually emits.
- rs_metadata.diagnostics.get(code)[source]#
Look up a diagnostic code in the catalog.
- Parameters:
code (str) – A registered diagnostic code.
- Returns:
DiagnosticCode – Its documentation: title, default severity, description, remediation.
- Raises:
KeyError – If the code is not registered. A test asserts that every code emitted anywhere in the source is in the catalog, so this can only be reached by a typo in rs-metadata’s own source.
- Return type:
Examples
>>> entry = get("consistency.mismatch") >>> entry.severity.value 'error' >>> entry.title 'Two metadata files disagree about the same concept'
Every code carries a documentation link, which travels onto the finding:
>>> entry.docs.endswith("docs/using/diagnostics.md#consistencymismatch") True
An unregistered code fails loudly rather than producing a finding nobody can look up:
>>> get("made.up") Traceback (most recent call last): ... KeyError: "Unknown diagnostic code 'made.up'. Codes must be...CATALOG."