meta_package_manager.package module

Manager-agnostic meta_package_manager.package.Package data model and the meta_package_manager.package.PackageMetadata companion that augments it with data pulled from sources outside the package manager itself.

Defines the lightweight representation of a package (ID, name, installed and latest versions, architecture) that every manager operation yields, plus meta_package_manager.package.packages_asdict() to serialize a subset of its fields for output.

Package is the inventory plane: what the package manager itself reports through its native query commands. It backs every operation in meta_package_manager.manager.

PackageMetadata is the enrichment plane: licenses, supplier, checksums, declared dependency graph, on-disk per-package SBOMs, and other facts gathered through extra queries (CLI sub-commands, on-disk parsers, upstream registries). Populated by meta_package_manager.manager.PackageManager.package_metadata_batch(), consumed by meta_package_manager.sbom today and reserved for any future caller that wants more than the bare inventory.

Kept deliberately free of manager logic, so it can be imported without pulling in the manager engine (meta_package_manager.manager).

meta_package_manager.package.PURL_QUALIFYING_NAMESPACES = frozenset({'alpm', 'apk', 'cpan', 'deb', 'luarocks', 'rpm'})

pURL types whose namespace qualifies a package without being part of its ID.

For these types, the namespace names who distributes the package: the vendor for alpm, apk, deb and rpm, the author’s CPAN ID for cpan, the user manifest for luarocks. parse_purl() keeps the name alone for them, where joining the two would ask apt for debian/curl. Every other type reads the namespace as the part of the package ID before its last slash: an npm scope, a Composer vendor, a Homebrew tap.

Each type’s namespace_definition in the pURL type definitions settles which side it falls on.

meta_package_manager.package.purl_parts(package_id)[source]

Split package_id into the namespace and the name of its pURL.

Everything before the last slash is the namespace. The whole ID stays the name when the split would leave an empty segment, like an absolute path or a URL: a pURL parser drops empty segments, so the ID would not read back.

PURL_QUALIFYING_NAMESPACES plays no part here. A pURL mpm writes carries a manager ID as its type, and the managers whose ID is one of those types (apk, cpan, luarocks) never put a slash in a package ID, which tests/test_docstring_corpus.py holds across every documented package ID.

Return type:

tuple[str | None, str]

meta_package_manager.package.manager_purl(manager_id, package_id, version=None)[source]

Render the pURL tying package_id to manager_id, the manager ID as its type.

parse_purl() reads it back to the same manager ID, package ID and version, so a command handed this pURL skips looking the package up.

