meta_package_manager.sbom packageΒΆ

Export the installed-package inventory as Software Bill of Materials documents.

This subpackage is the home of every SBOM-flavored writer in mpm. Its existence mirrors the optional [sbom] install extra defined in pyproject.toml: a default pip install meta-package-manager does not pull cyclonedx-python-lib or spdx-tools, so the modules below guard their heavy imports with try/except and expose spdx_support / cyclonedx_support flags that callers check before instantiating the rendering classes.

Modules:

The public surface re-exported here is the only API meta_package_manager.cli and the test suite depend on. Internal helpers (license parsing, checksum maps, etc.) stay in their respective format modules.

class meta_package_manager.sbom.SBOM(export_format=ExportFormat.JSON)[source]ΒΆ

Bases: object

Utilities shared by all SBOM classes.

Defaults to JSON export format.

bundled_scan: bool = TrueΒΆ

Whether the scan was a --bundled enrichment pass.

Set by set_scan_completeness() from the CLI. True means the metadata extractors ran and upstream per-package SBOMs were merged (the default). False is the --minimal mode: bare inventory only, no extractor calls. Subclasses use this flag to populate completeness markers in their respective standards (incomplete vs complete in CycloneDX, EXTRACTED license handling in SPDX).

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]

set_scan_completeness(bundled)[source]ΒΆ

Record whether the run was a bundled enrichment pass.

Called once by the CLI right after init_doc() and before the first add_package(). The information flows into per-format completeness markers in the rendered document.

Return type:

None

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.

Return type:

ExportFormat | None

class meta_package_manager.sbom.SPDX(export_format=ExportFormat.JSON)[source]ΒΆ

Bases: SBOM

Generates an SPDX document from a list of packages.

SPDX 2.3 specifications.

Defaults to JSON export format.

DOC_ID = 'SPDXRef-DOCUMENT'ΒΆ

Document root ID.

document: DocumentΒΆ
seen_ids: set[str]ΒΆ
name_index: dict[tuple[str, str], str]ΒΆ
pending_relationships: list[tuple[str, str, str, Any]]ΒΆ
merged_docs: dict[str, str]ΒΆ
classmethod normalize_spdx_id(str)[source]ΒΆ

SPDX IDs must only contain letters, numbers, . and -.

Return type:

str

init_doc()[source]ΒΆ

SPDX document metadata specifications.

Return type:

None

add_package(manager, package, metadata=PackageMetadata(download_url=None, homepage=None, vcs_url=None, issue_tracker_url=None, distribution_url=None, license_declared=None, license_concluded=None, copyright_text=None, supplier=None, originator=None, description=None, summary=None, cpe=None, dependencies=(), checksums=(), files=(), files_analyzed=False, install_date=None, build_date=None, release_date=None, external_sbom_path=None, extra_purls=(), extras={}))[source]ΒΆ

SPDX package metadata specifications.

Return type:

None

finalize()[source]ΒΆ

Emit pending dependency relationships.

Walks the queue built by add_package() and emits each relationship only when both ends resolve to packages we actually included in the document. Dangling references (the target package is not installed) are dropped silently: the SBOM only describes what is on the system, not what could be.

Return type:

None

stats()[source]ΒΆ

Extend the base stats with SPDX-specific counters.

Adds the number of upstream documents merged into the aggregate, the count of transitive packages those upstream documents contributed (over and above the inventory pass), and the total relationship count partitioned into dependency vs descriptive edges. packages_total from the base reports inventory packages only; packages_in_document here is the full count after merge, which is what consumers of the file actually see.

Return type:

dict[str, object]

export()[source]ΒΆ

Similar to spdx_tools.spdx.writer.write_anything.write_file but write directly to provided stream instead of file path.

Return type:

str

class meta_package_manager.sbom.CycloneDX(export_format=ExportFormat.JSON)[source]ΒΆ

Bases: SBOM

Generates a CycloneDX document from a list of packages.

CycloneDX 1.7 specifications.

Defaults to JSON export format.

