meta_package_manager.manager module¶
Abstract base class tying together every package manager definition.
Defines meta_package_manager.manager.PackageManager, the class each concrete
manager in meta_package_manager.managers inherits from, together with its
meta_package_manager.manager.MetaPackageManager metaclass and the
meta_package_manager.manager.ManagerScope classification.
A subclass declares its identity (supported platforms, version requirement, maintenance
status) and implements the operations it supports (installed, outdated,
install, upgrade, …). The CLI-execution engine it inherits lives in
meta_package_manager.execution, the operation vocabulary in
meta_package_manager.capabilities, and the package objects operations yield in
meta_package_manager.package. On top of the engine, this module adds the
availability policy: whether the manager is supported, fresh, and ready to use.
- meta_package_manager.manager.COOLDOWN_EXEMPT = datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)¶
Sentinel a
PackageManager.release_date()probe returns for a package outside the cooldown gate’s scope.A hybrid manager can serve two registries with different threat models: an AUR helper resolves the live, self-published AUR next to Arch’s official repositories, whose archive stages releases on its own (see the N/A rows of
docs/cooldown.md). The out-of-scope half reads as infinitely aged, so the hold logic ofPackageManager.cooldown_hold_reason()lets it through untouched, and the constant’s name keeps the probe’s intent readable where a baredatetime.minwould read as a bug.
- meta_package_manager.manager.JSON_FIELD_SELECTOR_REGEX = re.compile('^(?P<key>[^\\[\\]]+?)(?:\\[(?P<index>\\d+)\\])?$')¶
Parse a JSON field selector: a key name with an optional
[N]list index.A bare
versionmaps the package field to the item’sversionkey. Aversions[0]selector additionally picks one element out of a list-valued key (zerobrew reports each package’s installed versions as an array). Anything more nested stays out on purpose: a query needing real JSON traversal is better served by a custom parser. Shared byPackageManager.parse_json_items()and the declarative-manager validation inmeta_package_manager.definitions.
- class meta_package_manager.manager.ManagerScope(*values)[source]¶
Bases:
EnumFilesystem scope a package manager operates within.
- SYSTEM = 'system'¶
Manages software installed globally, machine-wide.
All currently-maintained managers are system-scoped.
- PROJECT = 'project'¶
Manages dependencies confined to a project’s working tree.
Not supported yet. The user-facing rationale, the ecosystems this would cover and the architectural work it waits on are catalogued once in Unsupported managers; the extension point is
meta_package_manager.manager.PackageManager.discover_projects().
- class meta_package_manager.manager.MetaPackageManager(name, bases, dct)[source]¶
Bases:
typeCustom metaclass used as a class factory for package managers.
Sets some class defaults, but only if they’re not redefined in the final manager class.
Also normalize list of platform, by ungrouping groups, deduplicate entries and freeze them into a set of unique platforms.
- class meta_package_manager.manager.PackageManager[source]¶
Bases:
CLIExecutorBase class from which all package manager definitions inherits.
Initialize
cli_errorslist.- scope: ClassVar[ManagerScope] = 'system'¶
Whether the manager operates on globally-installed software or project-local dependencies.
Defaults to
ManagerScope.SYSTEM, which covers every manager maintained today: they install and query software machine-wide. Project-scoped managers (Poetry, Bundler, Maven, …) resolve dependencies confined to a working tree and are not supported yet.
- unmaintained: bool = False¶
A manager whose upstream project is no longer maintained.
Covers projects that are officially retired and those we infer are abandoned: archived on their forge, left without a release or commit for years, formally superseded by a successor, or part of a discontinued platform. See the stability policy in
CLAUDE.mdfor the full criteria.An unmaintained manager is hidden from package selection by default (you can still use it by explicitly calling for it on the command line), and is exempt from the project stability policy: it may be dropped, in part or in full, in any release and without notice, once keeping it working becomes too burdensome.
Unmaintained managers are kept out of the functional and integration test matrices, so an unreliable or flaky one never blocks a release and we save CI resources. The commitment is to keep the wrapper for as long as that stays cheap: the cheap static invariants (ID format, attribute ordering, …) still apply for as long as the manager’s code lives in the source tree, to keep that code valid.
Every unmaintained manager must document itself through
unmaintained_message.
- unmaintained_message: str | None = None¶
Evidence and rationale for the
unmaintainedflag, as a MyST markdown block.Rendered into the documentation (the manager’s page, and a
⚠️marker in the manager tables). May embed markdown links to the archival notice, the successor project, or the discontinuation announcement. Required for every manager whoseunmaintainedflag is set, and only meaningful on such managers. Enforced bytest_unmaintained.
- maintenance_note: str | None = None¶
A watch note about a still-maintained upstream whose activity is slowing or whose status is ambiguous, as a MyST markdown block.
Unlike
unmaintained, this is purely informational: the manager stays in the default selection and in the test matrices. It renders as a{note}admonition atop the manager’s documentation page, flagging upstreams worth keeping an eye on (a slow release cadence, superseded-but-still-shipped tools, a discontinued platform still under vendor support). May embed markdown links. Mutually exclusive withunmaintained: a confirmed-dead manager carries anunmaintained_messageinstead. Enforced bytest_maintenance_note.
- id: str = 'packagemanager'¶
Package manager’s ID.
Derived by defaults from the lower-cased class name in which underscores
_are replaced by dashes-.This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.
- name: str = 'PackageManager'¶
Return package manager’s common name.
Default value is based on class name.
- homepage_url: str | None = None¶
Home page of the project, only used in documentation for reference.
- logo: str | None = None¶
Slug of the brand mark standing for this manager in the documentation.
Names an SVG vendored under
docs/assets/managers/, whose provenance and license are recorded indocs/assets/managers/logos.yaml. Inlined at the top of the manager’s page bymeta_package_manager._docs; a manager leaving it unset keeps the page’s default package glyph.Several managers legitimately share one slug, either because they wrap the same upstream (
brewandcask) or because the tool has no mark of its own and its ecosystem’s stands in (aptunder Debian’s swirl,cargounder Rust’s gear). Documentation-only, likehomepage_url: no CLI output reads it.
- brewfile_entry_type: ClassVar[str | None] = None¶
Name of the Brewfile DSL entry type this manager maps to, or
Noneif the manager has no Brewfile equivalent.Set by the subset of managers Homebrew Bundle’s DSL covers, and consumed by
meta_package_manager.brewfilewhen rendering the output ofmpm dump --brewfile. Which manager maps to which entry is tabulated from these declarations in Snapshot and export, section “Brewfile”, where the export’s own quirks are documented too.
- brewfile_skip_warning: ClassVar[str | None] = None¶
Optional stderr warning emitted when this manager’s installed packages are excluded from a Brewfile dump.
Set on managers where silently dropping the entries would mislead the user. The string supports a single
{count}placeholder for the installed-package count.
- platforms: frozenset[Platform] | Group | Platform | Iterable[Platform | Group] = frozenset({})¶
List of platforms supported by the manager.
Allows for a mishmash of platforms and groups of platforms. Will be normalized into a
frozensetofPlatforminstances at instantiation.
- requirement: str | None = None¶
Version requirement specifier.
Supports a comma-separated range of constraints (e.g.
">=1.20.0,<2.0.0"). A bare version string like"1.20.0"is treated as>=1.20.0.Parsed by
meta_package_manager.version.VersionRange.Defaults to
None, which deactivates version check entirely.
- virtual: bool = True¶
Should we expose the package manager to the user?
Virtual package manager are just skeleton classes used to factorize code among managers of the same family.
- ignore_auto_updates: bool = True¶
Some managers can report or ignore packages which have their own auto-update mechanism.
- split_name_version(token)[source]¶
Split a dash-joined
<package_id>-<version>token into its two parts.Matches
tokenagainst_NAME_VERSION_REGEXP(or the subclass’s override of it) and returns the(package_id, version)pair, orNonewhen the token carries no recognizable version. Shared by every manager whose listings glue the name and version together.
- parse_json(output)[source]¶
Parse a query’s JSON
output, tolerating empty and malformed captures.The shared first step of every JSON-emitting query, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._parse_spec_output()). ReturnsNonewhen the command produced no output (a manager with nothing to report often prints nothing at all), and when the output is not valid JSON, which logs one warning tagged with the manager ID instead of raising: a query that cannot be parsed yields no packages, mirroring how the fan-out commands swallow a failed CLI call into an empty result.Queries whose failure semantics differ keep their own parsing: a per-line NDJSON stream (
pkg search), a hardCLIErroron malformed payloads (pwsh-gallery), a best-effort metadata enrichment logging atDEBUG(brew info).
- parse_regex_lines(pattern, output)[source]¶
Yield one package per line of
outputmatchingpattern.The shared engine of every line-oriented text listing, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._make_query_property()). The pattern is searched in each line, and its named groups map straight onto the package fields:package_id(required: a match without one is skipped),installed_version,latest_version,name,descriptionandarch, empty and absent groups being dropped.Managers whose listings need per-line post-processing (multi-version reduction, name/version splitting, cross-query joins) keep their own loop and this stays their reference semantics.
- parse_json_items(output, *, list_path=None, fields)[source]¶
Yield one package per item of a JSON listing.
The shared engine of every flat-JSON query, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._make_query_property()). The document is parsed throughparse_json()(so a malformed payload warns and yields nothing), the package array is reached by walking the dottedlist_path(Nonewhen the document is itself the array), andfieldsmaps each package field (package_id, required, plus any ofinstalled_version,latest_version,name,description,arch) to its JSON selector: a key name with an optional[N]list index, likeversionorversions[0](seeJSON_FIELD_SELECTOR_REGEX). Items missing theirpackage_idand fields resolving toNoneare dropped.
- package(**kwargs)[source]¶
Instantiate a
Packageobject from the manager.Sets its
manage_idto the manager it belongs to.- Return type:
- brewfile_entry(package)[source]¶
Return
(entry_name, entry_options)for a Brewfile line, orNoneto skip the package.Default: emit
meta_package_manager.package.Package.idas the entry name with no options. Override on managers whose Brewfile DSL counterpart expects a different shape:masuses the app name withid: ADAM_ID,flatpakaddswith: ["remote"]. Only called whenbrewfile_entry_typeis set.
- property available: bool[source]¶
Is the package manager available and ready-to-use on the system?
Returns
Trueonly if the main CLI:
Short, human-readable explanation of why
availableisFalse, orNoneif the manager is available.Returned in priority order so the most actionable cause is reported first: platform support, then CLI lookup, then executable bit, then version requirement.
- property installed: Iterator[Package]¶
List packages currently installed on the system.
Optional. Will be simply skipped by mpm if not implemented.
- installed_or_empty()[source]¶
Materialized
installed, or an empty tuple on CLI failure.Best-effort inventory snapshot for the
installed,dumpandsbomsubcommands, and for theinstalled_idslookup behindremoveandupgrade <packages>: each wants “give me what’s installed, and just skip this manager if its CLI blew up” rather than re-implementing the samemeta_package_manager.execution.CLIErrorswallow. Logs one canonical warning on error and returns()so the caller carries on with the other managers.
- property installed_ids: frozenset[str][source]¶
Installed package IDs, materialized once from
installed_or_empty().Routed through the tolerant
installed_or_empty()rather thaninstalled()because its callers ask a discovery question: which managers have this package? A manager whose CLI just failed has no answer to give, which is not the same as a fatal error. Sourcing a spec forremoveandupgrade <packages>reads this for every selected manager, so a single broken CLI would otherwise abort the whole command before the managers that do have the package are ever tried.Contrast
installed_version_map, which deliberately keeps raising: it is read from inside anoutdatedparser, where an empty map does not mean “no answer” but silently reports every outdated package with an unknown installed version.
- property installed_version_map: dict[str, TokenizedString | str | None][source]¶
Installed versions keyed by package ID, materialized once from
installed().Convenience for
outdatedparsers that report each package’s latest version but not its currently-installed one, and so must look the latter up by ID (snap,xbps). The value mirrorsmeta_package_manager.package.Package.installed_version, whose declared type still carries the transientstrit normalizes away in__post_init__.
- package_metadata_batch(packages)[source]¶
Yield
(package, metadata)pairs enriched with whatever rich per-package data this manager can surface.Called by
mpm sbomin--bundledmode to populate licenses, checksums, download URLs, supplier/originator, and the declared dependency graph. The base implementation yieldsmeta_package_manager.package.EMPTY_METADATAfor each package and stays compatible with managers that do not (yet) expose richer metadata: their SBOM entries stay at the minimalPackagelevel, matching the historical and--minimalmodes.Manager subclasses override this with their native query path:
bulk shell-outs when the CLI accepts a package list (
brew info --json=v2 --installed,dpkg-query -W,apt-cache show);on-disk parsing when the metadata already lives on the filesystem (pip’s
.dist-infodirectories, Homebrew’s per-formulasbom.spdx.json, dpkg’s.md5sums).
The yielded pairs do not need to preserve the input order; the SBOM renderer matches by
Packageidentity. Implementations are expected to swallow per-package extraction errors and yieldmeta_package_manager.package.EMPTY_METADATAfor the affected packages rather than failing the whole scan: a single misbehaving formula must not abort an enrichment pass spanning hundreds of packages.Todo
Today every extractor is local-only (shell-outs to the manager’s CLI, plus on-disk reads). When extractors start reaching for network resources (PyPI’s JSON API, npm’s registry, crates.io, GitHub’s security advisories) the
--bundledflag will no longer be a fine-grained enough knob: some users will want enrichment but not network traffic (offline scans, CI without egress). The natural split is a future--network/--no-networkflag layered under--bundledto gate the network-touching code paths specifically, leaving local enrichment always-on for--bundled.- Return type:
- property outdated: Iterator[Package]¶
List installed packages with available upgrades.
Optional. Will be simply skipped by mpm if not implemented.
- property refiltered_outdated: Iterator[Package]¶
Wraps
outdated()with a version-equality filter.Some package managers report packages as outdated when the version strings differ at the character level but are numerically equal after parsing (e.g., Perl floating-point versions
2.0000vs2.000000). This filter drops those false positives.
- release_date(package_id)[source]¶
Publication timestamp of the latest release of a package.
The probe behind the synthesized per-package cooldown gate (see
cooldown_hold_reason()): a manager without a nativecooldown_env_varthat implements this method becomes gateable, package by package.The contract binds the timestamp’s provenance, not just its shape:
Returns a timezone-aware
datetime, orNonewhen the registry answers but carries no date (the gate then fails closed under the defaultenforceposture).The timestamp must be server-set: stamped by the registry, store or build service at publication. A client-set or package-embedded date (a git commit date, an archive metadata field the author writes) is forgeable by exactly the attacker the cooldown exists to stop, and must never back this probe.
The date is the one of the latest available release, the version the manager would resolve absent a pin. Implementations should read it through the manager’s own CLI, so the probe sees the same registry, mirrors and authentication as the install it guards.
Optional. Will be simply skipped by mpm if not implemented.
- cooldown_hold_reason(package_id)[source]¶
Decide whether the release-age cooldown holds back one package.
The per-package half of the gate, for managers that implement the
release_date()probe instead of carrying a nativecooldown_env_var. ReturnsNonewhen the package may proceed: no active probe-backed cooldown, or a publication old enough to clear the window. Returns a human-readable hold reason otherwise, which the caller renders in its trail and logs.The probe is fail-closed: a publication date that cannot be read (probe failure, or a registry carrying no date) holds the package under the default
enforceposture, and only abest-effortpolicy lets it through, unguarded.
- property orphans: Iterator[Package]¶
List packages installed as dependencies that nothing requires anymore.
The read-only counterpart of the
--orphansaction flags: wherempm cleanup --orphansremoves the orphans, this query only reports them, through the manager’s native listing (pacman --query --deps --unrequired,brew autoremove --dry-run,dnf repoquery --unneeded, …). mpm builds no dependency graph: the manager decides what is orphaned.Optional. Will be simply skipped by mpm if not implemented.
- search(query, extended, exact)[source]¶
Search packages available for install.
There is no need for this method to be perfect and sensitive to
extendedandexactparameters. If the package manager is not supporting these kind of options out of the box, just returns the closest subset of matching package you can come up with. Finer refiltering will happens in themeta_package_manager.manager.PackageManager.refiltered_search()method below.Optional. Will be simply skipped by mpm if not implemented.
- cli_names: tuple[str, ...] = ('packagemanager',)¶
List of CLI names the package manager is known as.
This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.
- ..hint::
This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named
pythonorpython3.
By default, this property’s value is derived from the manager’s ID (see the
MetaPackageManager.__init__method above).
- refiltered_search(query, extended, exact)[source]¶
Returns search results with extra manual refiltering to refine gross matchings.
Some package managers returns unbounded results, and/or don’t support fine search criterions. In which case we use this method to manually refilters
meta_package_manager.manager.PackageManager.search()results to either exclude non-extended or non-exact matches.Returns a generator producing the same data as the
meta_package_manager.manager.PackageManager.search()method above.Tip
If you are implementing a package manager definition, do not waste time to filter CLI results. Let this method do this job.
Instead, just implement the core
meta_package_manager.manager.PackageManager.search()method above and try to produce results as precise as possible using the native filtering capabilities of the package manager CLI.
- install(package_id, version=None)[source]¶
Install one package and one only.
Allows a specific
versionto be provided.- Return type:
- upgrade_all_cli()[source]¶
Returns the complete CLI to upgrade all outdated packages on the system.
- upgrade_one_cli(package_id, version=None)[source]¶
Returns the complete CLI to upgrade one package and one only.
Allows a specific
versionto be provided.
- upgrade_all_cli_excluding(package_ids)[source]¶
Returns the CLI upgrading all outdated packages except the named ones.
Optional refinement of
upgrade_all_cli()for managers whose full upgrade is one transaction with a native exclusion flag (pacman’s--ignore). Under an active probe-backed cooldown,mpmprefers this over per-package upgrades: the manager keeps its own transaction and dependency ordering, and only the held packages are left out.
- upgrade(package_id=None, version=None)[source]¶
Perform an upgrade of either all or one package.
Executes the CLI provided by either
meta_package_manager.manager.PackageManager.upgrade_all_cli()ormeta_package_manager.manager.PackageManager.upgrade_one_cli().If the manager doesn’t provides a full upgrade one-liner (i.e. if
meta_package_manager.manager.PackageManager.upgrade_all_cli()raisesNotImplementedError), then the list of all outdated packages will be fetched (viameta_package_manager.manager.PackageManager.outdated()) and each package will be updated one by one by callingmeta_package_manager.manager.PackageManager.upgrade_one_cli().See for example the case of
meta_package_manager.managers.pip.Pip.upgrade_one_cli().An active probe-backed cooldown (see
cooldown_hold_reason()) routes through_upgrade_all_with_cooldown()instead of the plain one-shot command, so individual too-fresh releases can be held back while the rest of the upgrade proceeds.- Return type:
- remove(package_id)[source]¶
Remove one package and one only.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- remove_orphan(package_id)[source]¶
Remove one package together with the dependencies it alone pulled in.
The opt-in counterpart to
meta_package_manager.manager.PackageManager.remove(), surfaced asmpm remove --orphans. It maps to the manager’s native “remove and drop now-unneeded dependencies” verb (apt remove --auto-remove,pacman --remove --recursive,dnf autoremove, …), so mpm builds no dependency graph of its own.Optional. A manager with no such native verb leaves this
NotImplementedError;mpm remove --orphansthen falls back tometa_package_manager.manager.PackageManager.remove()and logs oneINFOcapability-skip.- Return type:
- sync()[source]¶
Refresh package metadata from remote repositories.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- cleanup()[source]¶
Run the manager’s non-destructive cleanup categories.
Not an operation managers define anymore:
cleanupis the fixed composition of the non-destructive category methods a manager overrides (cleanup_cache(), thencleanup_repair()). The orphan sweep never joins in, native or synthesized: it is the one category that removes packages, so it only runs on an explicitmpm cleanup --orphans(or a directcleanup_orphan()call), keeping a plaincleanuppackage-preserving on every manager.A manager overriding no category method does not advertise the
cleanupoperation at all (seemeta_package_manager.capabilities.implements()) and this composer is then a no-op.- Return type:
- cleanup_orphan()[source]¶
Remove every orphaned package on the system, sparing the caches.
The system-wide “remove all packages nothing depends on anymore” sweep (
apt autoremove,brew autoremove,flatpak uninstall --unused, …). The one cleanup category that removes packages, so it is deliberately kept out of the plaincleanup()composition and only runs on an explicitmpm cleanup --orphans.Distinct from
meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As withcleanup(), mpm builds no dependency graph: the manager decides what is orphaned.A manager with no native sweep verb is backfilled by this base implementation when it supports both the
orphansquery and package removal: list the orphans, remove each one (withremove_orphan()when available, so every listed root takes its own now-orphaned subtree along), then re-query and repeat until the listing settles, since removing an orphan can orphan its own dependencies. The exact pattern of the synthesized fullupgrade --all, and the in-process equivalent of Arch’s classicpacman -Rns $(pacman -Qtdq)idiom. The re-query loop stops as soon as a round makes no progress, so removal failures cannot spin it forever.A manager implementing neither a native sweep nor the
orphansquery propagatesNotImplementedError, andmpm cleanup --orphanssimply skips it.- Return type:
- cleanup_cache()[source]¶
Prune the manager’s caches, downloads and other left-over artifacts.
The cache category of
cleanup(), surfaced asmpm cleanup --cacheand subtracted by--skip-cache(apt clean,dnf clean all,brew cleanup,npm cache clean, …). The broadest category: for most managers the whole cleanup amounts to it.Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- cleanup_repair()[source]¶
Verify and repair the manager’s local installation state.
The repair category of
cleanup(), surfaced asmpm cleanup --repairand subtracted by--skip-repair(flatpak repair --user).Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- doctor_cli()[source]¶
Returns the complete CLI running the manager’s native self-diagnosis.
The invocation must be read-only (
brew doctor,pip check,pacman --database --check, …):doctor()runs it, never mpm’s mutating machinery. The surveyed doctor verbs share one convention this contract leans on: a non-zero exit code means problems were found.Optional. Will be simply skipped by mpm if not implemented.
- doctor()[source]¶
Run the native self-diagnosis, returning
(healthy, report).Runs
doctor_cli()and interprets the outcome with a contract of its own, distinct from every other operation:Health is the exit code alone.
run()’s failure gate tolerates a non-zero exit with a silent<stderr>(a benign status for query parsers), but for a diagnosis that exit is the verdict:pip checkreports its conflicts on<stdout>only and would read as healthy under the gate.The report merges both streams. The tools split their findings across them (
brew doctorwarns on<stderr>), and the report is relayed verbatim to the user: there is nothing to parse.The diagnosis is not an error. The failure-gate entry an unhealthy exit may have accumulated is reclaimed from
cli_errors, so the end-of-run error summary is not inflated by a verdict mpm doctor``already reports on its own. The gate’s``WARNING diagnosis relay is skipped for the same reason (doctorsits in the gate’s_DIAGNOSIS_EXEMPT_OPERATIONS): the findings land in the report, verbatim. A run that never completed (timeout, interrupt, missing binary) keeps its entry: that is a genuine plumbing error, and the manager reports unhealthy.
- discover_projects()[source]¶
Locate project trees this manager governs by scanning the filesystem.
Extension point reserved for
ManagerScope.PROJECTmanagers: detecting virtual environments, lockfiles, or project manifests scattered across the filesystem.Caution
Not implemented for any manager yet. System-scoped managers (the default) own no project trees to discover.
Todo
Implement project-scope discovery. The candidate ecosystems, the project files that signal each and the architecture this waits on are catalogued in Unsupported managers.