Extension points#
A new metadata format is an Adapter plus a crosswalk mapping. Recipes has the order to do it in.
The adapter contract.
An adapter is one supported metadata file or ecosystem: the logic needed to find it, read it, check it against its own specification, and express what it says in CodeMeta concepts. Adapters know nothing about each other and nothing about comparison; they only ever translate into the common representation.
Adding support for a new ecosystem means writing one adapter and one mapping
file. Nothing in the comparison engine changes, and no existing adapter is
touched. See docs/developing/adapters.md.
- exception rs_metadata.adapters.base.SourceError(message, line=None)[source]#
Bases:
ExceptionA metadata file exists but cannot be read.
Carries a line number where the underlying parser reports one, so even a syntax error gets an inline CI annotation.
- class rs_metadata.adapters.base.Adapter[source]#
Bases:
ABCOne supported metadata format.
- role: str = 'optional'#
anchorfor codemeta.json,requiredfor a file whose absence is itself a finding,optionalfor formats that are auto-detected when a repository happens to contain them.
- mapping_name: str | None = None#
Mapping file stem, or
Nonefor the anchor, which needs no crosswalk.
- detect(root)[source]#
Find this format’s file in a repository, or return
None.Matching is case-insensitive because
citation.cffandCITATION.cffare the same file on macOS and Windows. Whether the casing is the one GitHub actually requires is a separate question, answered byvalidate_source().
- abstractmethod parse(path, relative)[source]#
Read the file. Raises
SourceErrorif it is malformed.
- validate_source(parsed)[source]#
Check the file against its own format’s specification.
- Parameters:
parsed (ParsedSource)
- Return type:
Which adapters exist, and in what order they are reported.
Registration order is reporting order. The anchor comes first, then the file required alongside it, then the optional formats that are auto-detected when a repository happens to contain them.
To add a format, write an adapter and a mapping file and add it to
ADAPTERS. Nothing else in the codebase needs to change.
- rs_metadata.adapters.registry.ANCHOR = <CodeMetaAdapter id='codemeta'>#
The canonical record every other source is compared against.
- rs_metadata.adapters.registry.companion_adapters()[source]#
Every adapter except the anchor, in reporting order.
- Returns:
tuple of Adapter – The formats compared against
codemeta.json.- Return type:
Examples
>>> for adapter in companion_adapters(): ... print(adapter.id) cff github zenodo biotools pyproject package-json r-description cargo julia-project dockerfile
The anchor is excluded because the companions are compared to it:
>>> ANCHOR.id in {adapter.id for adapter in companion_adapters()} False
- rs_metadata.adapters.registry.get_adapter(adapter_id)[source]#
Look up a registered adapter by its identifier.
- Parameters:
adapter_id (str) – The adapter’s
id, as it appears in a report’ssources.- Returns:
Adapter – The registered adapter.
- Raises:
KeyError – If nothing is registered under that id.
- Return type:
Examples
>>> get_adapter("cff").format 'Citation File Format' >>> get_adapter("nonexistent") Traceback (most recent call last): ... KeyError: "No adapter registered with id 'nonexistent'."
Loading of the declarative crosswalk mappings.
A mapping file answers two questions per CodeMeta property: which part of the source format corresponds to it, and how strictly the two should be expected to agree. Keeping that as data rather than as code means a new rule is a reviewable diff in a YAML file, and means the report can state exactly which crosswalk produced a finding and whether that crosswalk came from CodeMeta upstream or from rs-metadata.
The mapping never decides whether two values are equivalent. That is the normalization layer’s job, and it is shared across all formats.
- class rs_metadata.mapping.Rule(*values)[source]#
Bases:
StrEnumHow strictly a concept is expected to agree across two files.
A
StrEnumrather than a validated string, matchingSeverity:Rule(value)rejects an unknown rule on its own, and a typo in a mapping file fails at load time rather than silently comparing as something else.Examples
>>> Rule("subset") <Rule.SUBSET: 'subset'> >>> Rule.EQUAL == "equal" True
An unknown rule is rejected without any extra validation code:
>>> Rule("nearly") Traceback (most recent call last): ... ValueError: 'nearly' is not a valid Rule
- EQUAL = 'equal'#
- SUBSET = 'subset'#
- OVERLAP = 'overlap'#
- INFORMATIONAL = 'informational'#
- class rs_metadata.mapping.Origin(*values)[source]#
Bases:
StrEnumWhere a crosswalk came from.
A finding carries this so a reader can tell a mapping CodeMeta publishes from one this project decided on.
- UPSTREAM = 'codemeta-upstream'#
- RS_METADATA = 'rs-metadata'#
- MIXED = 'mixed'#
- class rs_metadata.mapping.FieldRule(property, source, strategy='text', rule=Rule.EQUAL, severity=Severity.ERROR, comparable=True, reason=None, note=None, anchor_properties=())[source]#
Bases:
objectThe comparison policy for one CodeMeta concept in one source format.
- Parameters:
- anchor_properties: tuple[str, ...] = ()#
Anchor concepts this source property is compared against. Usually just
property, but ecosystems draw role boundaries differently: npm’s author may legitimately appear as a CodeMeta maintainer.
- anchors()[source]#
Which anchor concepts this source property is compared against.
- Returns:
tuple of str – The declared anchor properties, or just
property.- Return type:
Examples
By default a concept is compared against itself:
>>> FieldRule(property="version", source="version").anchors() ('version',)
Ecosystems that draw a role boundary differently declare both sides, so an npm
authorcan legitimately be a CodeMetamaintainer:>>> FieldRule(property="author", source="author", ... anchor_properties=("author", "maintainer")).anchors() ('author', 'maintainer')
- class rs_metadata.mapping.Mapping(id, format, format_version, reference, fields, notes=None)[source]#
Bases:
objectOne crosswalk between a source format and CodeMeta.
- Parameters:
- reference: MappingRef#
- rs_metadata.mapping.load(name)[source]#
Load a crosswalk mapping by file stem.
- Parameters:
name (str) – Mapping file stem, for example
cff-1.2.0.- Returns:
Mapping – The parsed crosswalk. Cached, since mappings are immutable.
- Raises:
ValueError – If the mapping declares a property twice, or uses an unknown rule.
- Return type:
Examples
>>> mapping = load("cff-1.2.0") >>> mapping.format, mapping.format_version ('Citation File Format', '1.2.0')
Provenance travels with the mapping onto every finding it produces:
>>> mapping.reference.origin <Origin.UPSTREAM: 'codemeta-upstream'>
Rules are addressed by CodeMeta concept:
>>> rule = mapping.rule_for("version") >>> rule.source, rule.strategy, rule.rule.value, rule.severity.value ('version', 'version', 'equal', 'error')
A concept the format has no field for simply has no rule:
>>> mapping.rule_for("programmingLanguage") is None True