document: BomΒΆ
component_index: dict[tuple[str, str], Component]ΒΆ
pending_dependencies: list[tuple[Component, str, str]]ΒΆ
init_doc()[source]ΒΆ

CycloneDX document metadata specifications.

Return type:

None

add_package(manager, package, metadata=PackageMetadata(download_url=None, homepage=None, vcs_url=None, issue_tracker_url=None, distribution_url=None, license_declared=None, license_concluded=None, copyright_text=None, supplier=None, originator=None, description=None, summary=None, cpe=None, dependencies=(), checksums=(), files=(), files_analyzed=False, install_date=None, build_date=None, release_date=None, external_sbom_path=None, extra_purls=(), extras={}))[source]ΒΆ

CycloneDX package metadata specifications.

Return type:

None

finalize()[source]ΒΆ

Resolve queued dependency edges between Components.

Mirrors meta_package_manager.sbom.spdx.SPDX.finalize(). Dangling references (the dependency target is not in the inventory) are dropped silently.

Return type:

None

stats()[source]ΒΆ

Extend the base stats with CycloneDX-specific counters.

CycloneDX has no merge-content equivalent: per-package upstream SBOMs are linked through externalReferences[type=bom] rather than spliced in. The merged-document count therefore reports the number of components carrying a BOM external reference. The dependency-edge total walks the registered dependency graph and sums the dependsOn collection size across every entry.

Return type:

dict[str, object]

export()[source]ΒΆ

Serialize the document to its string representation.

Note

Unlike meta_package_manager.sbom.spdx.SPDX.export(), the generated document is not validated against its schema here. CycloneDX schema validation relies on cyclonedx-python-lib’s [validation] extra, which pulls in jsonschema and, transitively, rfc3987-syntax, lark, and lxml. To keep that stack out of mpm’s runtime dependencies, the validation runs in the test suite instead. See tests/test_cli_sbom.py.

Return type:

str

class meta_package_manager.sbom.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'ΒΆ

SubmodulesΒΆ

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] extra is not installed.

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.

Defaults to JSON export format.

bundled_scan: bool = TrueΒΆ

Whether the scan was a --bundled enrichment pass.

Set by set_scan_completeness() from the CLI. True means the metadata extractors ran and upstream per-package SBOMs were merged (the default). False is the --minimal mode: bare inventory only, no extractor calls. Subclasses use this flag to populate completeness markers in their respective standards (incomplete vs complete in CycloneDX, EXTRACTED license handling in SPDX).

packages_per_manager: dict[str, int]ΒΆ
enriched_per_manager: dict[str, int]ΒΆ
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]

set_scan_completeness(bundled)[source]ΒΆ

Record whether the run was a bundled enrichment pass.

Called once by the CLI right after init_doc() and before the first add_package(). The information flows into per-format completeness markers in the rendered document.

Return type:

None

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.

Return type:

ExportFormat | None

meta_package_manager.sbom.cyclonedx moduleΒΆ

CycloneDX 1.7 writer.

Heavy cyclonedx-python-lib imports are guarded behind a try/except block; cyclonedx_support reports whether the CycloneDX class can actually be used.

The license-normalization helper is shared with spdx and is imported from there rather than duplicated: SPDX license expressions are the lingua franca CycloneDX builds on, so the dependency direction is intentional and acyclic.

class meta_package_manager.sbom.cyclonedx.CycloneDX(export_format=ExportFormat.JSON)[source]ΒΆ

Bases: SBOM

Generates a CycloneDX document from a list of packages.

CycloneDX 1.7 specifications.

Defaults to JSON export format.

document: BomΒΆ
component_index: dict[tuple[str, str], Component]ΒΆ
pending_dependencies: list[tuple[Component, str, str]]ΒΆ
init_doc()[source]ΒΆ

CycloneDX document metadata specifications.

Return type:

None

add_package(manager, package, metadata=PackageMetadata(download_url=None, homepage=None, vcs_url=None, issue_tracker_url=None, distribution_url=None, license_declared=None, license_concluded=None, copyright_text=None, supplier=None, originator=None, description=None, summary=None, cpe=None, dependencies=(), checksums=(), files=(), files_analyzed=False, install_date=None, build_date=None, release_date=None, external_sbom_path=None, extra_purls=(), extras={}))[source]ΒΆ