Rendered here, not by packageurl-python’s PackageURL.to_string(), which lowercases the name of some types, npm among them. The npm type definition keeps names case-sensitive, since packages published before 2015 may carry uppercase letters: JSONStream would come back as jsonstream. to_string() also leaves a / unencoded in a name (package-url/packageurl-python#123).

Todo

Render with PackageURL.to_string() once packageurl-python keeps the case of npm names (package-url/packageurl-python#230) and encodes a / in a name (package-url/packageurl-python#123).

Return type:

str

meta_package_manager.package.purl_quote(text)[source]

Percent-encode one pURL segment, a / included, keeping : as is.

Return type:

str

class meta_package_manager.package.Package(id, manager_id, name=None, description=None, installed_version=None, latest_version=None, arch=None)[source]

Bases: object

Lightweight representation of a package and its metadata.

id: str

ID is required and is the primary key used by the manager.

manager_id: str

Handy to backtrack whose manager this package belongs to.

The manager ID is good enough and allows for no coupling with the parent manager object.

name: str | None = None

Optional human-readable display name. Falls back to id in output rendering, so only set this when the manager provides a name that differs from the package ID.

description: str | None = None
installed_version: TokenizedString | str | None = None
latest_version: TokenizedString | str | None = None

Installed and latest versions are optional: they’re not always provided by the package manager.

installed_version and latest_version are allowed to temporarily be strings between __init__ and __post_init__. Once they reach the later, they’re parsed and normalized into either TokenizedString or None. They can’t be strings beyond that point, i.e. after the Package instance has been fully instantiated. We don’t know how to declare this transient state with type hints, so we’re just going to allow string type.

arch: str | None = None
property purl: PackageURL[source]

Returns the package’s pURL object.

purl_parts() splits the ID, so a scoped npm package or a tap-qualified formula carries its namespace the way the pURL specification expects.

static query_parts(query: str) → frozenset[str][source]

Split query into its contiguous alphanumeric segments.

Contrary to meta_package_manager.version.TokenizedString, does not split on collated number/alphabetic junctions.

Canonical tokenizer behind matches() and the search/installed/outdated query matching. Cached, since a run asks it the same question once per package of every manager.

Return type:

frozenset[str]

matches(query, extended=False, exact=False)[source]

Tell whether this package matches the free-form query.

Shared predicate behind the search, installed and outdated subcommands, so all three honor the same matching semantics:

  • Fuzzy (default): a case-insensitive, tokenized substring match. Any alphanumeric segment of query (see query_parts()) found in the package ID or name counts as a match.

  • Exact (exact=True): the raw query must equal the package ID or name verbatim (case-sensitive, whole-string).

  • Extended (extended=True): also look into the package description. Only meaningful when the description is populated, as it is for search results.

A query with no alphanumeric segment (empty or punctuation-only) never matches.

Return type:

bool

meta_package_manager.package.packages_asdict(packages, keep_fields)[source]

Yield each package as a dict holding the keep_fields subset of its fields.

The fields are read straight off the instance rather than through dataclasses.asdict(), which deep-copies every value: a version is an immutable TokenizedString that the renderers only read, and copying thousands of them per query cost more than the query’s own parsing.

class meta_package_manager.package.DependencyScope(*values)[source]

Bases: str, Enum

Maps loosely onto SPDX RelationshipType variants.

SBOM renderers translate these into RUNTIME_DEPENDENCY_OF, BUILD_DEPENDENCY_OF, etc.; CycloneDX collapses everything to its flat dependencies graph. Future non-SBOM consumers can apply their own mapping or just expose the raw scope label.

RUNTIME = 'runtime'
BUILD = 'build'
DEV = 'dev'
OPTIONAL = 'optional'
TEST = 'test'
RECOMMENDED = 'recommended'
class meta_package_manager.package.ChecksumAlgorithm(*values)[source]

Bases: str, Enum

Subset of algorithms shared by SPDX and CycloneDX schemas.

Used by Checksum to identify a content hash without coupling the data model to any specific SBOM library’s enum.

MD5 = 'MD5'
SHA1 = 'SHA1'
SHA256 = 'SHA256'
SHA512 = 'SHA512'
SHA3_256 = 'SHA3-256'
SHA3_512 = 'SHA3-512'
BLAKE2B_256 = 'BLAKE2b-256'
BLAKE2B_512 = 'BLAKE2b-512'
class meta_package_manager.package.Checksum(algorithm, value)[source]

Bases: object

A single (algorithm, value) pair.

algorithm: ChecksumAlgorithm
value: str
class meta_package_manager.package.Supplier(name, url=None)[source]

Bases: object

Distributor of the package.

Distinct from the originator: the supplier is whoever served the bits (Homebrew, Debian, PyPI), the originator is the upstream author.

name: str
url: str | None = None
class meta_package_manager.package.Originator(name, email=None, is_organization=False)[source]

Bases: object

Upstream author or organization that produced the package.

name: str
email: str | None = None
is_organization: bool = False
class meta_package_manager.package.Dependency(target_id, scope=DependencyScope.RUNTIME, version_constraint=None)[source]

Bases: object

A single edge in the package’s declared dependency graph.

target_id is the dependency’s manager-native identifier (e.g. openssl@3 for Homebrew). Renderers match it against the inventory’s installed packages to decide whether to emit a relationship.

target_id: str
scope: DependencyScope = 'runtime'
version_constraint: str | None = None
class meta_package_manager.package.FileEntry(path, sha256=None, sha1=None, md5=None)[source]

Bases: object

An installed file shipped by the package.

Only populated for managers that can cheaply enumerate file contents and hashes (dpkg .md5sums, pip RECORD). Omitted otherwise; the SBOM renderer leaves filesAnalyzed=False on the SPDX Package.

path: str
sha256: str | None = None
sha1: str | None = None
md5: str | None = None
class meta_package_manager.package.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=<factory>)[source]

Bases: object

Maximalist metadata collected for a single installed package.

Distinct from Package in scope: where Package carries only what the package manager itself surfaces through its inventory commands (id, name, version, arch), PackageMetadata carries the augmentations gathered through extra queries (richer CLI sub-commands, on-disk parsing of dist-info or per-package SBOMs, upstream registry lookups). Today it powers the maximalist mpm sbom --bundled output; the structure is deliberately generic so a future search, audit, or info display can reuse it.

All fields are optional. extras is the escape hatch for manager- native fields that don’t fit the portable model: a Homebrew tap, a pip classifier list, an apt Section. SBOM renderers consult known keys and surface the rest as CycloneDX properties.

download_url: str | None = None
homepage: str | None = None
vcs_url: str | None = None
issue_tracker_url: str | None = None
distribution_url: str | None = None
license_declared: str | None = None
license_concluded: str | None = None
copyright_text: str | None = None
supplier: Supplier | None = None
originator: Originator | None = None
description: str | None = None
summary: str | None = None
cpe: str | None = None
dependencies: tuple[Dependency, ...] = ()
checksums: tuple[Checksum, ...] = ()
files: tuple[FileEntry, ...] = ()
files_analyzed: bool = False
install_date: datetime | None = None
build_date: datetime | None = None
release_date: datetime | None = None
external_sbom_path: Path | None = None

Path to an on-disk upstream SBOM document for this package.

Brew formulae installed with HOMEBREW_SBOM=1 write a per-formula SPDX 2.3 file at <prefix>/sbom.spdx.json. The Homebrew extractor sets this so the SBOM renderer can merge the upstream document into the aggregate output (or attach it by reference).

extra_purls: tuple[PackageURL, ...] = ()

Additional purls when the manager identifies the same package through multiple coordinate systems (multi-arch, multi-origin, or the upstream registry a recipe builds from).

SBOM renderers emit them as extra package-manager references and index them as aliases of the package’s own purl, so a vulnerability scan can query a coordinate an advisory database recognizes.

extras: dict[str, object]

Manager-native metadata that does not map cleanly to portable fields. SBOM renderers may surface entries as CycloneDX properties.

is_empty()[source]

True if the extractor produced no meaningful metadata.

Used by the SBOM renderers to short-circuit field-by-field gating and by the CLI to log which managers contributed enrichment.

Return type:

bool

meta_package_manager.package.EMPTY_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={})

Sentinel returned by the default no-op extractor on the base meta_package_manager.manager.PackageManager. Consumers (the SBOM renderers today) treat EMPTY_METADATA exactly like --minimal mode for the package: no enrichment, no placeholders.