meta_package_manager.sbom.base module

Format-agnostic SBOM base class and export-format enum.

Kept deliberately free of SPDX or CycloneDX dependencies: instantiating SBOM directly is meaningless, but importing the symbols here is safe even when the optional [sbom-offline] extra is not installed.

meta_package_manager.sbom.base.writer_unavailable_reason(distribution, error)[source]

Explain why an SBOM writer library could not be imported.

Tells apart the library being absent from it being installed but older than what the renderer imports. The two need opposite remedies, and the install-the-extra advice is actively misleading for the second: a distribution shipping an older release satisfies the extra already, so the reader installs it again, sees no change, and is left with a symbol name and no idea which package owns it. NixOS hits exactly this, carrying a cyclonedx-python-lib below the floor [sbom-offline] declares.

The floor itself is deliberately not repeated here: error already names the symbol that is missing, which is more precise than a version number and cannot drift away from pyproject.toml.

Return type:

str

class meta_package_manager.sbom.base.ExportFormat(*values)[source]

Bases: StrEnum

A user-friendly version of spdx_tools.spdx.formats.FileFormat.

Map format to user-friendly IDs.

JSON = 'json'
XML = 'xml'
YAML = 'yaml'
TAG_VALUE = 'tag'
RDF_XML = 'rdf'
class meta_package_manager.sbom.base.SBOM(export_format=ExportFormat.JSON)[source]

Bases: object

Utilities shared by all SBOM classes.

See also

Anchore’s Syft and Microsoft’s sbom-tool are mature SPDX and CycloneDX emitters, useful references for field-population conventions. Both inventory packages by parsing on-disk databases and lockfiles, whereas mpm queries the live managers directly.

Defaults to JSON export format.

packages_per_manager: dict[str, int]
enriched_per_manager: dict[str, int]
vulnerabilities_by_purl: dict[str, tuple[Vulnerability, ...]]
purl_aliases: dict[str, list[str]]
primary_purls: set[str]
register_purl_aliases(primary_purl, metadata)[source]

Index a package’s extra_purls as aliases of its primary purl.

An alias is a second coordinate for the same installed package, so an advisory found under it belongs to the package the inventory pass added. Homebrew is the motivating case: a formula’s own pkg:brew/… coordinate is unknown to every advisory database, while the upstream registry coordinate it records alongside is not.

Aliases join all_purls(), so the network layer queries them too, and resolve_purl_targets() maps the answers back.

Called once per package, whatever its metadata holds, so it also registers the primary purl itself.

Return type:

None

resolve_purl_targets(purl_str)[source]

Return every package purl an advisory key designates.

The key itself comes first, covering the ordinary case where the scan queried a package’s own purl. Its alias owners follow, so a coordinate that is both one package’s own purl and another’s alias reaches both: pkg:pypi/ty@… names the pip package directly and the ty formula built from it, and the advisory belongs to each.

Callers index each target against their own package map and skip what does not resolve, which is what filters out the key when it names no package of its own.

Return type:

tuple[str, ...]

all_purls()[source]

Yield every package purl present in the document.

Powers the vulnerability scan: the network layer queries OSV once with the full purl set rather than once per package. Subclasses implement this against their own component index, and add the aliases register_purl_aliases() collected.

Return type:

Iterator[str]

attach_vulnerabilities(vulnerabilities)[source]

Bind cross-package vulnerability data to the document.

Called by the CLI between the per-package add_package loop and finalize, only in --network mode. Renderers read the stored data in their finalize override and project it into the format-native vulnerability surface (CycloneDX vulnerabilities array, SPDX security externalRefs).

Return type:

None

stats()[source]

Return a summary of what landed in the document.

Format-agnostic counters live in the base implementation; SPDX and CycloneDX subclasses extend the returned dict with their own merged-documents, dependency-graph, and any other format-specific counts. Surfaced by the CLI as a post-run INFO-level summary and usable by tests or programmatic consumers without re-parsing the rendered document.

Return type:

dict[str, object]

finalize()[source]

Resolve any deferred state before export().

Some constructs cannot be emitted at add_package() time because they reference packages that may not have been added yet: a Homebrew formula’s runtime dependency on another formula listed later in the scan, for example. Subclasses queue those during add_package and flush them here. The base implementation is a no-op so subclasses can rely on it being called exactly once.

Return type:

None

static autodetect_export_format(file_path)[source]

Better version of spdx_tools.spdx.formats.file_name_to_format which is based on Path objects and is case-insensitive.

Todo

Contribute generic autodetection method to Click Extra?

Return type:

ExportFormat | None