CycloneDX package metadata specifications.

Return type:

None

finalize()[source]ΒΆ

Resolve queued dependency edges between Components.

Mirrors meta_package_manager.sbom.spdx.SPDX.finalize(). Dangling references (the dependency target is not in the inventory) are dropped silently.

Return type:

None

stats()[source]ΒΆ

Extend the base stats with CycloneDX-specific counters.

CycloneDX has no merge-content equivalent: per-package upstream SBOMs are linked through externalReferences[type=bom] rather than spliced in. The merged-document count therefore reports the number of components carrying a BOM external reference. The dependency-edge total walks the registered dependency graph and sums the dependsOn collection size across every entry.

Return type:

dict[str, object]

export()[source]ΒΆ

Serialize the document to its string representation.

Note

Unlike meta_package_manager.sbom.spdx.SPDX.export(), the generated document is not validated against its schema here. CycloneDX schema validation relies on cyclonedx-python-lib’s [validation] extra, which pulls in jsonschema and, transitively, rfc3987-syntax, lark, and lxml. To keep that stack out of mpm’s runtime dependencies, the validation runs in the test suite instead. See tests/test_cli_sbom.py.

Return type:

str

meta_package_manager.sbom.spdx moduleΒΆ

SPDX 2.3 writer plus the per-package upstream-SBOM merge logic.

Heavy spdx_tools imports are guarded behind a try/except block so this module is importable even when the optional [sbom] extra is not installed; in that case spdx_support is False and the SPDX class is still defined for type-hint compatibility but will not function (every public method depends on the missing imports).

_parse_license_expression and _coerce_spdx_string live here because they both touch spdx_tools types; cyclonedx imports the former for its own license normalization, which is one-way and acyclic.

class meta_package_manager.sbom.spdx.SPDX(export_format=ExportFormat.JSON)[source]ΒΆ

Bases: SBOM

Generates an SPDX document from a list of packages.

SPDX 2.3 specifications.

Defaults to JSON export format.

DOC_ID = 'SPDXRef-DOCUMENT'ΒΆ

Document root ID.

document: DocumentΒΆ
seen_ids: set[str]ΒΆ
name_index: dict[tuple[str, str], str]ΒΆ
pending_relationships: list[tuple[str, str, str, Any]]ΒΆ
merged_docs: dict[str, str]ΒΆ
classmethod normalize_spdx_id(str)[source]ΒΆ

SPDX IDs must only contain letters, numbers, . and -.

Return type:

str

init_doc()[source]ΒΆ

SPDX document metadata specifications.

Return type:

None

add_package(manager, package, metadata=PackageMetadata(download_url=None, homepage=None, vcs_url=None, issue_tracker_url=None, distribution_url=None, license_declared=None, license_concluded=None, copyright_text=None, supplier=None, originator=None, description=None, summary=None, cpe=None, dependencies=(), checksums=(), files=(), files_analyzed=False, install_date=None, build_date=None, release_date=None, external_sbom_path=None, extra_purls=(), extras={}))[source]ΒΆ

SPDX package metadata specifications.

Return type:

None

finalize()[source]ΒΆ

Emit pending dependency relationships.

Walks the queue built by add_package() and emits each relationship only when both ends resolve to packages we actually included in the document. Dangling references (the target package is not installed) are dropped silently: the SBOM only describes what is on the system, not what could be.

Return type:

None

stats()[source]ΒΆ

Extend the base stats with SPDX-specific counters.

Adds the number of upstream documents merged into the aggregate, the count of transitive packages those upstream documents contributed (over and above the inventory pass), and the total relationship count partitioned into dependency vs descriptive edges. packages_total from the base reports inventory packages only; packages_in_document here is the full count after merge, which is what consumers of the file actually see.

Return type:

dict[str, object]

export()[source]ΒΆ

Similar to spdx_tools.spdx.writer.write_anything.write_file but write directly to provided stream instead of file path.

Return type:

str