meta_package_manager packageΒΆ

Meta Package Manager: a unified CLI wrapping many package managers.

Root package. Holds the canonical __version__; the mpm entry point lives in meta_package_manager.cli.

SubpackagesΒΆ

SubmodulesΒΆ

meta_package_manager.bar_plugin moduleΒΆ

Xbar and SwiftBar plugin for Meta Package Manager (the mpm CLI).

Default update cycle should be set to several hours so we have a chance to get user’s attention once a day. Higher frequency might ruin the system as all checks are quite resource intensive, and Homebrew might hit GitHub’s API calls quota.

meta_package_manager.bar_plugin.SWIFTBAR_MIN_VERSION = (2, 1, 2)ΒΆ

SwiftBar v2.1.2 fix an issue with multiple parameters in the font strings.

See: https://github.com/swiftbar/SwiftBar/issues/445

meta_package_manager.bar_plugin.XBAR_MIN_VERSION = (2, 1, 7)ΒΆ

Xbar v2.1.7-beta is the latest version available on Homebrew.

meta_package_manager.bar_plugin.MPM_MIN_VERSION = (5, 0, 0)ΒΆ

Mpm v5.0.0 was the first version taking care of the complete layout rendering.

meta_package_manager.bar_plugin.MPM_TIMEOUT = 60ΒΆ

Maximum duration in seconds the plugin lets any single mpm call run.

Passed as --timeout to every mpm invocation so the plugin is never at the mercy of mpm’s own per-operation defaults, which are tuned for interactive CLI use and far too long for a background menubar refresh (120s for read-only queries, 500s for state-changing operations like sync). A wedged package manager then fails the whole refresh in a minute instead of freezing the menubar for several.

class meta_package_manager.bar_plugin.MPMPlugin[source]ΒΆ

Bases: object

Implements the minimal code necessary to locate and call the mpm CLI on the system.

Once mpm is located, we can rely on it to produce the main output of the plugin.

The output must supports both Xbar dialect and SwiftBar dialect.

static getenv_str(var, default=None)[source]ΒΆ

Utility to get environment variables.

Note that all environment variables are strings. Always returns a lowered-case string.

Return type:

str | None

static getenv_bool(var, default=False)[source]ΒΆ

Utility to normalize boolean environment variables.

Relies on configparser.RawConfigParser.BOOLEAN_STATES to translate strings into boolean. See: https://github.com/python/cpython/blob/3c298e2e385fc6f462abaada2fd680deb1a2b58e/Lib/configparser.py#L596-L597

Return type:

bool

static normalize_params(font_string, valid_ids=None)[source]ΒΆ

Parse a multi-parameters string and return a normalized string.

The string is expected to be a space-separated list of parameters, each parameter being a key/value pair separated by an equal sign.

Only keeps the parameters that are in the valid_ids set and ignores the rest. By default, only color, font and size are kept.

Multiple values for the same parameter will be deduplicated, and the last one will be kept.

Available parameters are: - https://github.com/swiftbar/SwiftBar?tab=readme-ov-file#parameters - https://github.com/matryer/xbar-plugins/blob/main/CONTRIBUTING.md#parameters

Return type:

str

static str_to_version(version_string)[source]ΒΆ

Transforms a string into a tuple of integers representing a version.

Return type:

tuple[int, ...]

static version_to_str(version_tuple)[source]ΒΆ

Transforms a tuple of integers representing a version into a string.

Return type:

str

property table_rendering: boolΒΆ

Aligns package names and versions, like a table, for easier visual parsing.

If True, will aligns all items using a fixed-width font.

property default_font: strΒΆ

Make it easier to change font, sizes and colors of the output.

property monospace_font: strΒΆ

Make it easier to change font, sizes and colors of the output.

property error_font: strΒΆ

Error font is based on monospace font.

property is_swiftbar: boolΒΆ

SwiftBar is kind enough to tell us about its presence.

static search_venv(folder)[source]ΒΆ

Search for signs of a virtual env in the provided folder.

Returns CLI arguments that can be used to run mpm from the virtualenv context, or None if the folder is not a venv.

Inspired by autoswitch_virtualenv.plugin.zsh and uv’s get_interpreter_info.py.

Return type:

tuple[str, ...] | None

search_mpm()[source]ΒΆ

Iterate over possible CLI commands to execute mpm.

Should be able to produce the full spectrum of alternative commands we can use to invoke mpm over different context.

The order in which the candidates are returned by this method is conserved by the ranked_mpm() method below.

We prioritize venv-based findings first, as they’re more likely to have all dependencies installed and sorted out. They’re also our prime candidates in unittests.

Then we search for system-wide installation. And finally Python modules.

Return type:

Generator[tuple[str, ...], None, None]

check_mpm(mpm_cli_args)[source]ΒΆ

Test-run mpm execution and extract its version.

Return type:

tuple[bool, bool, tuple[int, ...] | None, str | Exception | None]

property ranked_mpm: list[tuple[tuple[str, ...], bool, bool, tuple[int, ...] | None, str | Exception | None]]ΒΆ

Rank the mpm candidates we found on the system.

Sort them by: - runnability - up-to-date status - version number - error

On tie, the order from search_mpm is respected.

property best_mpm: tuple[tuple[str, ...], bool, bool, tuple[int, ...] | None, str | Exception | None]ΒΆ
static pp(label, *args)[source]ΒΆ

Print one menu-line with the Xbar/SwiftBar dialect.

First argument is the menu-line label, separated by a pipe to all other non- empty parameters, themselves separated by a space.

Skip printing of the line if label is empty. A None parameter renders nothing, so a package without an upgrade CLI still gets its label-only menu line.

Return type:

None

static print_error_header()[source]ΒΆ

Generic header for blocking error.

Return type:

None

print_error(message, submenu='')[source]ΒΆ

Print a formatted error message line by line.

A red, fixed-width font is used to preserve traceback and exception layout. For compactness, the block message is dedented and empty lines are skipped.

Message is always casted to a string as we allow passing of exception objects and have them rendered.

Return type:

None

print_menu()[source]ΒΆ

Print the main menu.

Return type:

None

meta_package_manager.bar_plugin_renderer moduleΒΆ

mpm-side renderer that builds Xbar/SwiftBar plugin output.

Lives in its own module rather than in meta_package_manager.bar_plugin because that module is intentionally stdlib-only: the meta_package_manager.bar_plugin.MPMPlugin class is the script that gets installed as the user’s actual bar plugin and must stay light on dependencies.

This module is the heavier mpm-side companion that augments the shippable plugin code with click_extra, boltons, the manager pool, and the theme system to produce the final rendered output from mpm outdated --plugin-output.

meta_package_manager.bar_plugin_renderer.VERSION_PREFIX_COLOR = 245ΒΆ

Xterm-256 palette index coloring the unchanged version prefix in menu lines.

The CLI table keeps meta_package_manager.version.diff_versions()’s default bright_black (SGR 90), which terminals remap to their own theme. SwiftBar instead hard-maps SGR 90 to a fixed NSColor.darkGray, near-invisible on a dark-mode menu, while its 256-color support renders palette index 245 as a theme-neutral mid-gray (#8a8a8a), legible on both appearances. Xbar strips the ANSI codes it does not render, so the choice is inert there.

meta_package_manager.bar_plugin_renderer.LIGHT_MENU_OLD_COLOR = 124ΒΆ

Palette index for the old-version (red) suffix on a light-appearance menu.

#af0000, a 6.5:1 contrast ratio on the cream material. See BarPluginRenderer.menu_diff_colors() for why the override exists.

meta_package_manager.bar_plugin_renderer.LIGHT_MENU_NEW_COLOR = 23ΒΆ

Palette index for the new-version (green) suffix on a light-appearance menu.

#006600, a 6.3:1 contrast ratio on the cream material. See BarPluginRenderer.menu_diff_colors().

meta_package_manager.bar_plugin_renderer.DARK_MENU_NEW_COLOR = 46ΒΆ

Palette index for the new-version (green) suffix on a dark-appearance menu.

#00ff00, lifting the worst-case contrast from 4.0:1 (the adaptive NSColor.systemGreen) to 5.8:1 over a bright wallpaper showing through the translucent menu. The old-version (red) suffix keeps systemRed: it is already the most readable recognizable red the xterm-256 palette can express (a pure #ff0000 scores lower, and brighter options read as orange). See BarPluginRenderer.menu_diff_colors().

class meta_package_manager.bar_plugin_renderer.BarPluginRenderer[source]ΒΆ

Bases: MPMPlugin

All utilities used to render output compatible with both Xbar and SwiftBar plugin dialect.

The minimal code to locate mpm, then call it and print its output resides in the plugin itself at meta_package_manager.bar_plugin.MPMPlugin.best_mpm().

All other stuff, especially the rendering code, is managed here, to allow for more complex layouts relying on external Python dependencies. This also limits the number of required updates on the plugin itself.

property submenu_layout: boolΒΆ

Group packages into manager sub-menus.

If True, will replace the default flat layout with an alternative structure where actions are grouped into submenus, one for each manager.

Value is sourced from the VAR_SUBMENU_LAYOUT environment variable.

property menu_diff_colors: dict[str, int]ΒΆ

Appearance-adaptive version-diff suffix colors for the menu.

SwiftBar maps meta_package_manager.version.diff_versions()’s default SGR 31/32 suffixes to the adaptive NSColor.systemRed/systemGreen, and exports the menu appearance in the OS_APPEARANCE environment variable (which propagates to the mpm outdated –plugin-output subprocess). On the translucent β€œLiquid Glass” menus of recent macOS releases these system colors lose contrast against the material, so override them per appearance:

  • A light menu washes out both suffixes (the green measured 1.9:1), so darken them to LIGHT_MENU_OLD_COLOR and LIGHT_MENU_NEW_COLOR.

  • A dark menu over a bright wallpaper dims the green to 4.0:1, so brighten it to DARK_MENU_NEW_COLOR; the red keeps systemRed, already the most readable red the palette allows.

The result is returned as diff_versions keyword arguments. When the variable is absent (a consumer like Xbar, which strips these codes anyway) return an empty mapping, keeping the system-color defaults.

property mpm_cli: tuple[str, ...]ΒΆ

Absolute mpm invocation the menu actions are routed through.

Re-enters the very interpreter rendering the menu, so a click runs the mpm the plugin called and resolves the same configuration file. Derived from sys.executable rather than sys.argv[0]: the former is always an absolute path to a runnable entry point, while the latter degrades to a console script, a __main__.py or a bare -c depending on how mpm was started. A Nuitka-compiled mpm is its own interpreter, so it is invoked directly instead of through the module.

Note

The candidates meta_package_manager.bar_plugin.MPMPlugin.search_mpm() produces are deliberately not reused here. The venv ones lead with a bare uv / pipenv / poetry command name, while a bar app spawns a menu action with the bare launchd PATH, where such a name does not resolve.

static render_cli(cmd_args)[source]ΒΆ

Return a formatted CLI compatible with Xbar and SwiftBar plugin format.

I.e. a string with this schema:

shell=cmd_args[0] param1=cmd_args[1] param2=cmd_args[2] ...
Return type:

str

print_cli_item(*args)[source]ΒΆ

Print two CLI entries:

  • one that opens a visible terminal so the user can follow the execution

  • a second one, reachable by holding the Option key, that runs silently

Return type:

None

print_upgrade_all_item(manager, submenu='')[source]ΒΆ

Print the menu entry to upgrade all outdated package of a manager.

Return type:

None

render(outdated_data)[source]ΒΆ

Wraps the _render() method above to capture its <stdout> output.

Every producer down the _render path (the inherited pp and print_error included) writes through bare print calls, so redirecting <stdout> captures the whole rendering.

Return type:

str

add_upgrade_cli(outdated_data)[source]ΒΆ

Augment the outdated data from mpm outdated subcommand with upgrade CLI fields for bar plugin consumption.

Every menu action is an mpm_cli invocation restricted to the manager owning the section (mpm --brew upgrade wget), never that manager’s own native command. Going back through mpm is what subjects a click to the same policy as the run that rendered the menu: the configuration file found on the system, and with it the release-age cooldown, the manager selection, the sudo policy and the per-manager overrides. A native command escapes all of them, silently upgrading a package mpm itself would have held back.

Only the manager selector and the operation are passed, so every other setting is resolved from the user’s configuration at click time.

A manager is offered the action only when it implements() it, which is the same predicate mpm uses to route the subcommand: a manager it would skip gets a None CLI and renders as a label-only menu line.

print(outdated_data)[source]ΒΆ

Print the final plugin rendering to <stdout>.

Capturing the output of the plugin and re-printing it will introduce an extra line return, hence the extra call to rstrip().

Colors are forced on echo’s auto-detection: the bar plugin captures mpm outdated --plugin-output through a pipe, where echo would strip every ANSI code and the version-diff colors would never reach SwiftBar or Xbar. TTY detection is meaningless for this dialect, which flags ANSI rendering per line with the ansi=true/ansi=false parameters. An explicit opt-out (--color=never, NO_COLOR) is still honored: only the automatic (None) state is overridden.

Return type:

None

meta_package_manager.brewfile moduleΒΆ

Render the installed-package inventory as a Brewfile.

Defines build_brewfile() and the helpers used by mpm dump --brewfile to emit a Brewfile that brew bundle install can consume.

Note

Brewfile is a Ruby DSL. The format reference is the Homebrew Bundle source at Library/Homebrew/bundle/dsl.rb and the extensions under Library/Homebrew/bundle/extensions/ (brew 6.0.0+).

meta_package_manager.brewfile.BUNDLE_ENTRY_TYPES: tuple[str, ...] = ('tap', 'brew', 'cask', 'mas', 'vscode', 'npm', 'cargo', 'uv', 'winget', 'flatpak')ΒΆ

Canonical emission order of Brewfile sections.

Mirrors the registration order of Homebrew::Bundle.dump_package_types and the extensions under Library/Homebrew/bundle/extensions/. tap always comes first so that any third-party tap a downstream brew or cask entry references is registered before the install step runs.

meta_package_manager.brewfile.DEFAULT_TAPS: frozenset[tuple[str, str]] = frozenset({('homebrew', 'cask'), ('homebrew', 'core')})ΒΆ

Taps that brew enables by default. Never emitted as explicit tap lines.

meta_package_manager.brewfile.quote(value)[source]ΒΆ

Ruby-compatible double-quoted string literal.

brew bundle dump uses Ruby’s String#inspect: double quotes with backslash escapes for control characters and unicode. json.dumps(..., ensure_ascii=False) produces the same output for ASCII content and a Ruby-parseable double-quoted string for non-ASCII codepoints.

Return type:

str

meta_package_manager.brewfile.format_entry(entry_type, name, options=None)[source]ΒΆ

Render a single Brewfile DSL line.

Supports the two shapes Homebrew::Bundle::Extensions::Extension.dump_entry emits:

  • bare: brew "git"

  • with options: mas "Xcode", id: 497799835 or flatpak "org.mozilla.firefox", with: ["flathub"]

Return type:

str

meta_package_manager.brewfile.format_header(coverage, skipped, platform)[source]ΒΆ

Render the comment block at the top of a Brewfile dump.

Return type:

str

meta_package_manager.brewfile.tap_from_package_id(package_id)[source]ΒΆ

Return user/tap if package_id is tap-qualified, else None.

Default taps in DEFAULT_TAPS are filtered out: those are always enabled by brew and emitting tap lines for them would be noise.

Return type:

str | None

meta_package_manager.brewfile.build_brewfile(managers, *, packages_by_manager=None, include_header=True, skipped_counts=None, platform='')[source]ΒΆ

Render a Brewfile from the given managers’ installed packages.

Only managers whose brewfile_entry_type is set contribute output; the caller is expected to have filtered the iterable accordingly, but managers without a configured entry type are silently skipped as a defensive measure.

packages_by_manager (keyed by manager id) supplies each manager’s installed packages so the caller can fetch them concurrently up front. When omitted, each manager’s installed is queried inline instead (the path the unit tests exercise).

skipped_counts is a per-manager-id tally of packages excluded because their manager has no Brewfile mapping; it is rendered in the header for visibility.

Return type:

str

meta_package_manager.capabilities moduleΒΆ

Declaration and inspection of the operations each package manager supports.

A concrete manager advertises what it can do by implementing operation methods and annotating them with the helpers defined here:

Together they expose a uniform capability surface that meta_package_manager.capabilities.implements() introspects and the CLI uses to route each command only to the managers that support it. The meta_package_manager.capabilities.Operations enum is the vocabulary of those routable actions.

class meta_package_manager.capabilities.Operations(*values)[source]ΒΆ

Bases: Enum

Recognized operation IDs that are implemented by package manager with their specific CLI invocation.

Each operation has its own CLI subcommand.

installed = 'installed'ΒΆ
outdated = 'outdated'ΒΆ
orphans = 'orphans'ΒΆ
search = 'search'ΒΆ
install = 'install'ΒΆ
upgrade = 'upgrade'ΒΆ
upgrade_all = 'upgrade_all'ΒΆ
remove = 'remove'ΒΆ
sync = 'sync'ΒΆ
cleanup = 'cleanup'ΒΆ
doctor = 'doctor'ΒΆ
meta_package_manager.capabilities.implements(manager, op)[source]ΒΆ

Inspect a manager’s implementation to check for proper support of an operation.

Accepts either a manager instance or its class; support is determined from the class hierarchy. The verdict is narrated as a single answered DEBUG line (brew implements installed.), keyed on the manager ID rather than the raw class repr.

Return type:

bool

meta_package_manager.capabilities.upgrade_all_is_synthesized(manager)[source]ΒΆ

Whether mpm backfills the manager’s upgrade --all.

True when the manager supports the operation only through the one-by-one fallback of meta_package_manager.manager.PackageManager.upgrade(): it implements outdated and upgrade_one_cli but no class in its hierarchy provides a native upgrade_all_cli. False when a native one-shot command exists, or when the operation is not supported at all.

Feeds the per-manager table of docs/augmentations.md, rendered live by meta_package_manager._docs.

Return type:

bool

meta_package_manager.capabilities.implements_method(manager, method_name)[source]ΒΆ

Whether a non-base class in the manager’s MRO defines method_name.

The orphan refinements remove_orphan and cleanup_orphan are optional variants of the remove and cleanup commands rather than standalone Operations, so implements() cannot route them. This reports whether a manager overrides the base’s stub for one, delegating the MRO walk to meta_package_manager.manager.PackageManager._defines() (shared with the base cleanup composer), so it works for config-defined managers (whose methods live on the synthesized subclass) too.

Return type:

bool

meta_package_manager.capabilities.cleanup_orphan_is_synthesized(manager)[source]ΒΆ

Whether mpm backfills the manager’s system-wide orphan sweep.

True when no class in the manager’s hierarchy overrides cleanup_orphan with a native sweep, but the manager implements both the orphans query and remove: the base meta_package_manager.manager.PackageManager.cleanup_orphan() then synthesizes the sweep by listing the orphans and removing them one by one, the exact pattern of the synthesized full upgrade --all. False when a native sweep exists, or when the manager lacks the building blocks.

Feeds the per-manager table of docs/augmentations.md, rendered live by meta_package_manager._docs.

Return type:

bool

meta_package_manager.capabilities.supports_cleanup_cache(manager)[source]ΒΆ

Whether mpm cleanup --cache can drive the manager.

Return type:

bool

meta_package_manager.capabilities.supports_cleanup_repair(manager)[source]ΒΆ

Whether mpm cleanup --repair can drive the manager.

Return type:

bool

meta_package_manager.capabilities.exact_search_is_synthesized(manager)[source]ΒΆ

Whether mpm backfills the manager’s search --exact refinement.

True when the manager’s native search cannot filter exact matches, so meta_package_manager.manager.PackageManager.refiltered_search() does the narrowing itself. Feeds the per-manager table of docs/augmentations.md and the per-manager operation tables, rendered live by meta_package_manager._docs.

Return type:

bool

meta_package_manager.capabilities.extended_search_is_synthesized(manager)[source]ΒΆ

Whether mpm backfills the manager’s search --extended refinement.

True when the manager’s native search cannot reach descriptions, so meta_package_manager.manager.PackageManager.refiltered_search() does the filtering itself. Feeds the per-manager table of docs/augmentations.md and the per-manager operation tables, rendered live by meta_package_manager._docs.

Return type:

bool

meta_package_manager.capabilities.search_capabilities(extended_support=True, exact_support=True)[source]ΒΆ

Decorator factory to be used on search() operations to signal mpm framework manager’s capabilities.

The flags are exposed as extended_support and exact_support attributes on the wrapped method, so the documentation can derive which managers rely on meta_package_manager.manager.PackageManager.refiltered_search() to honor the --exact and --extended flags. An undecorated search carries no attribute and is read as natively supporting both refinements.

meta_package_manager.capabilities.version_not_implemented(func)[source]ΒΆ

Decorator to be used on install() or upgrade_one_cli() operations to signal that a particular operation does not implement (yet) the version specifier parameter.

Return type:

Callable[[ParamSpec(P, bound= None)], TypeVar(T)]

class meta_package_manager.capabilities.DelegatedMethod(method, cli_name)[source]ΒΆ

Bases: object

Descriptor that delegates a method call to another manager’s CLI.

When accessed on an instance, returns a wrapper that sets _delegate_cli_path on the instance so that build_cli uses the target manager’s binary instead of the host manager’s own CLI.

class meta_package_manager.capabilities.Delegate(source_class)[source]ΒΆ

Bases: object

Factory that creates DelegatedMethod descriptors for delegating operations to another package manager’s CLI.

Typical usage in a manager class body:

from .scoop import Scoop

_scoop = Delegate(Scoop)
install = _scoop.install
remove = _scoop.remove

meta_package_manager.cli moduleΒΆ

The mpm command-line interface: the group and its shared plumbing.

Defines the Click command group (global options, manager selection, the GlobalOptions state every subcommand reads) and the helpers several subcommand modules share: the inventory snapshot, the per-package action engine, the failure gates and the file-output guards.

The subcommands themselves live in one module per help section β€” meta_package_manager.cli_explore (the read-only queries), meta_package_manager.cli_maintenance (the state changers and diagnostics), meta_package_manager.cli_snapshots (manifest export and replay) and meta_package_manager.cli_sbom β€” imported at the bottom of this module so their @mpm.command registrations run. Each subcommand selects the managers from meta_package_manager.pool that implement the matching meta_package_manager.capabilities.Operations action, runs it across all of them, and renders the aggregated, multi-manager result.

meta_package_manager.cli.XKCD_MANAGER_ORDER = ('pip', 'brew', 'npm', 'dnf', 'apt', 'steamcmd')ΒΆ

Sequence of package managers as defined by XKCD #1654: Universal Install Script.

See the corresponding implementation rationale in issue #10.

class meta_package_manager.cli.GlobalOptions(all_managers, user_selection, user_drops, selected_managers, description, summary, network, timeout)[source]ΒΆ

Bases: object

Global options and selection state every subcommand reads from ctx.obj.

Built once by the mpm group body, after the eager option callbacks have accumulated the manager selectors into the transient ctx.obj dict this instance replaces (see update_manager_selection()).

all_managers: boolΒΆ

Include unsupported and unmaintained managers in the selection.

user_selection: list[str] | NoneΒΆ

Managers explicitly selected by the user, in priority order, or None.

user_drops: set[str] | NoneΒΆ

Managers explicitly excluded by the user, or None.

selected_managers: Callable[[...], Iterator[PackageManager]]ΒΆ

Resolve the target managers, applying selection and manager-level options.

description: boolΒΆ

Show package description in results.

summary: boolΒΆ

Print the end-of-run summary on stderr.

network: boolΒΆ

Allow network calls during the run.

timeout: int | NoneΒΆ

User-set maximum duration in seconds for each CLI call, or None.

meta_package_manager.cli.COOLDOWN_SUPPORTED_MANAGERS = ('npm', 'pip', 'pipx', 'pnpm', 'uv', 'uvx', 'yay')ΒΆ

IDs of the managers that natively enforce a release-age mpm --cooldown.

Derived from the pool so the --cooldown help text never drifts from the set of managers that actually carry a cooldown_env_var: adding cooldown support to a manager surfaces it here automatically.

meta_package_manager.cli.guard_existing_output(ctx, output_path, *, overwrite)[source]ΒΆ

Block clobbering an existing output file unless overwrite is set.

Warns and exits with code 2 when output_path already exists and the user did not pass --overwrite/--force/--replace. No-op when the file is absent. Callers handle the stdout case separately.

Return type:

None

meta_package_manager.cli.update_manager_selection(ctx, param, value)[source]ΒΆ

Update global selection list of managers in the context.

Accumulate and merge all manager selectors to form the initial population enforced by the user.

Return type:

None

meta_package_manager.cli.single_manager_selectors()[source]ΒΆ

Dynamiccaly creates a dedicated flag selector alias for each manager.

meta_package_manager.cli.bar_plugin_path(ctx, param, value)[source]ΒΆ

Print the location of the Xbar/SwiftBar plugin.

Returns the normalized path of the standalone bar_plugin.py script that is distributed with this Python module. This is made available under the mpm --bar-plugin-path option.

Notice that the fully-qualified home directory get replaced by its shorthand (~) if applicable:

  • the full /home/user/.python/site-packages/mpm/bar_plugin.py path is simplified to ~/.python/site-packages/mpm/bar_plugin.py,

  • but /usr/bin/python3.10/mpm/bar_plugin.py is returned as-is.

meta_package_manager.cli.query_option(f)ΒΆ

--query filter of the inventory exporters (dump, sbom).

meta_package_manager.cli.query_exact_option(f)ΒΆ

--exact refinement of query_option.

meta_package_manager.cli.overwrite_option(f)ΒΆ

Opt-in clobbering of an existing output file (dump, sbom); see guard_existing_output().

meta_package_manager.cli.package_label(spec)[source]ΒΆ

Render a spec as package_id or package_id@version for trail output.

Return type:

str

meta_package_manager.cli.fail_unless_zero_exit(ctx, message)[source]ΒΆ

Print the durable critical: :message:` record, then exit `1 unless -0/--zero-exit opted out of the gate.

The shared failure gate of the action commands (exit_on_failures()) and doctor: the summary always prints, following the linter convention where findings gate automation, and -0 keeps the exit code at 0 with the printed summary staying the durable record. Usage and configuration errors are unaffected: they exit 2 regardless, as genuine execution failures.

Return type:

None

meta_package_manager.cli.exit_on_failures(ctx, verb, failures)[source]ΒΆ

Report the per-package failures collected this run and exit non-zero.

A no-op when failures is empty. Otherwise routes the deduplicated, sorted Could not {verb}: ... summary through fail_unless_zero_exit(). Shared by every action command (install, remove, upgrade <packages>, restore).

Return type:

None

meta_package_manager.cli_explore moduleΒΆ

The explore subcommands: the read-only queries and inspection tools.

managers, installed, outdated, orphans, search, which and config-template, plus the query plumbing they share: the concurrent collect prelude, the row builders and the query-match highlighter. Every command here only reads system state and renders a table (or its serialized counterpart).

The mpm group itself, and the plumbing shared with the other subcommand modules, live in meta_package_manager.cli.

meta_package_manager.cli_explore.exact_match_option(f)ΒΆ

--exact refinement of the optional positional QUERY of installed and outdated.

meta_package_manager.cli_maintenance moduleΒΆ

The maintenance subcommands: the state changers and diagnostics.

install, upgrade, remove, sync, cleanup and doctor, plus the machinery only they need: the cooldown gate, the sourced-operation dispatch that resolves each package spec to its source managers, and the cleanup category selection.

The mpm group itself, and the per-package action engine restore also drives, live in meta_package_manager.cli.

meta_package_manager.cli_maintenance.cooldown_permits(manager)[source]ΒΆ

Decide whether a release-introducing operation may run on manager.

Returns True when no cooldown is active, when the manager can enforce it natively, or when the user opted out of the requirement with --allow-unsupported-managers or its require_cooldown_support configuration key. Returns False (after logging the skip) when an active cooldown cannot be enforced and the requirement still holds, so the caller leaves the manager alone rather than letting a freshly-published version slip in.

The skip message names both remedies, the one-shot flag and the persistent configuration key: opting out of a supply-chain safeguard is a standing policy decision, not something to re-type on every run.

Return type:

bool

meta_package_manager.cli_maintenance.CLEANUP_CATEGORIES = ('orphans', 'cache', 'repair')ΒΆ

Cumulative categories the cleanup subcommand decomposes into.

Each category has a two-sided --<category>/--skip-<category> flag pair. Positive flags narrow the run to exactly the listed categories; skip flags subtract categories from the default selection.

meta_package_manager.cli_maintenance.DEFAULT_CLEANUP_CATEGORIES = frozenset({'cache', 'repair'})ΒΆ

Categories a plain cleanup (no category flag) runs.

The orphan sweep is deliberately absent: it removes packages, where cache pruning and state repair only reclaim disk and fix metadata. Keeping it strictly behind an explicit --orphans makes the default non-destructive and identical on every manager, native sweep or not, mirroring how remove keeps its cascade behind the same flag.

meta_package_manager.cli_sbom moduleΒΆ

The SBOM subcommand: export the package inventory as a standard document.

sbom renders the installed inventory as a SPDX or CycloneDX file, with optional metadata enrichment and an opt-in OSV vulnerability scan.

The mpm group itself, and the plumbing shared with the other subcommand modules, live in meta_package_manager.cli.

meta_package_manager.cli_snapshots moduleΒΆ

The package snapshots subcommands: manifest export and replay.

dump (TOML manifest or Brewfile) and restore (install back the packages a TOML manifest references, through the shared per-package action engine).

The mpm group itself, and the plumbing shared with the other subcommand modules, live in meta_package_manager.cli.

meta_package_manager.config moduleΒΆ

Configuration utilities for mpm.

Hosts the schema of the [mpm] configuration section consumed by click_extra and the runtime policy around the [mpm.managers.<id>] sections of the same configuration file: applying attribute overrides to shipped managers, gating manager definitions on the trust of their source, and registering them into the pool.

The concerns stay separate across three modules: meta_package_manager.pool.ManagerPool owns the live manager instances and the per-manager overridden_fields tracking dict; meta_package_manager.definitions owns the declarative schema (which fields a section may set, how to coerce values) and the class factory; this module owns the loading policy and mutates the pool through the apply_manager_overrides() and register_config_managers() helpers.

class meta_package_manager.config.MpmConfig(all_managers=False, ignore_auto_updates=True, stop_on_error=False, dry_run=False, plan=False, sudo=None, timeout=None, jobs='auto', cooldown='', require_cooldown_support=True, description=False, sort_by=<factory>, summary=True, network=False, suggest_contribs=True, managers=<factory>)[source]ΒΆ

Bases: object

Schema for mpm configuration files.

Defines the recognized options for the [mpm] (or [tool.mpm]) configuration section. Each field corresponds to a CLI option on the root mpm group.

Note

Dynamic manager selectors (brew = true, pip = false, etc.), click-extra built-in options (verbosity, table_format) and one-shot utility flags (--bar-plugin-path, --xkcd) are handled by the default_map pipeline and do not appear here.

Note

Multi-word fields pin their config path through CONFIG_PATH_METADATA_KEY: mpm’s configuration convention is underscored keys (matching Click parameter names, the --validate-config checks and every documented example), while click-extra would otherwise kebab-case field names in the rendered click:config reference.

all_managers: bool = FalseΒΆ

Force evaluation of all managers, including unsupported and unmaintained.

ignore_auto_updates: bool = TrueΒΆ

Exclude auto-updating packages from outdated/upgrade results.

stop_on_error: bool = FalseΒΆ

Stop on first manager CLI error instead of continuing.

dry_run: bool = FalseΒΆ

Simulate CLI calls without performing any action.

plan: bool = FalseΒΆ

Capture the state-changing CLI calls for inspection instead of running them.

sudo: bool | None = NoneΒΆ

Force privileged manager operations with (True) or without (False) sudo. Unset by default: system managers escalate, user-level managers do not. Overridden per manager by a sudo entry in [mpm.managers.<id>].

timeout: int | None = NoneΒΆ

Maximum duration in seconds for each manager CLI call. When unset, a per-operation default applies: 120 for read-only queries (installed, outdated, search) and 500 for state-changing operations. A set value overrides every operation.

jobs: int | str = 'auto'ΒΆ

Maximum number of managers to run concurrently. Accepts an integer, or the keywords auto (one fewer than the logical CPU count, the default) and max (every logical CPU); set 1 to run sequentially.

cooldown: str = ''ΒΆ

Minimum release age (like 7 days or 1 week) a package version must reach before it can be installed or upgraded. Empty disables the gate.

require_cooldown_support: bool = TrueΒΆ

Require managers to natively support a requested cooldown to run install/upgrade: skip those that cannot (fail-closed). Set to False to run them anyway, without the safeguard.

description: bool = FalseΒΆ

Show package description in results.

sort_by: list[str]ΒΆ

Default fields to sort results by, in priority order.

summary: bool = TrueΒΆ

Print an end-of-run summary on stderr: a count line of per-manager totals plus any subcommand-specific follow-up notes.

network: bool = FalseΒΆ

Opt into network calls during the run. Today this only affects mpm sbom, which queries OSV.dev for vulnerability data.

suggest_contribs: bool = TrueΒΆ

Print a contribution invitation when a user override targets a field that likely indicates an upstream detection bug.

managers: dict[str, dict]ΒΆ

Per-manager attribute overrides keyed by manager ID.

Typed as dict[str, dict] so click-extra treats the sub-tree as opaque: its keys are manager IDs (data, not flag names) and its leaf entries are validated by validate_manager_overrides_section() registered as a click_extra.ConfigValidator. The field carries no CLI flag β€” it only exists in the schema to declare opacity and to enable --validate-config coverage of the override block.

meta_package_manager.config.INVALIDATED_CACHED_PROPS: Final[tuple[str, ...]] = ('available', 'cli_path', 'executable', 'fresh', 'supported', 'version')ΒΆ

Cached properties on meta_package_manager.manager.PackageManager that may have been computed from attributes covered by OVERRIDABLE_FIELDS.

Any pre-computed values are popped from the manager instance’s __dict__ after an override is applied so the next access recomputes them against the new attribute values. Safe to pop even if nothing was cached.

meta_package_manager.config.CONTRIBUTION_HINT_FIELDS: Final[frozenset[str]] = frozenset({'cli_names', 'cli_search_path', 'requirement', 'version_cli_options', 'version_regexes'})ΒΆ

Subset of OVERRIDABLE_FIELDS whose override probably reflects a real upstream detection bug rather than a personal preference.

When the user overrides one of these, mpm did not find the binary, used the wrong binary name, rejected a valid version, or failed to parse one. The other overridable fields (timeout, ignore_auto_updates, pre_args, etc.) are user preferences and do not warrant a contribution invitation.

meta_package_manager.config.ISSUE_TRACKER_NEW_URL: Final[str] = 'https://github.com/kdeldycke/meta-package-manager/issues/new'ΒΆ

Base URL of the upstream GitHub issue tracker’s new-issue endpoint.

meta_package_manager.config.MAX_ISSUE_URL_LENGTH: Final[int] = 8192ΒΆ

Practical upper bound on the length of a pre-filled GitHub new-issue URL.

GitHub silently truncates very long URLs, which yields a broken issue form when the user clicks the invitation. Anything past 8 KiB is treated as a bug in the URL builder rather than a configuration we should tolerate.

class meta_package_manager.config.ContributionHint(manager_id, field, user_value, detected_cli_path)[source]ΒΆ

Bases: object

A user override of a detection-related field, candidate for upstream contribution.

Captured at override time by apply_manager_overrides() so the user can later be invited to file an upstream issue with a pre-filled bug-report URL.

manager_id: strΒΆ

ID of the manager whose attribute was overridden.

field: strΒΆ

Name of the overridden PackageManager attribute.

user_value: AnyΒΆ

Value the user supplied in their config file, after type coercion.

detected_cli_path: str | NoneΒΆ

The CLI path mpm resolved with the built-in defaults, before the override took effect. None when mpm could not find the binary, which is itself a strong signal that the upstream search heuristics need help.

meta_package_manager.config.format_contribution_hints(hints)[source]ΒΆ

Render a multi-line, human-readable batch message inviting the user to contribute their overrides back upstream.

Returns an empty string for an empty list so the caller can branch on truthiness without a length check.

Return type:

str

meta_package_manager.config.validate_manager_overrides_section(section, *, pool)[source]ΒΆ

Strict validator for the [mpm.managers.<id>] configuration sub-tree.

Pure function: inspects section against the pool’s registered managers and OVERRIDABLE_FIELDS, raises the first click_extra.ValidationError it encounters, never mutates the pool. Suitable for registration as a click_extra.ConfigValidator and for direct invocation by apply_manager_overrides() so both the --validate-config path and the runtime application path enforce the same rules.

A section keyed by a built-in manager ID is validated as an override (its fields must be a subset of OVERRIDABLE_FIELDS). A section keyed by any other ID is validated as a brand-new manager definition via parse_manager_definition().

Raises:

click_extra.ValidationError – when section is not a mapping, an override sets an unknown field or a wrong-typed value, or a definition is malformed. The path of the raised error is relative to the [mpm.managers] section root (e.g. "winget.cli_searchpath"); click-extra prepends the app prefix when surfacing the error.

Return type:

None

meta_package_manager.config.apply_manager_overrides(pool, overrides)[source]ΒΆ

Apply per-manager attribute overrides parsed from the user’s config file.

Expects overrides to be a mapping of manager ID to a mapping of attribute name to its new value, as returned by conf["mpm"]["managers"]. None and empty mappings are accepted as no-op shortcuts so callers can unconditionally forward whatever was parsed from the config file.

Validation is delegated to validate_manager_overrides_section(), which raises click_extra.ValidationError on the first issue. Both the runtime config-loading path and the explicit --validate-config path enforce the same rules through that single validator, so a config that survives one survives the other.

After validation succeeds, every override is applied as an instance attribute (shadowing the class default for the lifetime of the process), recorded in ManagerPool.overridden_fields so ManagerPool._select_managers() skips the matching global --<flag> defaults for that manager, and the cached properties derived from the affected attributes are evicted so the next access recomputes them. List-valued fields use replace semantics: the override fully supersedes the built-in default.

Returns a list of ContributionHint entries, one per accepted override that targets a CONTRIBUTION_HINT_FIELDS field. Each hint captures the pre-override cli_path so the contribution invitation can show what mpm would have detected without the user’s intervention.

Return type:

list[ContributionHint]

meta_package_manager.config.build_manager_overrides_validator(pool)[source]ΒΆ

Construct a click_extra.ConfigValidator for the [mpm.managers] sub-tree, bound to a specific ManagerPool.

Used by the CLI bootstrap (@group decorator) to register a validator against the live pool. Wrapping validate_manager_overrides_section() in a closure satisfies the click_extra.ConfigValidator.validator signature (Callable[[dict], None]) while keeping the underlying validator pool-agnostic and testable in isolation.

Return type:

ConfigValidator

meta_package_manager.config.dump_manager_overrides(manager)[source]ΒΆ

Return the current overridable attributes of manager as a TOML-ready dict.

Walks OVERRIDABLE_FIELDS in alphabetical order, reads each attribute from the manager instance, and converts tuples to lists so tomli_w can serialize the result without translation. Attributes whose value is None are skipped: TOML cannot express None and the user cannot override a field to None either, so emitting the key would be misleading.

Every other overridable field is emitted, including ones still at the class default. The output is meant to be a canonical override template: paste, prune the rows that don’t apply, and customize the rest.

Return type:

dict[str, Any]

meta_package_manager.config.CTX_HINTS_KEY: Final[str] = 'mpm.contribution_hints'ΒΆ

ctx.meta key under which collected ContributionHint entries are accumulated between apply_manager_overrides_from_context() and print_contribution_hints().

meta_package_manager.config.apply_manager_overrides_from_context(ctx, pool)[source]ΒΆ

Read the [mpm.managers.<id>] sections from the loaded config and apply them to pool.

Reads the full parsed config that click_extra exposes under CONF_FULL after configuration discovery and forwards the ["mpm"]["managers"] subtree to apply_manager_overrides(). Returns silently when no configuration file was loaded or when the section is absent.

Any ContributionHint returned by apply_manager_overrides() is stashed under CTX_HINTS_KEY for print_contribution_hints() to surface at the end of the run.

Return type:

None

meta_package_manager.config.print_contribution_hints(ctx)[source]ΒΆ

Print the collected contribution hints to <stderr>.

Reads from CTX_HINTS_KEY and writes via click_extra.echo() rather than the logging module, so the message survives --verbosity CRITICAL``and the``logging.disable() block that suppresses log output for serialization formats. Caller is expected to gate this on the user’s suggest_contribs preference.

Return type:

None

meta_package_manager.config.RISKY_OVERRIDE_FIELDS: Final[frozenset[str]] = frozenset({'cli_names', 'cli_search_path', 'pre_cmds', 'sudo'})ΒΆ

Override fields that can redirect mpm to run an arbitrary binary (or sudo).

When such an override is read from an untrusted config source, apply_manager_overrides_from_context() logs a warning. See docs/security.md.

meta_package_manager.config.config_file_is_trusted(path)[source]ΒΆ

Whether a config file is safe to load executable manager definitions from.

Trusted on POSIX when both the file and its parent directory are owned by the current user or root and are not group- or world-writable, mirroring how ssh, git and sudo reason about config-file trust: a writable file (or a writable directory that lets an attacker swap the file) could inject arbitrary commands.

On platforms without os.getuid (Windows), the POSIX ownership model does not apply and the check is skipped (returns True); see docs/security.md for the rationale and the residual risk.

Return type:

bool

meta_package_manager.config.register_config_managers(pool, definitions, *, source=None, source_is_url=False)[source]ΒΆ

Build and register config-defined managers into pool, applying the trust gate.

A definition is skipped (with a warning) when its ID collides with a built-in, when it comes from a remote URL config, or when its local config file fails config_file_is_trusted(). Returns the IDs actually registered. Definitions whose ID is already in the pool (e.g. registered by the eager pre-load) are silently skipped so the eager and callback passes are idempotent.

Return type:

list[str]

meta_package_manager.config.register_config_managers_from_context(ctx, pool)[source]ΒΆ

Register config-defined managers from the loaded config (authoritative pass).

Reads the parsed config under CONF_FULL, parses the non-built-in [mpm.managers.<id>] sections, and registers them through register_config_managers(). This is the source of truth for availability: a manager defined in a config the eager pre-load could not reach (a URL, a custom path) still works from here, it just does not get a dedicated CLI flag.

Return type:

None

meta_package_manager.config.discover_config_definitions(pool)[source]ΒΆ

Eagerly read new-manager definitions before the CLI group is built.

Best-effort and local-only: any error (no config, parse failure, missing reader) yields no definitions so CLI startup never breaks. URL configs are deferred to the authoritative register_config_managers_from_context() pass. Supports both the standalone [mpm.managers] layout and [tool.mpm.managers] in pyproject.toml.

Return type:

tuple[dict[str, ManagerDefinition], Path | None]

meta_package_manager.config.register_eager_config_managers(pool)[source]ΒΆ

Register config-defined managers before the CLI group is constructed.

Called from __main__.main() ahead of importing the Click group, so the dynamic --<id> / --no-<id> selectors enumerate the augmented pool and config-defined managers become first-class flags alongside the built-ins.

Return type:

None

meta_package_manager.definitions moduleΒΆ

Declarative package managers: the TOML schema and its class factory.

A [mpm.managers.<id>] configuration section describes a manager as data. This module owns everything that turns such a description into a live PackageManager subclass:

  • the schema vocabulary: which manager attributes a section may set, both on a shipped manager (OVERRIDABLE_FIELDS) and on a brand-new definition (DEFINITION_CLI_FIELDS, the operations DSL constants);

  • the validation and parsing layer (parse_manager_definition()), shared by --validate-config and the runtime registration path so a config that survives one survives the other;

  • the class factory (build_manager_class()), which synthesizes a ConfigDrivenManager subclass implementing exactly the operations the definition declares;

  • the bundled-definition loader (load_bundled_definitions(), build_bundled_managers()): mpm ships some managers as *.toml package data under meta_package_manager/managers/, each a single [mpm.managers.<id>] section in the exact schema a user would write.

The runtime policy around definitions stays in meta_package_manager.config: where sections may be loaded from, the trust gate on local files, the override-application pass and the registration passes wired into the CLI. The split keeps this module dependent on meta_package_manager.manager only, so the configuration layer can build on it without a circular import.

meta_package_manager.definitions.OVERRIDABLE_FIELDS: Final[Mapping[str, Callable[[Any], Any]]] = {'cli_names': <function _to_str_tuple>, 'cli_search_path': <function _to_str_tuple>, 'dry_run': <function _to_bool>, 'extra_env': <function _to_str_dict>, 'ignore_auto_updates': <function _to_bool>, 'plan': <function _to_bool>, 'post_args': <function _to_str_tuple>, 'pre_args': <function _to_str_tuple>, 'pre_cmds': <function _to_str_tuple>, 'requirement': <function _to_str>, 'stop_on_error': <function _to_bool>, 'sudo': <function _to_bool>, 'timeout': <function _to_int>, 'unmaintained': <function _to_bool>, 'version_cli_options': <function _to_str_tuple>, 'version_regexes': <function _to_str_tuple>}ΒΆ

Per-manager attributes a user is allowed to override from the [mpm.managers.<id>] configuration section.

Each entry maps a meta_package_manager.manager.PackageManager attribute name to a converter that validates the raw TOML value and returns the value as the attribute’s expected runtime type. Lists are coerced into tuples to match the attributes’ tuple types.

Note

id, name, platforms, homepage_url and virtual are intentionally excluded: they are identity, lookup or platform-classification attributes that the pool’s registration relies on. Phase 1 of TOML-driven configuration only exposes attributes whose runtime override is safe.

meta_package_manager.definitions.VALID_PLATFORM_TOKENS: Final[frozenset[str]] = frozenset({'aix', 'all_agents', 'all_architectures', 'all_arm', 'all_ci', 'all_mips', 'all_platforms', 'all_shells', 'all_sparc', 'all_terminals', 'all_traits', 'all_windows', 'almalinux', 'alpine', 'altlinux', 'amzn', 'android', 'arch', 'arch_32_bit', 'arch_64_bit', 'big_endian', 'bourne_shells', 'bsd', 'bsd_without_macos', 'buildroot', 'c_shells', 'cachyos', 'centos', 'chromeos', 'clearlinux', 'cloudlinux', 'cygwin', 'debian', 'dragonfly_bsd', 'endeavouros', 'exherbo', 'fedora', 'freebsd', 'generic_linux', 'gentoo', 'gpu_terminals', 'guix', 'haiku', 'hurd', 'ibm_mainframe', 'ibm_powerkvm', 'illumos', 'kali', 'kvmibm', 'linux', 'linux_layers', 'linux_like', 'linuxmint', 'little_endian', 'loongarch', 'macos', 'mageia', 'mandriva', 'manjaro', 'midnightbsd', 'multiplexers', 'native_terminals', 'netbsd', 'nixos', 'nobara', 'openbsd', 'opensuse', 'openwrt', 'oracle', 'os400', 'other_posix', 'other_shells', 'parallels', 'pidora', 'pikaos', 'powerpc', 'raspbian', 'rhel', 'riscv', 'rocky', 'scientific', 'slackware', 'sles', 'slitaz', 'solaris', 'sourcemage', 'sunos', 'system_v', 'tuxedo', 'ubuntu', 'ultramarine', 'unix', 'unix_layers', 'unix_without_macos', 'void', 'web_terminals', 'webassembly', 'windows', 'windows_shells', 'wsl1', 'wsl2', 'x86', 'xenserver'})ΒΆ

Platform and group IDs accepted in a definition’s platforms list.

Union of every extra_platforms.Platform ID and every group ID, so both a specific platform (ubuntu) and a group (linux, all_platforms) resolve.

meta_package_manager.definitions.DEFINITION_CLI_FIELDS: Final[Mapping[str, Callable[[Any], Any]]] = {'brewfile_entry_type': <function _to_str>, 'brewfile_skip_warning': <function _to_str>, 'cli_names': <function _to_str_tuple>, 'cli_search_path': <function _to_str_tuple>, 'default_sudo': <function _to_bool>, 'extra_env': <function _to_str_dict>, 'internal_sudo': <function _to_bool>, 'maintenance_note': <function _to_str>, 'post_args': <function _to_str_tuple>, 'pre_args': <function _to_str_tuple>, 'pre_cmds': <function _to_str_tuple>, 'requirement': <function _to_str>, 'timeout': <function _to_int>, 'unmaintained': <function _to_bool>, 'unmaintained_message': <function _to_str>, 'version_cli': <function _to_str>, 'version_cli_options': <function _to_str_tuple>, 'version_regexes': <function _to_str_tuple>}ΒΆ

CLI-execution attributes a definition may set, mostly reusing the override converters.

The runtime-preference fields (dry_run, ignore_auto_updates, plan, stop_on_error) are excluded: they are command-line/global concerns, not part of a manager’s identity, and resolve through the usual option precedence. unmaintained is reused from the override converters so a TOML-defined manager can flag its own upstream as abandoned (see docs/cooldown.md for the affected managers).

Seven fields are definition-only:

  • brewfile_entry_type maps the manager onto a Homebrew Bundle DSL entry so its installed packages join mpm dump --brewfile exports (see brewfile_entry_type).

  • brewfile_skip_warning is the message emitted when the manager’s packages are deliberately left out of such an export (see brewfile_skip_warning).

  • default_sudo is the manager’s built-in escalation policy (see default_sudo). Operations marked sudo = true escalate by default, while the user’s global --no-sudo flag or a sudo override still win.

  • internal_sudo marks a manager whose CLI invokes sudo itself mid-run (see internal_sudo). mpm never wraps its commands in sudo; priming instead reuses a warm credential cache for these internal escalations. See docs/sudo.md.

  • maintenance_note renders a {note} admonition atop the manager’s page for a still-maintained upstream under watch (see maintenance_note).

  • unmaintained_message documents an abandoned upstream, rendering a {warning} admonition and the ⚠️ table markers (see unmaintained_message).

  • version_cli names an alternate binary for the version probe (see version_cli), for suites whose own binaries expose no version flag (OpenBSD’s pkg_add).

meta_package_manager.definitions.DEFINITION_IDENTITY_FIELDS: Final[frozenset[str]] = frozenset({'homepage_url', 'name', 'operations', 'platforms'})ΒΆ

Top-level keys of a definition section that are not CLI-execution fields.

meta_package_manager.definitions.QUERY_OPERATIONS: Final[frozenset[str]] = frozenset({'installed', 'orphans', 'outdated', 'search'})ΒΆ

Operations that parse the command’s stdout into packages.

meta_package_manager.definitions.COMMAND_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup_cache', 'cleanup_orphan', 'cleanup_repair', 'doctor', 'install', 'remove', 'remove_orphan', 'sync', 'upgrade_all', 'upgrade_one'})ΒΆ

Operations that only run a command and produce no inventory to parse.

cleanup itself is deliberately absent: it is not an operation a manager defines anymore, but the fixed composition of the declared cleanup categories (see meta_package_manager.manager.PackageManager.cleanup()). A definition declaring it is rejected with a targeted error.

meta_package_manager.definitions.ALL_DEFINITION_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup_cache', 'cleanup_orphan', 'cleanup_repair', 'doctor', 'install', 'installed', 'orphans', 'outdated', 'remove', 'remove_orphan', 'search', 'sync', 'upgrade_all', 'upgrade_one'})ΒΆ

Every operation name a definition may declare.

meta_package_manager.definitions.RECOGNIZED_PARSE_FIELDS: Final[frozenset[str]] = frozenset({'installed_version', 'latest_version', 'package_id'})ΒΆ

Named regex groups / JSON field keys a query parser may map to a package.

meta_package_manager.definitions.REQUIRED_PARSE_FIELDS: Final[Mapping[str, frozenset[str]]] = {'installed': frozenset({'package_id'}), 'orphans': frozenset({'package_id'}), 'outdated': frozenset({'latest_version', 'package_id'}), 'search': frozenset({'package_id'})}ΒΆ

Parse fields each query operation must extract to be useful.

installed needs only the package ID: some tools genuinely track no per-package version (Clear Linux bundles under swupd, Cygwin listings under apt-cyg), and mpm’s package model treats the installed version as optional everywhere. outdated without a latest_version would report nothing actionable, so there the version capture stays mandatory.

meta_package_manager.definitions.OPERATION_ARG_PLACEHOLDER: Final[Mapping[str, str]] = {'install': 'package_id', 'remove': 'package_id', 'remove_orphan': 'package_id', 'upgrade_one': 'package_id'}ΒΆ

Placeholder each operation’s args must reference, so a value is actually passed to the CLI (a remove with no {package_id} would target nothing).

search is deliberately absent: its {query} placeholder is optional. A tool with no real search command can still declare the operation by listing its whole catalog (opkg list, swupd bundle-list --all) and letting meta_package_manager.manager.PackageManager.refiltered_search() narrow the results, mirroring the search-from-scratch augmentation some built-in managers use.

meta_package_manager.definitions.SEARCH_REFINEMENT_KEYS: Final[frozenset[str]] = frozenset({'exact_args', 'extended_args', 'id_name_only_args'})ΒΆ

Optional per-refinement argument templates of the search operation.

Each key holds the CLI arguments spliced into the args template β€” at the position of the matching {exact_args}-style marker β€” when the refinement is active: exact_args for an --exact search, extended_args for an --extended one, and id_name_only_args for the default ID/name-restricted mode (mpm’s --id-name-only, for tools like Chocolatey whose unrestricted search is the default and take a flag to narrow it). An inactive refinement expands its marker to nothing.

Declaring a key advertises native support for the matching mpm flag (exact_args sets the search method’s exact_support introspection attribute, either of the other two sets extended_support), which feeds the augmentations documentation. meta_package_manager.manager.PackageManager.refiltered_search() still refines the results client-side either way, exactly as for the built-in managers.

meta_package_manager.definitions.ALLOWED_ARG_PLACEHOLDERS: Final[Mapping[str, frozenset[str]]] = {'install': frozenset({'package_id'}), 'remove': frozenset({'package_id'}), 'remove_orphan': frozenset({'package_id'}), 'search': frozenset({'exact_args', 'extended_args', 'id_name_only_args', 'query'}), 'upgrade_one': frozenset({'package_id'})}ΒΆ

Placeholders each operation’s args may reference.

Operations absent from this mapping take no placeholder at all. Any {token} outside the operation’s set is rejected at parse time: a typoed {qeury} would otherwise reach the CLI as a literal argument and fail in silent, tool-specific ways.

meta_package_manager.definitions.ARG_PLACEHOLDER_REGEX: Final = re.compile('\\{([a-z_]+)\\}')ΒΆ

Match {placeholder} tokens in an operation’s args, for validation.

meta_package_manager.definitions.QUERY_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'fields', 'format', 'list_path', 'regex', 'sudo'})ΒΆ

Keys allowed in a query operation’s table.

cli is the same alternate-binary hook as on command operations. sudo = true marks the query as privileged, for the rare tool that gates even its read-only listings behind root (deb-get’s upgradable check); escalation then follows the usual per-manager policy.

meta_package_manager.definitions.SEARCH_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'exact_args', 'extended_args', 'fields', 'format', 'id_name_only_args', 'list_path', 'regex', 'sudo'})ΒΆ

Keys allowed in the search operation’s table: a query operation plus the per-refinement argument templates of SEARCH_REFINEMENT_KEYS.

meta_package_manager.definitions.COMMAND_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'sudo'})ΒΆ

Keys allowed in a command operation’s table.

cli names an alternate binary for this operation, resolved on the search path at call time: it lets one definition span sibling binaries (urpmq querying while urpmi installs). sudo = true marks the operation as privileged, mirroring the sudo=True flag built-in managers pass to run_cli: escalation then follows the per-manager policy (the definition’s default_sudo, overridden by the user’s --sudo/--no-sudo).

class meta_package_manager.definitions.OperationSpec(args, cli=None, sudo=False, exact_args=None, extended_args=None, id_name_only_args=None, parse_mode='none', regex=None, list_path=None, fields=None)[source]ΒΆ

Bases: object

Declarative specification of one operation of a config-defined manager.

args: tuple[str, ...]ΒΆ

CLI arguments appended after the resolved binary, before post_args.

May embed the {package_id} and {query} placeholders, substituted at call time. {version} is intentionally unsupported: config-defined managers do not pin versions (see _make_install()).

cli: str | None = NoneΒΆ

Alternate binary name for this operation, or None for the manager’s main cli_path.

Resolved with which() at call time, so one definition can span sibling binaries (urpmi/urpme/ urpmq, cast/dispel/gaze). The operation fails with FileNotFoundError when the binary is missing rather than silently falling back to the main CLI.

sudo: bool = FalseΒΆ

Mark the operation as privileged, mirroring the sudo=True flag built-in managers pass to run_cli().

Escalation still follows the per-manager policy: the definition’s default_sudo, overridden by the user’s --sudo/--no-sudo. Command operations are the usual bearers; a query may also set it, for the rare tool that gates its read-only listings behind root (deb-get).

exact_args: tuple[str, ...] | None = NoneΒΆ

Arguments spliced at the {exact_args} marker of a search’s args when an exact match is requested, or None when the tool has no native exact mode. See SEARCH_REFINEMENT_KEYS.

extended_args: tuple[str, ...] | None = NoneΒΆ

Arguments spliced at the {extended_args} marker of a search’s args when the extended (description-reaching) mode is requested, or None when the tool has no native switch for it. See SEARCH_REFINEMENT_KEYS.

id_name_only_args: tuple[str, ...] | None = NoneΒΆ

Arguments spliced at the {id_name_only_args} marker of a search’s args when the default ID/name-restricted mode is requested, for tools whose unrestricted search is the default (Chocolatey’s --by-id-only), or None. See SEARCH_REFINEMENT_KEYS.

parse_mode: str = 'none'ΒΆ

How to turn the command’s stdout into packages: "regex" (per-line named groups), "json" (structured extraction), or "none" for command-only operations that produce no inventory (install, remove, sync, …).

regex: str | None = NoneΒΆ

Regular expression matched against each stdout line in "regex" mode.

Recognized named groups: package_id (required), installed_version and latest_version (optional). Compiled with re.MULTILINE.

list_path: str | None = NoneΒΆ

Dotted path to the package array inside the JSON document in "json" mode.

None or empty means the document is itself the array.

fields: dict[str, str] | None = NoneΒΆ

Mapping of recognized package field (package_id, installed_version, latest_version) to its JSON selector, in "json" mode.

A selector is a key name with an optional [N] list index (versions[0]); see JSON_FIELD_SELECTOR_REGEX.

class meta_package_manager.definitions.ManagerDefinition(manager_id, name, platforms, homepage_url, cli_fields, operations)[source]ΒΆ

Bases: object

A brand-new package manager declared from a [mpm.managers.<id>] section.

Produced by parse_manager_definition() after validation, consumed by build_manager_class().

manager_id: strΒΆ

Manager ID, taken from the configuration section name.

name: strΒΆ

Human-readable manager name.

platforms: tuple[str, ...]ΒΆ

Platform and group ID strings, resolved to extra_platforms.Platform members at build time.

homepage_url: str | NoneΒΆ

Project home page, for documentation reference only.

cli_fields: dict[str, object]ΒΆ

Overridable CLI-execution attributes (cli_names, requirement, version_regexes, …), pre-coerced to their runtime types.

operations: dict[str, OperationSpec]ΒΆ

Declared operations keyed by name (installed, install, …).

class meta_package_manager.definitions.ConfigDrivenManager[source]ΒΆ

Bases: PackageManager

Base class for managers synthesized from configuration.

Carries no operation methods on purpose: only the dynamically-created subclass returned by build_manager_class() defines the operations the user actually declared, so meta_package_manager.capabilities.implements() reports an accurate capability set. Defining an operation here would make every config-defined manager falsely advertise it.

Exists mainly as a marker (isinstance(manager, ConfigDrivenManager) distinguishes user-defined managers from built-ins) and as a shared home for any future config-driven behavior.

Initialize cli_errors list.

cli_names: tuple[str, ...] = ('configdrivenmanager',)ΒΆ

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 python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'configdrivenmanager'ΒΆ

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 = 'ConfigDrivenManager'ΒΆ

Return package manager’s common name.

Default value is based on class name.

virtual: bool = FalseΒΆ

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.

definition_source: str | None = NoneΒΆ

Repo-relative path to the bundled TOML file this manager was defined in.

Set by build_bundled_managers() for the managers mpm ships as package data; stays None for a manager defined in a user’s own configuration file. The documentation generator links a bundled manager’s benchmark entry to this file, a config-defined manager having no Python source line to point at.

meta_package_manager.definitions.parse_manager_definition(manager_id, section)[source]ΒΆ

Validate and parse one [mpm.managers.<id>] definition section.

Returns a ManagerDefinition ready for build_manager_class(). Raises click_extra.ValidationError (path relative to the [mpm.managers] root) on any problem, so the same function backs both --validate-config and the runtime registration path.

Return type:

ManagerDefinition

meta_package_manager.definitions.build_manager_class(definition)[source]ΒΆ

Synthesize a PackageManager subclass from a validated definition.

Assembles a class namespace from the definition’s identity and CLI fields, then adds one method (or property) per declared operation. Only the declared operations land in the namespace, so meta_package_manager.capabilities.implements() reflects exactly what the user configured. Single- and all-package upgrades map to upgrade_one_cli() / upgrade_all_cli() so the inherited upgrade() orchestrator drives them, just like the built-in managers.

Return type:

type[ConfigDrivenManager]

meta_package_manager.definitions.BUNDLED_DEFINITIONS_PACKAGE: Final[str] = 'meta_package_manager.managers'ΒΆ

Import package whose *.toml resources hold mpm’s bundled manager definitions.

meta_package_manager.definitions.load_bundled_definitions()[source]ΒΆ

Parse every bundled [mpm.managers.<id>] definition shipped as package data.

Reads each *.toml resource of BUNDLED_DEFINITIONS_PACKAGE via importlib.resources (so it works the same from an unpacked install, a zip or a Nuitka onefile), and validates every section with parse_manager_definition(). Returns (definition, source) pairs, where source is the repo-relative path used to link the manager’s documentation. Cached because the shipped files never change at runtime.

A malformed bundled file is a packaging bug, but it is logged and skipped rather than raised so one bad resource cannot break mpm startup for everyone. The hermetic test_bundled_inventory and test_bundled_registered keep the shipped files valid.

Return type:

tuple[tuple[ManagerDefinition, str], ...]

meta_package_manager.definitions.bundled_manager_ids()[source]ΒΆ

IDs of the managers mpm ships as bundled configuration definitions.

Return type:

frozenset[str]

meta_package_manager.definitions.build_bundled_managers()[source]ΒΆ

Instantiate every bundled definition into a live, pool-ready manager.

Each ConfigDrivenManager subclass records the TOML file it came from in ConfigDrivenManager.definition_source, so the documentation generator can link to it. Called once by meta_package_manager.pool.ManagerPool.register.

Return type:

list[PackageManager]

meta_package_manager.dispatch moduleΒΆ

Cross-manager dispatch: scheduling many package managers at once.

Where meta_package_manager.execution runs one manager’s CLI in one subprocess, this module schedules many managers concurrently: the job-count policy that decides sequential-vs-concurrent (effective_jobs()), the up-front availability probe used during selection (warm_availability()), the two progress-wrapped fan-out primitives the CLI subcommands drive (collect_from_managers(), collect_per_package()) with their shared dispatch() engine, the backend-lock catalog that serializes conflicting managers (SHARED_LOCK_FAMILIES and merge_into_lock_lanes()), and the manager-bound βœ“/βœ— ledger (OperationTrail) that the concurrent and sequential paths both report through.

The generic layers live upstream in click-extra: the concurrency primitives in click_extra.execution (run_jobs/run_lanes driven by mpm --jobs) and the batch-reporting trail in click_extra.spinner (OperationTrail with its trail_glyph/trail_line atoms). This module keeps what is package-manager policy: which managers must never overlap, how the trail binds to the pool’s --progress state, and when a batch collapses to a sequential pass.

meta_package_manager.dispatch.SHARED_LOCK_FAMILIES: Final[tuple[frozenset[str], ...]] = (frozenset({'apt', 'apt-mint', 'deb-get'}), frozenset({'brew', 'cask'}), frozenset({'dnf', 'dnf5', 'yum', 'zypper'}), frozenset({'pacman', 'pacstall'}))ΒΆ

Managers that contend for one shared backend lock, grouped by backend.

Different managers are otherwise independent processes over disjoint state, so running them in parallel is safe. The exception is a handful that drive a shared backend and serialize on its lock:

  • apt, apt-mint and deb-get all reach dpkg (/var/lib/dpkg/lock).

  • brew and cask are the same brew binary and serialize on Homebrew’s own update lock: two concurrent brew update (which mpm sync issues identically for both, as the formula/cask split does not apply to it) collide, one failing with β€œAnother active Homebrew update process is already running”.

  • dnf, dnf5, yum and zypper all reach the RPM database.

  • pacman and pacstall all reach the pacman database (/var/lib/pacman/db.lck).

Concurrency is safe across families and unsafe within one, just as it is unsafe within a single manager (which is why a manager’s own packages stay serial). When two members run at once the shared lock makes them block or fail, never corrupt.

Enforced for the mutating fan-outs only: merge_into_lock_lanes() collapses each family’s members into a single dispatch() lane, so they run serially while distinct families still run in parallel. The read-only queries (installed/outdated/search) take no backend lock, so they keep one lane per manager and stay fully concurrent. Members of a lane also share a command cache (see CLIExecutor.run_cache), so two that resolve to a byte-identical invocation (brew and cask for sync and cleanup) run the subprocess once.

Adding a newly-conflicting set of managers is a one-line edit here: append a frozenset of their ids and both the serialization and the cache pick it up.

meta_package_manager.dispatch.effective_jobs(ctx, count)[source]ΒΆ

Resolve how many worker threads to use for a batch of count items.

Thin wrapper over click_extra.execution.resolve_jobs() pinning mpm’s policy: always collapse to a single (sequential) worker at DEBUG verbosity, where coherent per-manager log narration matters more than the speed-up (interleaved threads would scramble it). The base helper also collapses to sequential with no active CLI context, for a single item, or at mpm --jobs 1; otherwise the mpm --jobs value wins, capped at count (no point spinning up more workers than there are items).

Return type:

int

meta_package_manager.dispatch.warm_availability(managers)[source]ΒΆ

Probe several managers’ available concurrently.

Reading available forces a manager’s --version detection, whose result (and the cli_path / executable / version it depends on) is cached on the instance. Warming the candidate set up front turns the sequential string of probes into a single round bounded by the slowest one, shaving startup latency off any command that touches many managers.

Each manager is a distinct instance with its own cached attributes and subprocess, so the probes are independent and thread-safe; the GIL is released while each waits. The executor barrier publishes every cached value before the caller reads it back.

Sized by effective_jobs(): a no-op (leaving the probes to lazy, sequential evaluation) without an active context, at DEBUG verbosity, for a single candidate, or at mpm --jobs 1.

Return type:

None

class meta_package_manager.dispatch.OperationTrail(managers, *, label='', unit='', total=0, jobs=1, coverage=False)[source]ΒΆ

Bases: OperationTrail

click_extra.spinner.OperationTrail bound to the manager pool.

The upstream class owns the two renderings (sequential echoed lines, or one aggregate indicator with buffered-then-streamed lines) and the interactive gating; this subclass supplies mpm’s policy around it:

  • Enablement follows ``–progress``, folded into each manager’s progress flag by the CLI (a TTY, no serialized output, not at DEBUG verbosity): any enabled manager turns the trail on, auto-gated on an interactive stderr.

  • A concurrent batch mutes the managers’ own per-call spinners (which would collide on stderr) for the duration of the aggregate one.

  • A concurrent batch’s aggregate indicator is a determinate progress bar, not an indeterminate spinner: every dispatch() batch counts its work up front (one task per manager, or per package-manager pair), so the bar always has a length to render against.

  • ``coverage`` keeps the read-command semantics: their result table is the real output and each manager keeps its per-call spinner, so the sequential rendering stays silent (upstream’s echo_sequential=False).

The ordering-bound sequential state changers (install’s priority search) construct it bare; every dispatch() batch drives it as a context manager.

Parameters:
  • managers (Iterable[PackageManager]) – the batch’s managers, read for the --progress gate and (when concurrent) to mute their per-call spinners.

  • label (str) – present-tense verb for the running indicator (β€œSearching”).

  • unit (str) – the noun counted in the indicator tally (β€œmanagers”, β€œpackages”).

  • total (int) – how many outcomes are expected, for the done/total count and the progress bar’s length.

  • jobs (int) – the worker count from effective_jobs(); > 1 selects the concurrent rendering.

  • coverage (bool) – when set, a sequential run stays silent (the caller has another output, its result table). Unused when concurrent.

Configure (but do not start) the trail.

Parameters:
  • label (str) – present-tense verb for the running aggregate indicator ("Fetching"), composed into its {label} {done}/{total} {unit} tally.

  • unit (str) – the noun counted in the tally ("files", "feeds").

  • total (int) – how many outcomes are expected, for the done/total count.

  • jobs (int) – the batch’s worker count; > 1 selects the concurrent rendering (one aggregate spinner), <= 1 the sequential one (plain echoed lines).

  • spinner – a SpinnerPreset from the SPINNERS catalog (spinner=SPINNERS["moon"]) for the concurrent aggregate spinner. Ignored by the sequential and progress-bar renderings, and mutually exclusive with progress_bar.

  • progress_bar – render the aggregate indicator as a determinate click.progressbar() instead of a spinner, for a sequential or concurrent batch alike. Requires a positive total (a bar needs a length) and is mutually exclusive with spinner.

  • timer – append each operation’s and the batch’s elapsed time to the trail lines and the finisher. None (the default) follows the CLI’s --time / --no-time flag; True forces timing on with format_duration()’s compact clock, a callable (seconds: float) -> str forces it on with a custom format, and False forces it off. Per-operation times come from a seconds argument to mark(), filled in automatically by an operation() handle.

  • clock – whether a running aggregate indicator shows elapsed time ("elapsed", the default: a stopwatch counting up, visible from the start) or remaining time ("eta": an estimate from the batch’s rate, appearing only once an outcome lets it be computed). Both the progress bar and the concurrent spinner honor "eta" (the spinner reuses Click’s progress-bar estimate, since the trail knows its total). Per-operation and finisher times are always elapsed.

  • enabled – force the trail on or off. None (the default) auto-detects: the sequential echo renders only on an interactive stream, and the aggregate indicator applies its own TTY gate.

  • echo_sequential – whether a sequential batch echoes its outcome lines and finisher at all. Turn it off when the batch has another output that is the real product (a result table) and the trail would be noise; an aggregate indicator is unaffected.

  • delay – seconds before the aggregate indicator first draws: a fast batch then completes without ever flashing one.

  • stream – where to render; defaults to sys.stderr so the trail never mixes into stdout data.

Raises:

ValueError – if progress_bar is set without a positive total, or together with spinner, or if clock is neither "elapsed" nor "eta".

meta_package_manager.dispatch.dispatch(label, done_label, unit, lanes, *, coverage=False, ctx=None)[source]ΒΆ

Fan a set of work lanes out across managers, narrating a βœ“/βœ— trail.

The single scheduling primitive behind both collect_from_managers() and collect_per_package(). A lane is one or more managers paired with a list of callables; lanes run concurrently (one worker each) while a lane’s own callables run serially, because a package manager cannot safely run two of its own invocations at once, nor can two managers sharing a backend lock (see SHARED_LOCK_FAMILIES). A lane usually wraps a single manager; merge_into_lock_lanes() is what bundles a whole lock family into one, and such a lane also gets a shared command cache (see CLIExecutor.run_cache) so its members collapse identical invocations.

Each callable does its work, records its own outcome (output to INFO, failures into a caller-owned list) and returns (ok, message) for the trail. The whole batch reports through one OperationTrail: a per-outcome βœ“/βœ— line plus a finisher, behind a single aggregate progress bar when concurrent (a slow batch on a terminal) and silent otherwise.

Concurrency is sized by effective_jobs() (driven by mpm --jobs): it collapses to a sequential pass β€” preserving each manager’s own per-call spinner β€” for a single lane, at --jobs 1, or at DEBUG verbosity.

Parameters:
  • coverage (bool) – forwarded to OperationTrail. Read commands set it (their result table is the output, so the sequential pass stays silent and the finisher reports coverage, {done_label} N {unit}, always βœ“). Maintenance and state-changing commands leave it False (the trail is their output, so the finisher reports the success count, {done_label} N/M {unit}, βœ— on any failure).

  • ctx (Context | None) – the active click context, read only to size concurrency (effective_jobs()). Defaults to the current context, so a command need not thread it; tests pass an explicit stand-in.

Return type:

None

meta_package_manager.dispatch.merge_into_lock_lanes(pairs)[source]ΒΆ

Group (manager, task) pairs into dispatch() lanes, one per lock family.

Managers sharing a SHARED_LOCK_FAMILIES entry collapse into a single lane so their tasks run serially (the lane is dispatch()’s unit of mutual exclusion), while unrelated managers each keep their own lane and run concurrently. A manager not in any family keys on its own id, so its tasks still group together (a manager’s own invocations cannot overlap either). First-seen order is preserved, both across lanes and within a lane’s task list.

Used by the mutating fan-outs only: the state changers through collect_per_package(), and sync/cleanup/upgrade --all through collect_from_managers(). The read commands take no backend lock and skip this, keeping one lane per manager.

Return type:

list[tuple[tuple[PackageManager, ...], list[Callable[[], tuple[bool, str]]]]]

meta_package_manager.dispatch.collect_from_managers(label, done_label, managers, work, *, report_state=False, ctx=None)[source]ΒΆ

Run work(manager) for every manager concurrently, results in input order.

The fan-out primitive for the read-only commands (installed/outdated/ search) and the independent maintenance commands (sync/cleanup/ upgrade --all). It adapts each manager into a dispatch() unit that runs work and stashes the (id, data) result in input position, so the returned list mirrors managers regardless of completion order. The maintenance commands (report_state) then merge lock-family members into shared serial lanes (merge_into_lock_lanes()); the read commands keep one lane per manager.

work returns this manager’s (id, data); it must handle its own meta_package_manager.execution.CLIError (each manager owns its subprocess and error list, so the call is thread-safe per manager). A truthy data["errors"] (or data["failed"]) marks that manager’s trail line βœ—; an optional data["label"] overrides its text (upgrade --all uses it for cooldown skips).

Parameters:

report_state (bool) – maintenance commands set it (their only output is the trail). It flips the finisher to a success count, keeps the trail in the sequential fallback, and turns on lock-family serialization. Read commands leave it False: their table is the output, so the sequential fallback is silent and the finisher reports coverage. Passed to dispatch() as the inverse of coverage.

Return type:

list[tuple[str, dict]]

meta_package_manager.dispatch.collect_per_package(label, done_label, tasks, *, ctx=None)[source]ΒΆ

Run per-package operations across managers concurrently, serial within each.

The fan-out primitive for the ordering-free state changers that act on many (package, manager) pairs: remove, upgrade <packages>, restore and the manager-tied specs of install. Takes a flat list of (manager, task) pairs and groups them into lanes by lock family (merge_into_lock_lanes()) β€” so a manager’s own packages, and any lock-family peers, stay serial while unrelated managers run in parallel β€” then drives dispatch(). Each task returns (ok, message) after doing its CLI call and recording its own outcome. The unmatched-package priority search of install is not routed here: it has genuine cross-manager ordering (stop at the first manager that has the package) and stays sequential on its own.

Return type:

None

meta_package_manager.dispatch.warn_jobs_ignored(ctx)[source]ΒΆ

Note that --jobs does not parallelize this run.

Only install with at least one untied package reaches this: those packages need a priority search (install with the first manager that has the package, skip the rest), which is cross-manager-sequential, so the whole command runs serially. The other state changers (remove, upgrade <packages>, restore, and install of fully manager-tied specs) now fan out through collect_per_package(). When the user explicitly raised mpm --jobs above 1, say so once at INFO: the request simply has no effect on this run, which is narration, not a problem.

Return type:

None

meta_package_manager.docstring_corpus moduleΒΆ

Harvest the CLI-session samples documented in manager source docstrings.

Every query method (and the version_regexes attribute) documents a sample invocation and its output in a MyST {code-block} shell-session fence sitting right next to the regex (or JSON parser) that consumes it. This module reads those blocks straight from the source and exposes them, with a shared notion of which ones are literal, replayable fixtures.

It has two consumers:

  • tests.test_docstring_corpus replays each literal block back through the parser it illustrates, asserting the documented example still yields well-formed packages.

  • meta_package_manager._docs renders the literal blocks as the reference traces of a class-based manager’s documentation page, the config-defined twin of the [samples] fixtures shipped alongside TOML-defined managers.

Blocks are harvested from raw source, not the escape-processed __doc__: inspect.cleandoc() expands tabs and the compiler collapses \\, either of which would rewrite a tab-delimited or escaped-JSON fixture into something the parser rejects. Everything here reads static source through ast/inspect, so it is host-independent: safe to call at documentation build time on any machine.

meta_package_manager.docstring_corpus.FENCE_OPENERS = ('```{code-block} shell-session', '```{code-block} pwsh-session')ΒΆ

MyST fence openers introducing a captured CLI session.

PowerShell sessions use > as their prompt, which the dissector already recognizes, so both flavors share one extraction path.

Important

These two openers are the fixture fences: every installed / outdated / orphans / version_regexes block written under one is a complete sample that must parse (the corpus round-trip enforces it) and is rendered as a reference trace. An illustration that is not a literal fixture (a human-readable variant, an interactive prompt, a narrative before/after transcript) uses a non-harvested {code-block} console fence instead, so it stays out of the corpus and the traces while still rendering in the API docs.

meta_package_manager.docstring_corpus.extract_blocks(docstring)[source]ΒΆ

Return the dedented body of every shell-session fence in a docstring.

A fence body runs from the opener to the first closing ` ` ` line, and shares the fence's indentation. The blank line the MyST syntax puts between a ``{code-block} opener and its content is stripped along with the common indentation.

Return type:

list[str]

meta_package_manager.docstring_corpus.dissect(block)[source]ΒΆ

Split a shell-session block into its command tokens and its output.

$ starts a command and > continues it (the shell’s secondary prompt). A command may also continue onto unprefixed lines via a trailing backslash, so those are absorbed too. Every remaining line is output.

Return type:

tuple[list[str], str]

meta_package_manager.docstring_corpus.split_session(block)[source]ΒΆ

Return just the command output of a shell-session block.

Return type:

str

meta_package_manager.docstring_corpus.block_commands(block)[source]ΒΆ

Return each documented command of a block as its own token list.

Unlike dissect(), which pools every command of a block, this keeps commands separate so a block documenting several invocations (an apt cleanup running autoremove then autoclean) yields one list each. Prompt flavor is per-block: $-primary with > continuations for shell sessions, >-primary for PowerShell sessions.

Return type:

list[list[str]]

meta_package_manager.docstring_corpus.block_language(block)[source]ΒΆ

Return the fenced-code language matching a block’s prompt flavor.

A shell session opens on a $ prompt, a PowerShell session on >. The documented reference traces are re-fenced with the flavor they were captured under so their prompts keep highlighting correctly.

Return type:

str

meta_package_manager.docstring_corpus.class_blocks(cls)[source]ΒΆ

Map {member: [blocks]} kept in raw source form for the corpus.

Escapes and tabs survive verbatim so the round-trip feeds each block to the parser exactly as the CLI emits it. Rendered documentation wants the terminal-facing form instead: see class_display_blocks().

Return type:

dict[str, list[str]]

meta_package_manager.docstring_corpus.class_display_blocks(cls)[source]ΒΆ

Map {member: [blocks]} in compiled form for rendered documentation.

The reference-traces generator reads these so a transcript shows single backslashes and resolved escapes, matching what a reader would see running the command, rather than the doubled source escapes class_blocks() preserves for the parser.

Return type:

dict[str, list[str]]

meta_package_manager.docstring_corpus.is_fixture(output)[source]ΒΆ

A block is a fixture when it carries sample output to parse.

A shell-session block showing only a command (no output, an empty system) illustrates an invocation but has nothing for a parser to consume, so it is not a fixture.

Return type:

bool

meta_package_manager.docstring_corpus.literal_blocks(cls, members)[source]ΒΆ

Return (member, index, block) for a class’s replayable fixture blocks.

A block qualifies when it carries sample output (is_fixture()). The index is its position within the member’s full block list. Blocks come in compiled, terminal-facing form (class_display_blocks()): the escape/tab differences from the raw corpus form never touch a directive, so the same blocks are selected either way.

Return type:

list[tuple[str, int, str]]

meta_package_manager.docstring_corpus.version_trace(cls)[source]ΒΆ

Return the raw --version output documented for a class, or None.

The first version_regexes block’s output, mirroring the version [samples] fixture a TOML-defined manager ships.

Return type:

str | None

meta_package_manager.execution moduleΒΆ

CLI-execution engine shared by every package manager.

Runs one manager’s CLI in one subprocess: the meta_package_manager.execution.CLIExecutor mixin (which meta_package_manager.manager.PackageManager inherits) locates the binary and runs it, the meta_package_manager.execution.CLIError exception carries a failed call’s result, and meta_package_manager.execution.highlight_cli_name() themes a binary’s name.

Scheduling many managers at once is the next altitude up, and lives in meta_package_manager.dispatch: the concurrent fan-out primitives, the lock families and the shared βœ“/βœ— trail. The sudo machinery that cuts across both altitudes (credential priming, the keepalive, the hidden-prompt stall watchdog) lives in meta_package_manager.sudo: this module only consumes it, to wrap escalated commands and diagnose their failures.

Note

The name and intent mirror click_extra.execution from the sibling click-extra project, where the generic layers now live: the concurrency primitives (run_jobs/run_lanes driven by mpm --jobs), the single-subprocess engine (click_extra.execution.run_cli(), which disclosed invocations and streams output to the logs), and the Ctrl+C machinery (click_extra.execution.install_interrupt_handler() terminating the in-flight children registered by run_cli). This module keeps what is package-manager policy: per-operation timeouts, sudo escalation, cooldown enforcement and dry-run.

meta_package_manager.execution.DIAGNOSIS_TAIL_LINES: Final = 10ΒΆ

Trailing lines of a failed command’s report relayed at WARNING.

CLIs conclude with their actual error, so the tail is where the diagnosis lives; the cap keeps a verbose failure (a source build’s compiler spew) from flooding the default view. The raw streams are always available in full, live, at DEBUG.

exception meta_package_manager.execution.CLIError(code, output, error)[source]ΒΆ

Bases: Exception

An error occurred when running package manager CLI.

The exception internally keeps the result of CLI execution.

property diagnosis: strΒΆ

The command’s own account of its failure, capped for log relay.

Prefers <stderr>, the conventional stream for error reporting, and falls back on <stdout> for the tools that report failures there (steamcmd); a command that died silently is reduced to its exit code. Only the last DIAGNOSIS_TAIL_LINES lines are kept, behind a counter of the truncated ones: errors conclude streams.

meta_package_manager.execution.VERSION_PROBE: Final = 'version'ΒΆ

Pseudo-operation stamped on CLIExecutor._active_operation during version detection.

Not a member of meta_package_manager.capabilities.Operations (no subcommand routes it), but it participates in the same per-operation machinery: OPERATION_TIMEOUTS binds it to the short read-only cap, and CLIExecutor.run() demotes its command disclosure to DEBUG so the per-candidate probes cannot drown the INFO narration.

meta_package_manager.execution.format_plan_command(cmd_args, extra_env=None)[source]ΒΆ

Render a captured mpm --plan command as a copy-pasteable shell line.

Unlike click_extra.execution.format_cli_prompt() (styled, and prefixed with a $ prompt sigil for logs and dry-runs), this returns a plain, unstyled, shell-quoted line: the forced environment assignments followed by the resolved binary and its arguments, ready to paste into a terminal or pipe into a shell. See the plan-mode branch of CLIExecutor.run().

Return type:

str

meta_package_manager.execution.PLAN_RECORDER: Final = <meta_package_manager.execution._PlanRecorder object>ΒΆ

Process-wide sink for CLIExecutor.run()’s plan-mode captures.

A module-level singleton because run executes in the fan-out’s worker threads, where the click context is not reliably reachable. See _PlanRecorder.

meta_package_manager.execution.highlight_cli_name(path, match_names)[source]ΒΆ

Highlight the binary name in the provided path.

The name is only highlighted when it matches one of the recognized match_names, so an unrecognized binary stays plain. Matching is insensitive to case on Windows and case-sensitive on other platforms, thanks to os.path.normcase.

The rendering is delegated to click_extra.execution.highlight_bin_name(), the same helper behind the $-prompt and spawn-trace log lines, so the mpm managers table and the logs can never drift apart.

Return type:

str | None

meta_package_manager.execution.READ_ONLY_TIMEOUT: Final = 120ΒΆ

Default timeout (seconds) for read-only probes and queries.

These operations only inspect state, so a short cap lets a wedged binary fail fast instead of stalling the whole run. The value is generous enough for legitimately slow scans (a freshly-pulled guix search walking every package’s metadata) while still being far below MUTATING_TIMEOUT.

meta_package_manager.execution.MUTATING_TIMEOUT: Final = 500ΒΆ

Default timeout (seconds) for operations that change system state.

Installs, upgrades, removals, channel syncs and cleanups routinely build from source, download large archives or pull entire channels, so they need a long cap. Kept identical to the historical global default so these operations behave exactly as before when no explicit --timeout is given.

meta_package_manager.execution.DEFAULT_TIMEOUT: Final = 500ΒΆ

Fallback timeout (seconds) for a CLI call whose operation is unknown.

Defaults to the conservative MUTATING_TIMEOUT: when in doubt, wait rather than risk killing a legitimate long-running command.

meta_package_manager.execution.OPERATION_TIMEOUTS: Final[dict[str, int]] = {'cleanup': 500, 'doctor': 500, 'install': 500, 'installed': 120, 'orphans': 120, 'outdated': 120, 'remove': 500, 'search': 120, 'sync': 500, 'upgrade': 500, 'upgrade_all': 500, 'version': 120}ΒΆ

Per-operation timeout defaults, applied only when the user has set no explicit --timeout (or per-manager timeout override).

Keyed by the meta_package_manager.capabilities.Operations member name, plus the special "version" detection probe. The keys are validated against the Operations enum by the test suite so the two never drift apart. An operation absent from this map resolves to DEFAULT_TIMEOUT.

meta_package_manager.execution.SPINNER_DELAY: Final = 0.1ΒΆ

Seconds a CLI call must run before its progress spinner appears.

Kept short so the spinner surfaces almost immediately on any call that is not instant: prompt feedback makes mpm feel responsive from the start rather than stalled during the first second. Only the quickest calls (cached version probes, trivial metadata queries) finish within this delay and stay silent; anything slower (a guix search, a source build) shows the spinner right away.

class meta_package_manager.execution.CLIExecutor[source]ΒΆ

Bases: object

Locate a manager’s CLI on the system and run it.

Mixin inherited by meta_package_manager.manager.PackageManager. Owns the CLI-invocation configuration (names, search paths, environment, arguments, timeout) and the engine that searches for the binary, executes it, captures and normalizes its output, accumulates errors, and parses its self-reported version.

Initialize cli_errors list.

cli_names: tuple[str, ...]ΒΆ

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 python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

cli_search_path: tuple[str, ...] = ()ΒΆ

List of additional path to help mpm hunt down the package manager CLI.

Must be a list of strings whose order dictates the search sequence.

Most of the time unnecessary: meta_package_manager.execution.CLIExecutor.cli_path works well on all platforms.

extra_env: ClassVar[Mapping[str, str | None] | None] = NoneΒΆ

Additional environment variables to add to the current context.

Automatically applied on each meta_package_manager.execution.CLIExecutor.run_cli() calls.

pre_cmds: tuple[str, ...] = ()ΒΆ

Global list of pre-commands to add before before invoked CLI.

Automatically added to each meta_package_manager.execution.CLIExecutor.run_cli() call.

Used to prepend sudo or other system utilities.

pre_args: tuple[str, ...] = ()ΒΆ
post_args: tuple[str, ...] = ()ΒΆ

Global list of options used before and after the invoked package manager CLI.

Automatically added to each meta_package_manager.execution.CLIExecutor.run_cli() call.

Essentially used to force silencing, low verbosity or no-color output.

version_cli_options: tuple[str, ...] = ('--version',)ΒΆ

CLI options used to produce the version of the package manager.

The raw output produced by the package manager CLI will be parsed with the version_regexes below to extract the version number.

version_cli: str | None = NoneΒΆ

Alternate binary probed for the manager’s version, instead of the main CLI.

Some manager suites expose no version flag on any of their own binaries (OpenBSD’s pkg_add/pkg_info, Solaris’ pkgadd/pkginfo): they ship with the base system and are versioned with the OS itself. Naming a version_cli (like uname) makes the version probe run that binary with version_cli_options and parse its output with version_regexes, while every operation keeps using the manager’s own cli_path. The binary is resolved with which(); the version resolves to None (manager not fresh) when it is not found.

version_regexes: tuple[str, ...] = ('(?P<version>\\S+)',)ΒΆ

Regular expressions used to extract the version number.

This property must be a tuple of strings, each of which is a valid regular expression that must contain a group named <version>.

The first of these regexes producing a match and returning non-empty <version> group will be used as the version string of the package manager.

That version string will then be sanitized and normalized by meta_package_manager.execution.CLIExecutor.version.

By default match the first part that is space-separated.

Caution

These regexes are compiled with re.MULTILINE only. They are not compiled with re.VERBOSE, so literal whitespace in the pattern is significant and matches whitespace in the CLI output.

stop_on_error: bool = FalseΒΆ

Tell the manager to either raise or continue on errors.

dry_run: bool = FalseΒΆ

Do not actually perform any action, just simulate CLI calls.

plan: bool = FalseΒΆ

Capture state-changing CLI calls for inspection instead of running them.

Set by mpm --plan. Unlike dry_run (which simulates every call, read-only queries included), plan mode lets the read-only queries (installed, outdated, search) run for real so the resolved plan reflects actual system state, and records only the state-changing commands (see _MUTATING_OPERATIONS) into PLAN_RECORDER.

timeout: int | None = NoneΒΆ

Maximum number of seconds to wait for a CLI call to complete.

None means the user expressed no explicit preference: the effective cap is then resolved per-operation by _resolve_timeout() from OPERATION_TIMEOUTS. A non-None value (the --timeout flag or a per-manager override) wins for every operation.

progress: bool = FalseΒΆ

Whether CLI calls may show a progress spinner while they block.

Set by the CLI to an interactive, human-facing run only (a TTY, no serialized output, not at DEBUG verbosity). Even when True the spinner still self-suppresses off a TTY: see _make_spinner(). Defaults to False so programmatic use stays silent.

cooldown: timedelta | None = NoneΒΆ

Minimum age a release must have before it can be installed or upgraded.

When set, the manager refuses to bring in any package version published more recently than cooldown ago. This is a mitigation against supply-chain attacks: a malicious release is typically detected and pulled within days of publication, so a waiting period keeps freshly-published (and potentially compromised) versions out of the system. None disables the gate.

Only managers able to natively enforce a release-age limit honor this; see cooldown_env_var and supports_cooldown.

require_cooldown_support: bool = TrueΒΆ

Require native cooldown support to run install/upgrade.

By default (True, fail-closed), when a cooldown is requested, install and upgrade operations are skipped for managers lacking native release-age support, so nothing slips in unguarded. Setting this to False opts into running those operations anyway, without the safeguard.

sudo: bool | None = NoneΒΆ

User escalation policy: run this manager’s privileged commands with sudo.

None (the default) means the user expressed no preference, so the built-in default_sudo decides. True/False force escalation on or off for every operation this manager marks privileged (a build_cli(..., sudo=True) call). Set globally by mpm --sudo / mpm --no-sudo and per manager by the [mpm.managers.<id>] sudo config key, the latter winning (see meta_package_manager.pool.ManagerPool._select_managers()).

Only privileged operations on UNIX are ever escalated. A manager that escalates internally (internal_sudo) has no such markers and is never wrapped in sudo by mpm: its own sudo reuses the credential cache when prime_sudo() finds it already warm, and is otherwise covered by the silent-call notice in run().

default_sudo: bool = FalseΒΆ

Built-in escalation default, used when sudo is None.

False on the base: most managers install into user-writable trees and never need root. The system package managers whose privileged operations require root (apt, dnf, pacman, zypper, …) set this to True so their build_cli(..., sudo=True) operations escalate out of the box, while staying switchable off through sudo (--no-sudo or config) for rootless setups.

internal_sudo: bool = FalseΒΆ

Marks a manager whose CLI invokes sudo itself mid-run.

Homebrew cask runs it from installer artifacts, fink re-execs its root commands through it, and the AUR helpers call sudo pacman for their install steps. mpm never wraps such a manager’s commands: either none of its operations carry a build_cli(..., sudo=True) marker (cask, fink), or its default_sudo = False policy leaves the markers it inherits unescalated (the AUR helpers). Running the tool under sudo is often forbidden outright (brew refuses root, makepkg refuses to build). Consumed by prime_sudo(), whose opportunistic probe keeps an already-warm credential cache alive for these internal escalations, and by the silent-call notice in run(), which flags a possibly-hidden password prompt on a cold cache.

Forcing sudo = true on such a manager (config key or --sudo) still never wraps its commands, but does promote it into the up-front prompt path of prime_sudo().

cooldown_env_var: ClassVar[str | None] = NoneΒΆ

Environment variable this manager reads to honor a cooldown.

None (the default) means the manager has no native release-age mechanism and cannot honor a cooldown. A subclass that sets this string advertises support (see supports_cooldown); the value produced by cooldown_env_value() is then injected into the environment of every CLI call.

windows_creation_flags: int = 0ΒΆ

Additional Windows process creation flags OR-ed with CREATE_NO_WINDOW.

Use this on individual managers to control how their subprocess is attached to the calling process’s console. For example, setting this to subprocess.DETACHED_PROCESS (0x8) fully detaches the child from the parent’s console. Any grandchild process (like a COM server or installer EXE) that calls GenerateConsoleCtrlEvent(0) on exit will then fail silently because there is no console to broadcast to.

No-op on non-Windows platforms (getattr returns 0 for Windows-only flags).

windows_processes_to_cleanup: tuple[str, ...] = ()ΒΆ

Windows process image names to forcibly terminate after each CLI call.

When a package manager spawns grandchild processes that outlive the direct subprocess (like winget’s WindowsPackageManagerServer.exe COM server), those orphans can linger and consume resources. List the image names here so they are killed after communicate() returns.

No-op on non-Windows platforms.

run_cache: dict[tuple, tuple[int, str, str]] | None = NoneΒΆ

Optional cache that de-duplicates identical CLI runs within a lock family.

None by default, which disables caching: every run() call spawns its own subprocess. meta_package_manager.dispatch.dispatch() injects one shared dict into all the managers of a multi-manager lock-family lane (see meta_package_manager.dispatch.SHARED_LOCK_FAMILIES) for the duration of that lane, so members resolving to a byte-identical command (brew and cask both running brew update for mpm sync) run the subprocess once and replay the cached (code, output, error) for the rest. The replay still walks run()’s logging and failure gate, so a failed shared command is attributed to every member. Keyed on the resolved command line and its environment, so only genuinely identical invocations collapse.

cli_errors: list[CLIError]ΒΆ

Accumulate all CLI errors encountered by the package manager.

property supports_cooldown: boolΒΆ

Whether this manager can natively enforce a release-age cooldown.

cooldown_env_value()[source]ΒΆ

Render cooldown as the value of cooldown_env_var.

Defaults to the RFC 3339 timestamp of the most recent release date still allowed, i.e. now minus the cooldown. Managers whose environment variable expects another format (a number of minutes, a bare day count, …) override this.

Return type:

str

cooldown_rounded_up(unit_seconds)[source]ΒΆ

Render cooldown as an integer count of unit_seconds-long units, rounded up.

Helper for the cooldown_env_value() overrides of managers whose native release-age knob expects a unit count rather than the default RFC 3339 timestamp (npm’s day-based min-release-age, pnpm’s minute-based minimumReleaseAge). Sub-unit cooldowns round up so the gate over-protects rather than silently collapsing to 0 (the β€œno cooldown” sentinel).

Return type:

str

cooldown_env()[source]ΒΆ

Environment fragment enforcing the cooldown, empty when inactive.

Returns an empty mapping unless a cooldown is set and the manager supports it. Merged into the environment of every run() call.

Return type:

Mapping[str, str | None]

search_all_cli(cli_names, env=None)[source]ΒΆ

Search for all binary files matching the CLI names, in all environment path.

This is like our own implementation of shutil.which(), with the difference that it is capable of returning all the possible paths of the provided file names, in all environment path, not just the first one that match. And on Windows, prevents matching of CLI in the current directory, which takes precedence on other paths.

Returns all files matching any cli_names, by iterating over all folders in this order:

  • folders provided by cli_search_path,

  • then in all the default places specified by the environment variable (i.e. os.getenv("PATH")).

Only returns files that exists and are not empty.

Caution

Symlinks are not resolved, because some manager like Homebrew on Linux relies on some sort of symlink-based trickery to set environment variables.

Return type:

Generator[Path, None, None]

which(cli_name)[source]ΒΆ

Emulates the which command.

Based on the search_all_cli() method.

Return type:

Path | None

sibling_cli(name, *, same_dir=False)[source]ΒΆ

Resolve the path of a sibling binary of the manager’s main CLI.

Some managers ship as a suite of binaries (xbps-install/xbps-query, pkg_add/pkg_info, emerge’s qlist): an operation then runs a sibling instead of the main CLI. By default the sibling is searched like the main CLI itself (which(), honoring cli_search_path), and a missing binary raises FileNotFoundError rather than silently falling back to the wrong program.

same_dir=True instead takes the sibling from the directory of cli_path, without an existence probe: suites installing all their binaries side by side (XBPS, Nix) guarantee the neighbor, and resolving it from the same directory can never mix two installations. A genuinely missing file then surfaces at spawn time.

Return type:

Path

property cli_path: Path | NoneΒΆ

Fully qualified path to the canonical package manager binary.

Try each CLI names provided by cli_names, in each system path provided by cli_search_path. In that order. Then returns the first match.

Executability of the CLI will be separately assessed later by the meta_package_manager.execution.CLIExecutor.executable property below.

property version: TokenizedString | NoneΒΆ

Invoke the manager and extract its own reported version string.

Returns a parsed and normalized version in the form of a meta_package_manager.version.TokenizedString instance.

Skipped on platforms where the manager is not supported, even if cli_path resolved to an executable: that binary almost certainly belongs to a different tool that happens to share the same name (e.g. GNU make on macOS getting matched by the FreeBSD ports manager), so probing it would either misreport the version or surface confusing error output.

property executable: boolΒΆ

Is the package manager CLI can be executed by the current user?

acting_as(operation=None, *, stop_on_error=None)[source]ΒΆ

Temporarily adjust the manager’s execution state, restoring it on exit.

operation re-stamps _active_operation (the per-operation timeout and watchdog key) for the duration of the block; None leaves the current stamp untouched. stop_on_error likewise overrides the failure policy when set: the per-package state changers run their action under stop_on_error=True so a botched operation raises and is recorded by the caller instead of being silently accumulated.

The public seam for callers needing a scoped state override: the CLI layer must never poke _active_operation or stop_on_error directly.

Return type:

Iterator[None]

run(*args, extra_env=None, must_succeed=False)[source]ΒΆ

Run a shell command, return the output and accumulate error messages.

args is allowed to be a nested structure of iterables, in which case it will be recursively flatten, then None will be discarded, and finally each item casted to strings.

Running commands with that method takes care of:
  • disclosing the invocation at INFO (the reproducible $-prompt line with forced environment variables) and streaming the raw output live to DEBUG, prefixed with the manager ID, via click_extra.execution.run_cli()

  • flagging, on a terminal, the mutating call of an internal escalator that goes silent on a cold credential cache and may be blocked on a hidden password prompt (see _StallWatchdog)

  • detaching every other call into its own POSIX session and process group, so a timeout or Ctrl+C reaps the whole process tree and a wedged grandchild cannot linger as an orphan; the flagged call above keeps the controlling terminal so its sudo prompt stays answerable

  • removing ANSI escape codes from subprocess.CompletedProcess.stdout and subprocess.CompletedProcess.stderr

  • returning ready-to-use normalized strings (dedented and stripped)

  • letting mpm --dry-run and mpm --stop-on-error have expected effect on execution

Parameters:

must_succeed (bool) – if True, raise meta_package_manager.manager.CLIError when the command fails, regardless of the user-facing stop_on_error preference, rather than accumulating the error for an end-of-run summary. Use for calls whose output is parsed (JSON, XML, regex), where a swallowed failure would be indistinguishable from empty results. A non-zero exit that leaves <stderr> empty is tolerated as a benign status code (npm and pnpm outdated exit 1 when updates exist); only the per-package state changers, which run under a patched stop_on_error, treat every non-zero exit as a failure. See the failure gate below for details.

Return type:

str

build_cli(*args, auto_pre_cmds=True, auto_pre_args=True, auto_post_args=True, override_pre_cmds=None, override_cli_path=None, override_pre_args=None, override_post_args=None, sudo=False)[source]ΒΆ

Build the package manager CLI by combining the custom *args with the package manager’s global parameters.

Returns a tuple of strings.

Helps the construction of CLI’s repeating patterns and makes the code easier to read. Just pass the specific *args and the full CLI string will be composed out of the globals, following this schema:

$ [<pre_cmds>|sudo --non-interactive] <cli_path> <pre_args> <*args> <post_args>
Return type:

tuple[str, …]

Each additional set of elements can be disabled with their respective flag:

  • auto_pre_cmds=False to skip the automatic addition of self.pre_cmds

  • auto_pre_args=False to skip the automatic addition of self.pre_args

  • auto_post_args=False to skip the automatic addition of self.post_args

Each global set of elements can be locally overridden with:

  • override_pre_cmds=tuple()

  • override_cli_path=str

  • override_pre_args=tuple()

  • override_post_args=tuple()

On UNIX, an operation marked privileged (sudo=True) is escalated only when the per-manager policy opts in (sudo, falling back to default_sudo). It is then run through sudo with --non-interactive (it spends the credential cache warmed by prime_sudo() and fails fast rather than blocking on a password prompt). When escalation applies, override_pre_cmds is not allowed to be set and auto_pre_cmds is forced to False. A non-UNIX host never escalates.

run_cli(*args, auto_extra_env=True, auto_pre_cmds=True, auto_pre_args=True, auto_post_args=True, override_extra_env=None, override_pre_cmds=None, override_cli_path=None, override_pre_args=None, override_post_args=None, force_exec=False, must_succeed=False, sudo=False)[source]ΒΆ

Build and run the package manager CLI by combining the custom *args with the package manager’s global parameters.

After the CLI is built with the meta_package_manager.execution.CLIExecutor.build_cli() method, it is executed with the meta_package_manager.execution.CLIExecutor.run() method, augmented with environment variables from self.extra_env.

All parameters are the same as meta_package_manager.execution.CLIExecutor.build_cli(), plus:

  • auto_extra_env=False to skip the automatic addition of self.extra_env

  • override_extra_env=dict() to locally overrides the later

  • force_exec ignores the mpm --dry-run, mpm --stop-on-error and mpm --plan options to force the execution and completion of the command. It is used for reads whose output is needed regardless (version detection, yarn global dir), which must run for real even when the user asked to simulate or to only plan mutations.

  • must_succeed raises on non-zero exit regardless of mpm --stop-on-error. See run() for details.

Return type:

str

meta_package_manager.labels moduleΒΆ

Utilities to generate the extra labels and labeller rules for GitHub issues and PRs.

The content and file rules produced here are a convenience: they pre-label a freshly filed issue or PR to save the maintainer a first pass. They never replace the manual review and classification, and nothing downstream treats them as authoritative. They are therefore tuned for precision over recall: a rule is encoded only when its signal is unambiguous (see generate_content_rules() and generate_file_rules()), and a manager with no unambiguous term simply gets no content rule and is labelled by hand.

meta_package_manager.labels.generate_labels(all_labels, groups, prefix, color)[source]ΒΆ

Generate labels.

A dedicated label is produced for each entry of the all_labels parameter, unless it is part of a group. In which case a dedicated label for that group will be created.

Returns the {label_id: label_name} map and the list of (label_name, color, description) rows to register, leaving the caller to fold them into the global LABELS registry. Kept pure (no global mutation) so it can be called repeatedly without double-populating the registry.

Return type:

tuple[dict[str, str], list[tuple[str, str, str]]]

meta_package_manager.labels.MANAGER_LABEL_GROUPS: TLabelGroup = {'dpkg-based': frozenset({'apt', 'apt-mint', 'deb-get', 'opkg', 'pacstall'}), 'homebrew': frozenset({'brew', 'cask', 'zerobrew'}), 'npm-based': frozenset({'npm', 'pnpm', 'volta', 'yarn', 'yarn-berry'}), 'pacman-based': frozenset({'pacaur', 'pacman', 'paru', 'yay'}), 'pip-based': frozenset({'pip', 'pipx'}), 'pkg-based': frozenset({'pkg', 'ports'}), 'rpm-based': frozenset({'dnf', 'dnf5', 'urpmi', 'yum', 'zypper'}), 'scoop-based': frozenset({'scoop', 'sfsu'}), 'uv-based': frozenset({'uv', 'uvx'}), 'vscode-based': frozenset({'vscode', 'vscodium'})}ΒΆ

Managers sharing the same ecosystem are grouped together under the same label.

Grouping is by ecosystem (the underlying packaging system), not by installation paradigm. For example, source-based helpers like Pacstall and AUR helpers are grouped with their ecosystem (dpkg-based and pacman-based respectively), even though they build from source rather than fetching pre-built binaries.

meta_package_manager.labels.all_manager_label_ids = frozenset({'apk', 'apm', 'apt', 'apt-cyg', 'apt-mint', 'asdf', 'brew', 'cargo', 'cask', 'cave', 'choco', 'chromebrew', 'composer', 'conda', 'cpan', 'deb-get', 'dnf', 'dnf5', 'emerge', 'eopkg', 'fink', 'flatpak', 'fwupd', 'gem', 'gh-ext', 'guix', 'macports', 'mas', 'mise', 'mpm', 'nix', 'npm', 'opkg', 'pacaur', 'pacman', 'pacstall', 'paru', 'pip', 'pipx', 'pkcon', 'pkg', 'pkg-tools', 'pkgin', 'pnpm', 'ports', 'pwsh-gallery', 'scoop', 'sdkman', 'sfsu', 'slapt-get', 'snap', 'soar', 'sorcery', 'steamcmd', 'stew', 'sun-tools', 'swupd', 'tazpkg', 'tlmgr', 'topgrade', 'urpmi', 'uv', 'uvx', 'volta', 'vscode', 'vscodium', 'winget', 'xbps', 'yarn', 'yarn-berry', 'yay', 'yum', 'zerobrew', 'zypper'})ΒΆ

Adds mpm as its own manager alongside all those implemented.

meta_package_manager.labels.MANAGER_LABELS = {'apk': 'πŸ“¦ manager: apk', 'apm': 'πŸ“¦ manager: apm', 'apt': 'πŸ“¦ manager: dpkg-based', 'apt-cyg': 'πŸ“¦ manager: apt-cyg', 'apt-mint': 'πŸ“¦ manager: dpkg-based', 'asdf': 'πŸ“¦ manager: asdf', 'brew': 'πŸ“¦ manager: homebrew', 'cargo': 'πŸ“¦ manager: cargo', 'cask': 'πŸ“¦ manager: homebrew', 'cave': 'πŸ“¦ manager: cave', 'choco': 'πŸ“¦ manager: choco', 'chromebrew': 'πŸ“¦ manager: chromebrew', 'composer': 'πŸ“¦ manager: composer', 'conda': 'πŸ“¦ manager: conda', 'cpan': 'πŸ“¦ manager: cpan', 'deb-get': 'πŸ“¦ manager: dpkg-based', 'dnf': 'πŸ“¦ manager: rpm-based', 'dnf5': 'πŸ“¦ manager: rpm-based', 'emerge': 'πŸ“¦ manager: emerge', 'eopkg': 'πŸ“¦ manager: eopkg', 'fink': 'πŸ“¦ manager: fink', 'flatpak': 'πŸ“¦ manager: flatpak', 'fwupd': 'πŸ“¦ manager: fwupd', 'gem': 'πŸ“¦ manager: gem', 'gh-ext': 'πŸ“¦ manager: gh-ext', 'guix': 'πŸ“¦ manager: guix', 'macports': 'πŸ“¦ manager: macports', 'mas': 'πŸ“¦ manager: mas', 'mise': 'πŸ“¦ manager: mise', 'mpm': 'πŸ“¦ manager: mpm', 'nix': 'πŸ“¦ manager: nix', 'npm': 'πŸ“¦ manager: npm-based', 'opkg': 'πŸ“¦ manager: dpkg-based', 'pacaur': 'πŸ“¦ manager: pacman-based', 'pacman': 'πŸ“¦ manager: pacman-based', 'pacstall': 'πŸ“¦ manager: dpkg-based', 'paru': 'πŸ“¦ manager: pacman-based', 'pip': 'πŸ“¦ manager: pip-based', 'pipx': 'πŸ“¦ manager: pip-based', 'pkcon': 'πŸ“¦ manager: pkcon', 'pkg': 'πŸ“¦ manager: pkg-based', 'pkg-tools': 'πŸ“¦ manager: pkg-tools', 'pkgin': 'πŸ“¦ manager: pkgin', 'pnpm': 'πŸ“¦ manager: npm-based', 'ports': 'πŸ“¦ manager: pkg-based', 'pwsh-gallery': 'πŸ“¦ manager: pwsh-gallery', 'scoop': 'πŸ“¦ manager: scoop-based', 'sdkman': 'πŸ“¦ manager: sdkman', 'sfsu': 'πŸ“¦ manager: scoop-based', 'slapt-get': 'πŸ“¦ manager: slapt-get', 'snap': 'πŸ“¦ manager: snap', 'soar': 'πŸ“¦ manager: soar', 'sorcery': 'πŸ“¦ manager: sorcery', 'steamcmd': 'πŸ“¦ manager: steamcmd', 'stew': 'πŸ“¦ manager: stew', 'sun-tools': 'πŸ“¦ manager: sun-tools', 'swupd': 'πŸ“¦ manager: swupd', 'tazpkg': 'πŸ“¦ manager: tazpkg', 'tlmgr': 'πŸ“¦ manager: tlmgr', 'topgrade': 'πŸ“¦ manager: topgrade', 'urpmi': 'πŸ“¦ manager: rpm-based', 'uv': 'πŸ“¦ manager: uv-based', 'uvx': 'πŸ“¦ manager: uv-based', 'volta': 'πŸ“¦ manager: npm-based', 'vscode': 'πŸ“¦ manager: vscode-based', 'vscodium': 'πŸ“¦ manager: vscode-based', 'winget': 'πŸ“¦ manager: winget', 'xbps': 'πŸ“¦ manager: xbps', 'yarn': 'πŸ“¦ manager: npm-based', 'yarn-berry': 'πŸ“¦ manager: npm-based', 'yay': 'πŸ“¦ manager: pacman-based', 'yum': 'πŸ“¦ manager: rpm-based', 'zerobrew': 'πŸ“¦ manager: homebrew', 'zypper': 'πŸ“¦ manager: rpm-based'}ΒΆ

Maps all manager IDs to their labels.

meta_package_manager.labels.PLATFORM_LABELS = {'ALT Linux': 'πŸ–₯ platform: Linux', 'AlmaLinux': 'πŸ–₯ platform: Linux', 'Alpine Linux': 'πŸ–₯ platform: Linux', 'Amazon Linux': 'πŸ–₯ platform: Linux', 'Android': 'πŸ–₯ platform: Linux', 'Arch Linux': 'πŸ–₯ platform: Linux', 'Buildroot': 'πŸ–₯ platform: Linux', 'CachyOS': 'πŸ–₯ platform: Linux', 'CentOS': 'πŸ–₯ platform: Linux', 'ChromeOS': 'πŸ–₯ platform: Linux', 'Clear Linux OS': 'πŸ–₯ platform: Linux', 'CloudLinux OS': 'πŸ–₯ platform: Linux', 'Cygwin': 'πŸ–₯ platform: Unix', 'Debian': 'πŸ–₯ platform: Linux', 'DragonFly BSD': 'πŸ–₯ platform: BSD', 'EndeavourOS': 'πŸ–₯ platform: Linux', 'Exherbo Linux': 'πŸ–₯ platform: Linux', 'Fedora': 'πŸ–₯ platform: Linux', 'FreeBSD': 'πŸ–₯ platform: BSD', 'GNU/Hurd': 'πŸ–₯ platform: Unix', 'Generic Linux': 'πŸ–₯ platform: Linux', 'Gentoo Linux': 'πŸ–₯ platform: Linux', 'Guix System': 'πŸ–₯ platform: Linux', 'Haiku': 'πŸ–₯ platform: Unix', 'IBM AIX': 'πŸ–₯ platform: Unix', 'IBM PowerKVM': 'πŸ–₯ platform: Linux', 'IBM i': 'πŸ–₯ platform: Unix', 'KVM for IBM z Systems': 'πŸ–₯ platform: Linux', 'Kali Linux': 'πŸ–₯ platform: Linux', 'Linux Mint': 'πŸ–₯ platform: Linux', 'Mageia': 'πŸ–₯ platform: Linux', 'Mandriva Linux': 'πŸ–₯ platform: Linux', 'Manjaro Linux': 'πŸ–₯ platform: Linux', 'MidnightBSD': 'πŸ–₯ platform: BSD', 'NetBSD': 'πŸ–₯ platform: BSD', 'NixOS': 'πŸ–₯ platform: Linux', 'Nobara': 'πŸ–₯ platform: Linux', 'OpenBSD': 'πŸ–₯ platform: BSD', 'OpenWrt': 'πŸ–₯ platform: Linux', 'Oracle Linux': 'πŸ–₯ platform: Linux', 'Parallels': 'πŸ–₯ platform: Linux', 'Pidora': 'πŸ–₯ platform: Linux', 'PikaOS': 'πŸ–₯ platform: Linux', 'Raspbian': 'πŸ–₯ platform: Linux', 'RedHat Enterprise Linux': 'πŸ–₯ platform: Linux', 'Rocky Linux': 'πŸ–₯ platform: Linux', 'SUSE Linux Enterprise Server': 'πŸ–₯ platform: Linux', 'Scientific Linux': 'πŸ–₯ platform: Linux', 'Slackware': 'πŸ–₯ platform: Linux', 'SliTaz GNU/Linux': 'πŸ–₯ platform: Linux', 'Solaris': 'πŸ–₯ platform: Unix', 'Source Mage GNU/Linux': 'πŸ–₯ platform: Linux', 'SunOS': 'πŸ–₯ platform: BSD', 'Tuxedo OS': 'πŸ–₯ platform: Linux', 'Ubuntu': 'πŸ–₯ platform: Linux', 'Ultramarine': 'πŸ–₯ platform: Linux', 'Void Linux': 'πŸ–₯ platform: Linux', 'Windows': 'πŸ–₯ platform: Windows', 'Windows Subsystem for Linux v1': 'πŸ–₯ platform: Linux', 'Windows Subsystem for Linux v2': 'πŸ–₯ platform: Linux', 'XenServer': 'πŸ–₯ platform: Linux', 'illumos': 'πŸ–₯ platform: Unix', 'macOS': 'πŸ–₯ platform: macOS', 'openSUSE': 'πŸ–₯ platform: Linux'}ΒΆ

Maps all platform names to their labels.

meta_package_manager.labels.LABELS: list[tuple[str, str, str]] = [('πŸ“¦ manager: apk', '#bfdadc', 'apk'), ('πŸ“¦ manager: apm', '#bfdadc', 'apm'), ('πŸ“¦ manager: apt-cyg', '#bfdadc', 'apt-cyg'), ('πŸ“¦ manager: asdf', '#bfdadc', 'asdf'), ('πŸ“¦ manager: cargo', '#bfdadc', 'cargo'), ('πŸ“¦ manager: cave', '#bfdadc', 'cave'), ('πŸ“¦ manager: choco', '#bfdadc', 'choco'), ('πŸ“¦ manager: chromebrew', '#bfdadc', 'chromebrew'), ('πŸ“¦ manager: composer', '#bfdadc', 'composer'), ('πŸ“¦ manager: conda', '#bfdadc', 'conda'), ('πŸ“¦ manager: cpan', '#bfdadc', 'cpan'), ('πŸ“¦ manager: dpkg-based', '#bfdadc', 'apt, apt-mint, deb-get, opkg, pacstall'), ('πŸ“¦ manager: emerge', '#bfdadc', 'emerge'), ('πŸ“¦ manager: eopkg', '#bfdadc', 'eopkg'), ('πŸ“¦ manager: fink', '#bfdadc', 'fink'), ('πŸ“¦ manager: flatpak', '#bfdadc', 'flatpak'), ('πŸ“¦ manager: fwupd', '#bfdadc', 'fwupd'), ('πŸ“¦ manager: gem', '#bfdadc', 'gem'), ('πŸ“¦ manager: gh-ext', '#bfdadc', 'gh-ext'), ('πŸ“¦ manager: guix', '#bfdadc', 'guix'), ('πŸ“¦ manager: homebrew', '#bfdadc', 'brew, cask, zerobrew'), ('πŸ“¦ manager: macports', '#bfdadc', 'macports'), ('πŸ“¦ manager: mas', '#bfdadc', 'mas'), ('πŸ“¦ manager: mise', '#bfdadc', 'mise'), ('πŸ“¦ manager: mpm', '#bfdadc', 'mpm'), ('πŸ“¦ manager: nix', '#bfdadc', 'nix'), ('πŸ“¦ manager: npm-based', '#bfdadc', 'npm, pnpm, volta, yarn, yarn-berry'), ('πŸ“¦ manager: pacman-based', '#bfdadc', 'pacaur, pacman, paru, yay'), ('πŸ“¦ manager: pip-based', '#bfdadc', 'pip, pipx'), ('πŸ“¦ manager: pkcon', '#bfdadc', 'pkcon'), ('πŸ“¦ manager: pkg-based', '#bfdadc', 'pkg, ports'), ('πŸ“¦ manager: pkg-tools', '#bfdadc', 'pkg-tools'), ('πŸ“¦ manager: pkgin', '#bfdadc', 'pkgin'), ('πŸ“¦ manager: pwsh-gallery', '#bfdadc', 'pwsh-gallery'), ('πŸ“¦ manager: rpm-based', '#bfdadc', 'dnf, dnf5, urpmi, yum, zypper'), ('πŸ“¦ manager: scoop-based', '#bfdadc', 'scoop, sfsu'), ('πŸ“¦ manager: sdkman', '#bfdadc', 'sdkman'), ('πŸ“¦ manager: slapt-get', '#bfdadc', 'slapt-get'), ('πŸ“¦ manager: snap', '#bfdadc', 'snap'), ('πŸ“¦ manager: soar', '#bfdadc', 'soar'), ('πŸ“¦ manager: sorcery', '#bfdadc', 'sorcery'), ('πŸ“¦ manager: steamcmd', '#bfdadc', 'steamcmd'), ('πŸ“¦ manager: stew', '#bfdadc', 'stew'), ('πŸ“¦ manager: sun-tools', '#bfdadc', 'sun-tools'), ('πŸ“¦ manager: swupd', '#bfdadc', 'swupd'), ('πŸ“¦ manager: tazpkg', '#bfdadc', 'tazpkg'), ('πŸ“¦ manager: tlmgr', '#bfdadc', 'tlmgr'), ('πŸ“¦ manager: topgrade', '#bfdadc', 'topgrade'), ('πŸ“¦ manager: uv-based', '#bfdadc', 'uv, uvx'), ('πŸ“¦ manager: vscode-based', '#bfdadc', 'vscode, vscodium'), ('πŸ“¦ manager: winget', '#bfdadc', 'winget'), ('πŸ“¦ manager: xbps', '#bfdadc', 'xbps'), ('πŸ”Œ plugin', '#fef2c0', 'Xbar/SwiftBar/GNOME Shell plugin code, documentation and features'), ('πŸ–₯ platform: BSD', '#bfd4f2', 'DragonFly BSD, FreeBSD, MidnightBSD, NetBSD, OpenBSD, SunOS'), ('πŸ–₯ platform: Linux', '#bfd4f2', 'AlmaLinux, Alpine Linux, ALT Linux, Amazon Linux, Android, Arch Linux, Buildroot, CachyOS, CentOS, …'), ('πŸ–₯ platform: macOS', '#bfd4f2', 'macOS'), ('πŸ–₯ platform: Unix', '#bfd4f2', 'Cygwin, GNU/Hurd, Haiku, IBM AIX, IBM i, illumos, Solaris'), ('πŸ–₯ platform: Windows', '#bfd4f2', 'Windows')]ΒΆ

Global registry of all labels used in the project.

Structure:

("label_name", "color", "optional_description")
meta_package_manager.labels.CONTENT_RULES_STATIC: TLabelRules = [('πŸ”Œ plugin', ('gnome shell', 'gnome-shell', 'plugin', 'swiftbar', 'xbar'))]ΒΆ

Curated keywords feeding the content rules of labels not derived from the pool.

Holds keywords, not finished patterns: generate_content_rules() runs them through _keyword_alternation() like every other content rule.

meta_package_manager.labels.FILE_RULES_STATIC: TLabelRules = [('πŸ”Œ plugin', ('gnome-shell/**', 'meta_package_manager/bar_plugin*', 'tests/*bar_plugin*', 'tests/*gnome*', 'tests/gnome/**')), ('πŸ“¦ manager: mpm', ('meta_package_manager/*',))]ΒΆ

File rules for labels that are not derived from the pool.

mpm gets no content rule: as the project’s own name it would match nearly every issue and PR.

meta_package_manager.labels.MANAGER_CONTENT_KEYWORDS: dict[str, tuple[str, ...]] = {'apk': ('alpine', 'alpine linux'), 'apm': ('atom',), 'apt-cyg': ('cygwin',), 'asdf': ('asdf-vm',), 'cargo': ('crate', 'rust'), 'cave': ('exherbo', 'paludis'), 'choco': ('chocolatey',), 'chromebrew': ('chrome os', 'chromeos'), 'composer': ('php',), 'conda': ('anaconda', 'conda-forge', 'miniconda'), 'cpan': ('perl',), 'dpkg-based': ('aptitude', 'debian', 'dpkg', 'ubuntu'), 'emerge': ('gentoo', 'portage'), 'eopkg': ('solus',), 'flatpak': ('flathub',), 'fwupd': ('lvfs',), 'gem': ('ruby',), 'gh-ext': ('gh extension', 'github cli'), 'guix': ('gnu guix',), 'homebrew': ('homebrew',), 'mas': ('app store', 'app-store'), 'nix': ('nixos', 'nixpkgs'), 'npm-based': ('node.js', 'nodejs'), 'pacman-based': ('arch',), 'pip-based': ('pypi',), 'pkcon': ('packagekit',), 'pkg-based': ('freebsd', 'freebsd ports'), 'pkg-tools': ('openbsd',), 'pkgin': ('netbsd', 'pkgsrc'), 'pwsh-gallery': ('powershell', 'powershell gallery', 'psgallery', 'psresourceget'), 'rpm-based': ('fedora', 'mageia', 'opensuse', 'redhat', 'rhel', 'rpm', 'suse'), 'sdkman': ('sdk man',), 'slapt-get': ('slackware',), 'snap': ('snapcraft',), 'sorcery': ('source mage',), 'steamcmd': ('valve',), 'sun-tools': ('solaris', 'svr4'), 'swupd': ('clear linux', 'clearlinux'), 'tazpkg': ('slitaz',), 'tlmgr': ('ctan', 'tex live', 'texlive'), 'vscode-based': ('visual studio', 'visual studio code'), 'xbps': ('void linux',)}ΒΆ

Curated ecosystem keywords feeding each manager label’s content rule.

Keyed by the manager or group ID the label derives from. These are the only content patterns a manager label gets: the bare manager IDs are deliberately left out (see generate_content_rules()). Add only terms that are both unambiguously about this manager and absent from anything mpm prints itself: the βœ“ <id> trail, the <id>: <count> summary line, the managers table (which lists every manager’s ID and CLI binary) and the $-prompt command disclosure. That rules out manager IDs and CLI names (fwupdmgr, pwsh), leaving the distro, language and brand names a human types in an issue. A manager with no such term gets no content rule and is labelled by hand.

Skip anything that doubles as a common word even once word-anchored (port, flat, mint, void): dropping the ID removed the implicit AND-guard those leaned on, so on their own they match unrelated prose.

meta_package_manager.labels.PLATFORM_CONTENT_KEYWORDS: dict[str, tuple[str, ...]] = {'BSD': ('bsd',), 'Linux': ('linux',), 'Unix': ('unix',), 'Windows': ('c:', 'microsoft', 'windows'), 'macOS': ('apple', 'mac os', 'macos', 'os x', 'osx')}ΒΆ

Curated keyword patterns feeding each platform label’s content rule.

meta_package_manager.labels.generate_content_rules()[source]ΒΆ

Build every content rule: the static ones plus one per manager or platform label that has curated keywords.

Manager labels are driven solely by MANAGER_CONTENT_KEYWORDS, never by the bare manager IDs or CLI names. mpm enumerates every installed manager in its own output (the βœ“ <id> trail, the <id>: <count> summary line, the managers table), so a pasted trace would otherwise make every manager on the user’s system match at once: a cpan-only report came back tagged mise, pip and uv merely because they sat in the trace. The keywords are the distro, language and brand names a human types, which mpm never prints.

Every rule β€” static, manager and platform alike β€” emits exactly one pattern, built by _keyword_alternation(). A rule listing its keywords raw would read as β€œall of these” under the labeller’s all-of semantics, which no issue ever satisfies: a multi-keyword label encoded that way is silently dead. A label with no keyword is skipped: that manager gets no content rule, only its file rule. Rules are sorted by label, both cases folded.

Return type:

list[tuple[str, tuple[str, ...]]]

meta_package_manager.labels.generate_file_rules()[source]ΒΆ

Build every file rule: static ones plus one per manager label.

A manager label matches its members’ definition files (Python modules and bundled TOML files alike, anchored on the full stem so pkg.* never swallows pkgin.toml or pkcon.py) and any test file carrying a member’s stem or ID. Platform labels have no file rule: no file is platform-specific.

Return type:

list[tuple[str, tuple[str, ...]]]

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.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 version maps the package field to the item’s version key. A versions[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 by PackageManager.parse_json_items() and the declarative-manager validation in meta_package_manager.definitions.

class meta_package_manager.manager.ManagerScope(*values)[source]ΒΆ

Bases: Enum

Filesystem 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. See meta_package_manager.manager.PackageManager.discover_projects().

See also

Microsoft’s Python Environment Tools (PET) is a Rust tool that locates Python environments (venv, conda, pyenv, pipenv, Poetry, uv, …) across a machine. It only discovers environments and does not inventory their packages, but is a useful reference and benchmark for implementing Python project-scope discovery.

class meta_package_manager.manager.MetaPackageManager(name, bases, dct)[source]ΒΆ

Bases: type

Custom 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: CLIExecutor

Base class from which all package manager definitions inherits.

Initialize cli_errors list.

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.md for 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 unmaintained flag, 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 whose unmaintained flag is set, and only meaningful on such managers. Enforced by test_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 with unmaintained: a confirmed-dead manager carries an unmaintained_message instead. Enforced by test_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.

brewfile_entry_type: ClassVar[str | None] = NoneΒΆ

Name of the Brewfile DSL entry type this manager maps to, or None if the manager has no Brewfile equivalent.

Set by the subset of managers covered by Homebrew Bundle’s DSL (brew, cask, mas, vscode, npm, cargo, uv, winget, flatpak). Consumed by meta_package_manager.brewfile when rendering the output of mpm dump --brewfile.

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 frozenset of Platform instances 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 token against _NAME_VERSION_REGEXP (or the subclass’s override of it) and returns the (package_id, version) pair, or None when the token carries no recognizable version. Shared by every manager whose listings glue the name and version together.

Return type:

tuple[str, str] | None

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()). Returns None when 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 hard CLIError on malformed payloads (pwsh-gallery), a best-effort metadata enrichment logging at DEBUG (brew info).

Return type:

Any | None

parse_regex_lines(pattern, output)[source]ΒΆ

Yield one package per line of output matching pattern.

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, description and arch, 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.

Return type:

Iterator[Package]

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 through parse_json() (so a malformed payload warns and yields nothing), the package array is reached by walking the dotted list_path (None when the document is itself the array), and fields maps each package field (package_id, required, plus any of installed_version, latest_version, name, description, arch) to its JSON selector: a key name with an optional [N] list index, like version or versions[0] (see JSON_FIELD_SELECTOR_REGEX). Items missing their package_id and fields resolving to None are dropped.

Return type:

Iterator[Package]

package(**kwargs)[source]ΒΆ

Instantiate a Package object from the manager.

Sets its manage_id to the manager it belongs to.

Return type:

Package

brewfile_entry(package)[source]ΒΆ

Return (entry_name, entry_options) for a Brewfile line, or None to skip the package.

Default: emit meta_package_manager.package.Package.id as the entry name with no options. Override on managers whose Brewfile DSL counterpart expects a different shape: mas uses the app name with id: ADAM_ID, flatpak adds with: ["remote"]. Only called when brewfile_entry_type is set.

Return type:

tuple[str, dict[str, object] | None] | None

property supported: boolΒΆ

Is the package manager supported on that platform?

property fresh: boolΒΆ

Does the package manager match the version requirement?

property available: boolΒΆ

Is the package manager available and ready-to-use on the system?

Returns True only if the main CLI:

  1. is supported on the current platform,

  2. was found on the system,

  3. is executable, and

  4. match the version requirement.

property unavailable_reason: str | NoneΒΆ

Short, human-readable explanation of why available is False, or None if 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, dump and sbom subcommands: each wants β€œgive me what’s installed, and just skip this manager if its CLI blew up” rather than re-implementing the same meta_package_manager.execution.CLIError swallow. Logs one canonical warning on error and returns () so the caller carries on with the other managers.

Return type:

tuple[Package, ...]

property installed_ids: frozenset[str]ΒΆ

Installed package IDs, materialized once from installed().

property installed_version_map: dict[str, TokenizedString | str | None]ΒΆ

Installed versions keyed by package ID, materialized once from installed().

Convenience for outdated parsers 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 mirrors meta_package_manager.package.Package.installed_version, whose declared type still carries the transient str it 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 sbom in --bundled mode to populate licenses, checksums, download URLs, supplier/originator, and the declared dependency graph. The base implementation yields meta_package_manager.package.EMPTY_METADATA for each package and stays compatible with managers that do not (yet) expose richer metadata: their SBOM entries stay at the minimal Package level, matching the historical and --minimal modes.

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-info directories, Homebrew’s per-formula sbom.spdx.json, dpkg’s .md5sums).

The yielded pairs do not need to preserve the input order; the SBOM renderer matches by Package identity. Implementations are expected to swallow per-package extraction errors and yield meta_package_manager.package.EMPTY_METADATA for 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 --bundled flag 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-network flag layered under --bundled to gate the network-touching code paths specifically, leaving local enrichment always-on for --bundled.

Return type:

Iterator[tuple[Package, PackageMetadata]]

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.0000 vs 2.000000). This filter drops those false positives.

property orphans: Iterator[Package]ΒΆ

List packages installed as dependencies that nothing requires anymore.

The read-only counterpart of the --orphans action flags: where mpm cleanup --orphans removes 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.

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 python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

search(query, extended, exact)[source]ΒΆ

Search packages available for install.

There is no need for this method to be perfect and sensitive to extended and exact parameters. 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 the meta_package_manager.manager.PackageManager.refiltered_search() method below.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

Iterator[Package]

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.

Return type:

Iterator[Package]

install(package_id, version=None)[source]ΒΆ

Install one package and one only.

Allows a specific version to be provided.

Return type:

str

upgrade_all_cli()[source]ΒΆ

Returns the complete CLI to upgrade all outdated packages on the system.

Return type:

tuple[str, ...]

upgrade_one_cli(package_id, version=None)[source]ΒΆ

Returns the complete CLI to upgrade one package and one only.

Allows a specific version to be provided.

Return type:

tuple[str, ...]

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() or meta_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() raises NotImplementedError), then the list of all outdated packages will be fetched (via meta_package_manager.manager.PackageManager.outdated()) and each package will be updated one by one by calling meta_package_manager.manager.PackageManager.upgrade_one_cli().

See for example the case of meta_package_manager.managers.pip.Pip.upgrade_one_cli().

Return type:

str

remove(package_id)[source]ΒΆ

Remove one package and one only.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

str

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 as mpm 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 --orphans then falls back to meta_package_manager.manager.PackageManager.remove() and logs one INFO capability-skip.

Return type:

str

sync()[source]ΒΆ

Refresh package metadata from remote repositories.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

None

cleanup()[source]ΒΆ

Run the manager’s non-destructive cleanup categories.

Not an operation managers define anymore: cleanup is the fixed composition of the non-destructive category methods a manager overrides (cleanup_cache(), then cleanup_repair()). The orphan sweep never joins in, native or synthesized: it is the one category that removes packages, so it only runs on an explicit mpm cleanup --orphans (or a direct cleanup_orphan() call), keeping a plain cleanup package-preserving on every manager.

A manager overriding no category method does not advertise the cleanup operation at all (see meta_package_manager.capabilities.implements()) and this composer is then a no-op.

Return type:

None

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 plain cleanup() composition and only runs on an explicit mpm cleanup --orphans.

Distinct from meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As with cleanup(), 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 orphans query and package removal: list the orphans, remove each one (with remove_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 full upgrade --all, and the in-process equivalent of Arch’s classic pacman -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 orphans query propagates NotImplementedError, and mpm cleanup --orphans simply skips it.

Return type:

None

cleanup_cache()[source]ΒΆ

Prune the manager’s caches, downloads and other left-over artifacts.

The cache category of cleanup(), surfaced as mpm cleanup --cache and 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:

None

cleanup_repair()[source]ΒΆ

Verify and repair the manager’s local installation state.

The repair category of cleanup(), surfaced as mpm cleanup --repair and subtracted by --skip-repair (flatpak repair --user).

Optional. Will be simply skipped by mpm if not implemented.

Return type:

None

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.

Return type:

tuple[str, ...]

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 check reports 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 doctor warns 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 (doctor sits 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.

Return type:

tuple[bool, str]

discover_projects()[source]ΒΆ

Locate project trees this manager governs by scanning the filesystem.

Extension point reserved for ManagerScope.PROJECT managers: 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

Candidate ecosystems for project-scope discovery. Listed with the project files that signal each, grouped by whether mpm already ships a system-scoped manager that could grow a project mode.

Already covered by a manager (npm, yarn, pnpm, pip, uv, cargo, gem, composer, cpan):

  • JavaScript: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml

  • Python: requirements.txt, pyproject.toml, poetry.lock, uv.lock

  • Rust: Cargo.toml, Cargo.lock

  • Ruby: Gemfile, Gemfile.lock

  • PHP: composer.json, composer.lock

  • Perl: cpanfile

No manager yet:

  • Java: pom.xml (Maven), build.gradle (Gradle), ivy.xml

  • Go: go.mod, go.sum

  • .NET: *.csproj, packages.config (NuGet)

  • Swift: Package.swift, Package.resolved

  • CocoaPods: Podfile, Podfile.lock

  • C/C++: conanfile.txt (Conan), vcpkg.json (vcpkg)

  • Conda: conda-lock.yml

Return type:

Iterator[Path]

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).

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ΒΆ

Returns the package’s pURL object.

static query_parts(query)[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.

Return type:

set[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]ΒΆ

Returns a list of packages casted to a dict with only a subset of its fields.

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).

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.

meta_package_manager.platforms moduleΒΆ

Top-level platform classification shared across mpm.

Defines MAIN_PLATFORMS, the curated platform groups (BSD, Linux, macOS, Unix, Windows) used to label managers in the CLI managers matrix, in the GitHub issue/PR platform labels, and in the documentation tables.

meta_package_manager.platforms.MAIN_PLATFORMS: tuple[Group | Platform, ...] = (Group(id='bsd', name='BSD'), Group(id='linux', name='Linux'), Platform(id='macos', name='macOS'), Group(id='unix', name='Unix'), Group(id='windows', name='Windows'))ΒΆ

Top-level classification of platforms.

This is the local reference used to classify the execution targets of mpm.

Each entry of this list will have its own dedicated column in the matrix. This list is manually maintained with tweaked IDs and names to minimize the matrix verbosity and make it readable both in CLI and documentation.

The order of this list determine the order of the resulting columns.

meta_package_manager.pool moduleΒΆ

Registration, indexing and caching of package manager supported by mpm.

meta_package_manager.pool.manager_classes = (<class 'meta_package_manager.managers.apk.APK'>, <class 'meta_package_manager.managers.apm.APM'>, <class 'meta_package_manager.managers.apt.APT'>, <class 'meta_package_manager.managers.apt.APT_Mint'>, <class 'meta_package_manager.managers.asdf.ASDF'>, <class 'meta_package_manager.managers.homebrew.Brew'>, <class 'meta_package_manager.managers.homebrew.Cask'>, <class 'meta_package_manager.managers.composer.Composer'>, <class 'meta_package_manager.managers.conda.Conda'>, <class 'meta_package_manager.managers.deb_get.Deb_Get'>, <class 'meta_package_manager.managers.dnf.DNF'>, <class 'meta_package_manager.managers.dnf.DNF5'>, <class 'meta_package_manager.managers.emerge.Emerge'>, <class 'meta_package_manager.managers.eopkg.EOPKG'>, <class 'meta_package_manager.managers.flatpak.Flatpak'>, <class 'meta_package_manager.managers.fwupd.FWUPD'>, <class 'meta_package_manager.managers.gem.Gem'>, <class 'meta_package_manager.managers.guix.Guix'>, <class 'meta_package_manager.managers.mas.MAS'>, <class 'meta_package_manager.managers.mise.Mise'>, <class 'meta_package_manager.managers.nix.Nix'>, <class 'meta_package_manager.managers.npm.NPM'>, <class 'meta_package_manager.managers.pacman.Pacaur'>, <class 'meta_package_manager.managers.pacman.Pacman'>, <class 'meta_package_manager.managers.pacstall.Pacstall'>, <class 'meta_package_manager.managers.pacman.Paru'>, <class 'meta_package_manager.managers.pip.Pip'>, <class 'meta_package_manager.managers.pipx.Pipx'>, <class 'meta_package_manager.managers.pkcon.Pkcon'>, <class 'meta_package_manager.managers.pkg.PKG'>, <class 'meta_package_manager.managers.pnpm.PNPM'>, <class 'meta_package_manager.managers.pkg.Ports'>, <class 'meta_package_manager.managers.pwsh_gallery.PWSH_Gallery'>, <class 'meta_package_manager.managers.scoop.Scoop'>, <class 'meta_package_manager.managers.sdkman.SDKMAN'>, <class 'meta_package_manager.managers.sfsu.SFSU'>, <class 'meta_package_manager.managers.snap.Snap'>, <class 'meta_package_manager.managers.sun_tools.Sun_Tools'>, <class 'meta_package_manager.managers.tazpkg.Tazpkg'>, <class 'meta_package_manager.managers.uv.UV'>, <class 'meta_package_manager.managers.uv.UVX'>, <class 'meta_package_manager.managers.volta.Volta'>, <class 'meta_package_manager.managers.winget.WinGet'>, <class 'meta_package_manager.managers.xbps.XBPS'>, <class 'meta_package_manager.managers.yarn.YarnBerry'>, <class 'meta_package_manager.managers.yarn.YarnClassic'>, <class 'meta_package_manager.managers.pacman.Yay'>, <class 'meta_package_manager.managers.dnf.YUM'>, <class 'meta_package_manager.managers.zypper.Zypper'>)ΒΆ

The list of all classes implementing the specific package managers.

Is considered valid package manager, definitions classes which:

  1. are located in the meta_package_manager.pool.ManagerPool.manager_subfolder

    subfolder, and

  2. are sub-classes of meta_package_manager.manager.PackageManager, and

  3. are not meta_package_manager.manager.PackageManager.virtual (i.e. have a

    non-null meta_package_manager.manager.PackageManager.cli_names property).

These properties are checked and enforced in unittests.

class meta_package_manager.pool.ManagerPool[source]ΒΆ

Bases: object

A dict-like register, instantiating all supported package managers.

ALLOWED_EXTRA_OPTION: Final = frozenset({'cooldown', 'dry_run', 'ignore_auto_updates', 'plan', 'progress', 'require_cooldown_support', 'stop_on_error', 'sudo', 'timeout'})ΒΆ

List of extra options that are allowed to be set on managers during the use of the meta_package_manager.pool.ManagerPool.select_managers() helper below.

property register: dict[str, PackageManager]ΒΆ

Instantiate all supported package managers.

Built-in classes first, then mpm’s bundled configuration-defined managers (built from shipped *.toml package data). Both land here at construction time, so the augmented pool is complete before the CLI enumerates it to build the dynamic --<id> flags, in every context including the test runner.

property builtin_manager_ids: frozenset[str]ΒΆ

IDs of the managers shipped with mpm, taken from manager_classes.

Computed from the classes (their id is set by the metaclass at class creation, no instantiation needed). Lets the configuration layer tell a built-in override apart from a brand-new manager definition: a [mpm.managers.<id>] section whose ID is in this set tunes a built-in, any other ID defines a new manager. See meta_package_manager.config.validate_manager_overrides_section().

property config_defined_ids: set[str]ΒΆ

IDs of managers added at runtime from configuration definitions.

Populated by add_manager(). Disjoint from builtin_manager_ids.

property bundled_manager_ids: frozenset[str]ΒΆ

IDs of the managers mpm ships as bundled configuration definitions.

Config-defined (built from shipped *.toml package data, not a Python class) yet always present in register like the built-ins. Disjoint from builtin_manager_ids and config_defined_ids.

property known_manager_ids: frozenset[str]ΒΆ

Every manager ID mpm ships: built-in classes plus bundled definitions.

A [mpm.managers.<id>] section keyed by one of these tunes a shipped manager (an override); any other ID defines a brand-new one. The configuration layer routes override-versus-definition on this set. See meta_package_manager.config.validate_manager_overrides_section().

property overridden_fields: dict[str, set[str]]ΒΆ

Per-manager attribute names that the user explicitly overrode via [mpm.managers.<id>].

Populated by meta_package_manager.config.apply_manager_overrides(). Read by _select_managers to skip the global --<flag> defaults for fields the user has explicitly set per manager. Tracked separately from instance __dict__ membership so the global defaults can still refresh fields that were previously set by an earlier _select_managers call but were never user-overridden.

get(key)ΒΆ
values()[source]ΒΆ
items()[source]ΒΆ
add_manager(manager)[source]ΒΆ

Register a runtime-built manager (from a config definition) into the pool.

Inserts the instance and evicts the cached ID lists so the new manager is picked up by selection, default-set computation and the dynamic CLI flags. Built into the pool (rather than mutating register from outside) so the cache invalidation stays in one place. Applied by meta_package_manager.config.register_config_managers().

Return type:

None

property all_manager_ids: tuple[str, ...]ΒΆ

All recognized manager IDs.

Returns a list of sorted items to provide consistency across all UI, and reproducibility in the order package managers are evaluated.

property maintained_manager_ids: tuple[str, ...]ΒΆ

All manager IDs which are not unmaintained.

property default_manager_ids: tuple[str, ...]ΒΆ

All manager IDs supported on the current platform and not unmaintained.

Must keep the same order defined by meta_package_manager.pool.ManagerPool.all_manager_ids.

property unsupported_manager_ids: tuple[str, ...]ΒΆ

All manager IDs unsupported on the current platform but still maintained.

Order is not important here as this list will be used to discard managers from selection sets.

select_managers(*args, **kwargs)[source]ΒΆ

Wraps _select_managers() to stop CLI execution if no manager are selected.

Return type:

Iterator[PackageManager]

meta_package_manager.specifier moduleΒΆ

Utilities to manage and resolve constraints from a set of package specifiers.

meta_package_manager.specifier.VERSION_SEP: Final = '@'ΒΆ

Separator used by mpm to split package’s ID from its version:

This has been chosen as a separator because it is shared by popular package managers (like npm) and pURLs.

..code-block:

package_id@version
meta_package_manager.specifier.PURL_MAP: dict[str, set[str] | None] = {'alpine': None, 'alpm': {'pacaur', 'pacman', 'paru', 'yay'}, 'android': None, 'apache': None, 'apk': {'apk'}, 'bitbucket': None, 'bitnami': None, 'bower': None, 'buildroot': None, 'cargo': {'cargo'}, 'carthage': None, 'chef': None, 'chocolatey': {'choco'}, 'clojars': None, 'cocoapods': None, 'composer': {'composer'}, 'conan': None, 'conda': None, 'coreos': None, 'cpan': {'cpan'}, 'cran': None, 'crystal': None, 'ctan': None, 'deb': {'apt', 'apt-mint'}, 'docker': None, 'drupal': None, 'dtype': None, 'dub': None, 'ebuild': {'emerge'}, 'eclipse': None, 'elm': None, 'gem': {'gem'}, 'generic': None, 'gitea': None, 'github': None, 'gitlab': None, 'golang': None, 'gradle': None, 'guix': {'guix'}, 'hackage': None, 'haxe': None, 'helm': None, 'hex': None, 'huggingface': None, 'julia': None, 'luarocks': None, 'maven': None, 'melpa': None, 'meteor': None, 'mlflow': None, 'nim': None, 'nix': {'nix'}, 'npm': {'npm', 'pnpm', 'volta', 'yarn', 'yarn-berry'}, 'nuget': None, 'oci': None, 'opam': None, 'openwrt': {'opkg'}, 'osgi': None, 'p2': None, 'pear': None, 'pecl': None, 'perl6': None, 'platformio': None, 'pub': None, 'puppet': None, 'pypi': {'pip', 'pipx', 'uv'}, 'qpkg': None, 'rpm': {'dnf', 'dnf5', 'yum', 'zypper'}, 'rubygems': {'gem'}, 'sourceforge': None, 'sublime': None, 'swid': None, 'terraform': None, 'vagrant': None, 'vim': None, 'wordpress': None, 'yocto': None}ΒΆ

Map pURL’s types to MPM’s manager IDs.

Keys are recognized pURL’s types, and values are the set of MPM’s manager IDs that can handle the package type.

Warning

There is no official list of pkg:<type>/... prefixes defined in the pURL specification.

The only source we found lying around in the pURL literature is this list of diverse aliases, examples and libraries. We use this document to compile the keys of this PURL_MAP mapping.

Todo

Reuse the mapping that is proposed upstream to the package-url Python project.

class meta_package_manager.specifier.Specifier(raw_spec, package_id, manager_id=None, version=None)[source]ΒΆ

Bases: object

Lightweight representation of a package specification.

Contains all parsed metadata to be used as constraints.

raw_spec: strΒΆ

Original, un-parsed specifier string provided by the user.

package_id: strΒΆ

ID is required and is the primary key used for specification.

manager_id: str | None = NoneΒΆ
version: str | None = NoneΒΆ

Version string, a 1:1 copy of the one provided by the user.

classmethod parse_purl(spec_str)[source]ΒΆ

Resolve a pURL into its corresponding manager candidates.

Yields Specifier objects or returns None.

Return type:

tuple[Specifier, ...] | None

classmethod from_string(spec_str)[source]ΒΆ

Parse a string into a package specifier.

Supports various formats: - plain package_id - simple package ID with version: package_id@version - package with multiple version separators: @eslint/json@0.9.0 - pURL: pkg:npm/left-pad@3.7

If a specifier resolves to multiple constraints (as it might be the case for pURL), we produce and returns all variations. That way the Solver below has all necessary details to resolve the constraints.

Returns a tuple of Specifier.

Return type:

tuple[Specifier, ...]

property parsed_version: TokenizedStringΒΆ
exception meta_package_manager.specifier.EmptyReduction[source]ΒΆ

Bases: Exception

Raised by the solver if no constraint can’t be met.

class meta_package_manager.specifier.Solver(spec_strings=None, manager_priority=None)[source]ΒΆ

Bases: object

Combine a set of Specifier and allow for the solving of the constraints they represent.

spec_pool: set[Specifier]ΒΆ
manager_priority: Sequence[str] = ()ΒΆ
populate_from_strings(spec_strings)[source]ΒΆ

Populate the solver with package specifiers parsed from provided strings.

top_priority_manager(keep_managers=None)[source]ΒΆ

Returns the top priority manager configured on the solver.

keep_managers allows for filtering by discarding managers not in that list.

Return type:

str | None

reduce_specs(specs)[source]ΒΆ

Reduce a collection of Specifier to its essential, minimal and unique form.

This method assumes that all provided specs are of the same package (like resolve_package_specs() does).

The reduction process consist of several steps. At each step, as soon as we managed to reduce the constraints to one Specifier, we returns it.

Filtering steps:

  1. We remove all constraints tied to all by the top priority manager if provided.

  2. If no manager priority is provided, we discard constraints not tied to a manager.

  3. We discard constraints not tied to a version.

  4. We only keep constraints tied to the highest version.

If we ends up with more than one set of constraints after all this filtering, an error is raised to invite the developer to troubleshoot the situation and refine this process.

Return type:

Specifier

resolve_package_specs()[source]ΒΆ

Regroup specs of the pool by package IDs, and solve their constraints.

Each package ID yields one reduced spec per distinct target manager. A package therefore produces several specs when the user explicitly names several managers for it (pkg:uv/rich pkg:brew/rich β†’ one spec each). In contrast, a single alias pURL that expands to several managers (pkg:rpm/ping β†’ dnf/yum/zypper) is a set of alternatives, reduced to the top-priority one.

Return type:

Iterator[tuple[str, Specifier]]

resolve_specs_group_by_managers()[source]ΒΆ

Resolves package specs, and returns them grouped by managers.

Return type:

dict[str | None, set[Specifier]]

meta_package_manager.sudo moduleΒΆ

Privilege-escalation machinery for the mutating fan-outs.

This module owns sudo credential priming (prime_sudo()) and its background keepalive (_start_sudo_keepalive()), escalation-policy resolution (_resolved_sudo()), sudo-failure detection (_is_sudo_auth_failure()), and the hidden-prompt stall watchdog (_StallWatchdog). The execution engine (meta_package_manager.execution) consumes the policy pieces to wrap and diagnose escalated commands; the CLI calls prime_sudo() at the top of each mutating subcommand.

Why priming exists: a concurrent state-changing command mutes per-manager output and feeds each child stdin=/dev/null, so a sudo password prompt raised mid-run (by mpm’s own sudo --non-interactive or by a manager that escalates internally, like Homebrew cask) lands invisibly on /dev/tty and can stall the run up to the mutating timeout. Priming first probes the credential cache non-interactively: found warm, it is silently kept alive for the whole run; found cold on a terminal, the managers mpm itself escalates get a single up-front password prompt, naming them and branded [mpm]. Internal escalators never prompt up front: their rare cold-cache escalation is covered by the silent-call stall notice instead, raised while the hidden prompt can still be answered.

Note

Everything in this module is UNIX-only: a Windows run returns early at prime_sudo()’s guard and never arms the watchdog (the internal escalators are macOS-only managers today).

meta_package_manager.sudo.prime_sudo(ctx, managers)[source]ΒΆ

Warm the sudo credential cache, up front, for a mutating fan-out.

Probes the cache non-interactively (sudo --non-interactive --validate) before considering any prompt. A warm cache (pre-authenticated sudo --validate, a NOPASSWD rule, a recent run) is silently kept fresh for the whole invocation by _start_sudo_keepalive(), so every later escalation on the same terminal, mpm’s own sudo --non-interactive as well as a manager’s internal sudo (CLIExecutor.internal_sudo), spends the cache instead of blocking on an invisible prompt inside the concurrent fan-out. Only a cold cache, on an interactive terminal, with managers that mpm itself escalates (_resolved_sudo()), triggers the interactive path: a notice naming the managers and the subcommand, then a single branded sudo password prompt.

Call at the top of each mutating subcommand, before the fan-out draws its spinner. Never prompts when:

  • Windows (no sudo) or the process is already root,

  • no selected manager escalates, through mpm or internally,

  • a dry run or a plan run (no state-changing CLI is executed),

  • already primed once this invocation (idempotent),

  • the sudo executable is missing (one warning is logged),

  • the probe finds the cache already warm (keepalive only, fully silent),

  • no interactive terminal is available: one warning names the managers mpm escalates and leaves them to fail fast rather than block on a prompt no one can answer, while an internal-only selection stays silent, or

  • only internal escalators are selected on a cold cache: most such runs never escalate, so the rare mid-run prompt is covered by the silent-call stall notice instead.

Return type:

None

meta_package_manager.summary moduleΒΆ

End-of-run summary printing for mpm subcommands.

Every long-running subcommand (installed, outdated, search, dump, sbom) closes with a one-line summary written to stderr:

223 packages total (brew: 223).

Plus optional follow-up lines specific to that subcommand (the SBOM writer surfaces upstream-document merge counts and dependency-graph edge counts here). The whole summary is gated by the global --summary/--no-summary flag and respects the user’s choice across every subcommand uniformly.

Vocabulary note: β€œsummary” describes the rendered text that lands on stderr. β€œStats” describes the raw numbers fed into it (meta_package_manager.sbom.base.SBOM.stats() returns a dict of counts). The two terms stay distinct deliberately: the flag/module/function name reflects what the user sees; the data-side method keeps the unambiguous stats name.

This module is the single home of the summary contract:

The renderer stays in this module rather than scattered across each subcommand so the visual format is unique and obvious to find. The adapters live here too because their job is to translate subcommand-native shapes into the print contract, which is also summary-domain logic.

meta_package_manager.summary.print_summary(counts, notes=())[source]ΒΆ

Print a one-line per-category count to stderr, plus optional follow-up notes.

counts is a collections.Counter keyed by an opaque category label. The label is usually a package manager id, but the dump --brewfile subcommand uses Brewfile entry types and any future caller is free to use whatever bucket makes sense. The parameter is named counts rather than manager_stats to avoid lying about the key’s meaning.

Prints something like:

10 packages total (brew: 2, pip: 2, gem: 2, vscode: 2, npm: 2, composer: 0).

notes is an iterable of follow-up lines printed verbatim under the count line. mpm sbom uses it to surface facts that don’t fit the per-category-Counter shape: number of upstream SBOM documents merged into the aggregate, enrichment ratios, dependency-graph edge counts. Other subcommands today pass no notes; the count line is enough.

Always writes to stderr so the call site is free to pipe stdout elsewhere (a generated SBOM document, a TOML manifest, a Brewfile) without the summary polluting the output. Gated upstream by the global --summary/--no-summary flag; this function itself is unconditional once called.

Return type:

None

meta_package_manager.summary.package_counts(payload)[source]ΒΆ

Build a per-manager Counter from a typical subcommand payload.

installed, outdated, and search all stash their results in a {manager_id: {"packages": [...]}} dict. This helper turns that into the count-by-manager-id Counter that print_summary() accepts, eliminating the Counter({k: len(v["packages"]) for k, v in payload.items()}) boilerplate that appeared verbatim at three CLI call sites.

Mismatched payloads (an extractor that stashes packages under a different key, the dump --brewfile line-counter pass) build their Counter inline rather than wedging this helper into serving every shape.

Return type:

Counter[str]

meta_package_manager.summary.sbom_summary(sbom, bundled)[source]ΒΆ

Adapt meta_package_manager.sbom.base.SBOM.stats() to the print_summary() shape.

SBOM stats live on the renderer because the renderer knows what actually landed in the document (after dedup, after merge). This adapter flattens that structured dict into the count-line + follow-up-notes shape print_summary() consumes, conditioning each note on what the run actually did so --minimal scans, casks-only runs, and formats without a merge concept all stay tidy.

The function lives in this module (rather than next to the SBOM renderers) because its job is translating between two different data shapes: SBOM stats on one side, the print contract on the other. Summary-domain glue, not SBOM-domain logic.

Return type:

tuple[Counter, list[str]]

meta_package_manager.tables moduleΒΆ

Table-output vocabulary and rendering plumbing shared by the subcommands.

The mpm subcommands render heterogeneous tables (different columns per command) but share the same output machinery. This module owns all of it:

  • SortableField, the vocabulary of the global mpm --sort-by selector. The selector itself is click-extra’s field-vocabulary SortByOption, and the per-table resolution (sort by the selected fields the table carries, keep the original row order when it carries none) happens inside click_extra.table.print_table(), from the field each header pairs with its column in the registries below.

  • The per-command column registries, each pairing a click-extra ColumnSpec (whose ID addresses the column from --columns) with the SortableField the column carries (None for a column that cannot drive the sort). A registry is the single source of truth for its command: the same tuple feeds the @columns_option declaration (which validates the user selection) and print_projected_table() (which projects headers and rows before rendering).

  • print_projected_table() and print_serialized_and_exit(), the human-friendly and machine-friendly rendering paths every table-producing subcommand goes through.

Note

The registry pairs’ second element is annotated str | None rather than SortableField | None: on Python 3.10, SortableField extends backports.strenum.StrEnum, whose stubs type the members as plain str, so the tighter annotation only checks under 3.11+. StrEnum members being str subclasses, the wider annotation is accurate on every supported version.

class meta_package_manager.tables.SortableField(*values)[source]ΒΆ

Bases: StrEnum

Fields IDs allowed to be sorted.

MANAGER_ID = 'manager_id'ΒΆ
MANAGER_NAME = 'manager_name'ΒΆ
PACKAGE_ID = 'package_id'ΒΆ
PACKAGE_NAME = 'package_name'ΒΆ
VERSION = 'version'ΒΆ
meta_package_manager.tables.MANAGERS_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='manager_id', label='Manager ID', description="Manager's identifier."), SortableField.MANAGER_ID), (ColumnSpec(id='manager_name', label='Name', description="Manager's common name."), SortableField.MANAGER_NAME), (ColumnSpec(id='supported', label='Supported', description='Support status on the current platform.'), None), (ColumnSpec(id='cli', label='CLI', description="Location of the manager's binary on the system."), None), (ColumnSpec(id='executable', label='Executable', description='Whether the binary is executable.'), None), (ColumnSpec(id='version', label='Version', description="Manager's self-reported version, and the unsatisfied requirement when stale."), SortableField.VERSION))ΒΆ

Columns of the mpm managers table.

meta_package_manager.tables.INSTALLED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.'), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.'), SortableField.VERSION))ΒΆ

Columns of the mpm installed table.

meta_package_manager.tables.OUTDATED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.'), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.'), SortableField.VERSION), (ColumnSpec(id='latest_version', label='Latest version', description='Version available for upgrade.'), None))ΒΆ

Columns of the mpm outdated table.

meta_package_manager.tables.SEARCH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the match.'), SortableField.MANAGER_ID), (ColumnSpec(id='latest_version', label='Latest version', description='Latest version available.'), SortableField.VERSION), (ColumnSpec(id='description', label='Description', description='Package description, for managers that provide one. Out of the default selection: select it explicitly or pass --description.'), None))ΒΆ

Columns of the mpm search table.

The description column exists in the registry (so --columns can select it) but stays out of the default selection unless --description (or --extended, which searches descriptions) is passed.

meta_package_manager.tables.WHICH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='manager_id', label='Manager ID', description='Manager whose search path found the binary.'), SortableField.MANAGER_ID), (ColumnSpec(id='priority', label='Priority', description="Rank of the match in the manager's search path."), None), (ColumnSpec(id='cli_path', label='CLI path', description='Location of the matched binary.'), None), (ColumnSpec(id='symlink', label='Symlink destination', description='Resolved target when the match is a symlink.'), None))ΒΆ

Columns of the mpm which table.

meta_package_manager.tables.column_specs(columns)[source]ΒΆ

Extract the bare ColumnSpec tuple from a column registry.

Return type:

tuple[ColumnSpec, ...]

meta_package_manager.tables.print_projected_table(ctx, columns, rows, default_ids=None)[source]ΒΆ

Render dict rows as a table projected through --columns.

The --columns selection restricts and reorders the rendering, SQL-SELECT-style; click-extra’s ColumnsOption already validated it against the same columns registry, so unknown IDs never reach this point. default_ids is the selection applied when the user passed none (search uses it to hide the description column unless --description); None keeps every column in canonical order.

Sorting stays on mpm’s global --sort-by: each header pairs its label with the sortable field the column carries, and click-extra’s print_table() reads the selection (with the --table-format one) from the shared context meta and resolves it per table. A sort field whose column is projected out is simply skipped, and a table carrying none of the selected fields keeps its original row order.

Return type:

None

meta_package_manager.tables.print_serialized_and_exit(ctx, data)[source]ΒΆ

Render data in the active serialization format, then exit.

When the global --table-format resolves to one of the structured serialization formats (JSON, YAML, TOML, XML, …), serialize data under the shared mpm root element and stop the program. Otherwise return, so the caller falls through to its human-friendly table rendering.

Return type:

None

meta_package_manager.version moduleΒΆ

Helpers and utilities to parse and compare version numbers.

mpm wraps dozens of package managers, each with its own versioning scheme: semver, PEP 440, calendar versioning, Debian epochs, Gentoo suffixes, and others. Rather than implementing format-specific parsers, this module provides a universal tokenizer that produces good-enough ordering across all of them.

DesignΒΆ

The tokenizer splits version strings into alternating digit and letter tokens at every digit/letter boundary and every non-alphanumeric separator. Tokens that parse as integers are compared numerically; the rest are compared as lowercase strings. This gives natural sort order where (2019, 0, 1) > (9, 3) β€” something neither pure-string nor pure-numeric comparison achieves.

Key rules:

  • Epochs dominate. A leading integer joined by : (Debian, RPM, pacman) or ! (PEP 440) is an epoch: a version-space reset that outranks the rest of the string. 2:1.0 > 9.0 and 1!1.0 > 2.0 because epoch 2/1 beats the implicit epoch 0. Versions without an epoch default to 0, so they compare unchanged.

  • Integers outrank strings. A numeric token always sorts higher than a string token at the same position. This makes 3.12.0 > 3.12.0a4 (release beats alpha) and 0.1 > 0.beta2 work without understanding PEP 440 or semver pre-release semantics.

  • Trailing zeros are padding. 6.2 and 6.2.0 compare equal. When one token tuple is a prefix of the other and all extra tokens are zero integers, the versions are equivalent.

  • Pre-release suffixes lose. When a release version is a prefix of a longer version whose first significant extra token is a string (e.g., "alpha", "git"), the shorter release is considered greater.

  • Hex hashes stay whole. A contiguous run of 7+ hex characters with interleaved digits and letters (at least one letter-then-digit and one digit-then-letter adjacency) is kept as a single opaque token. Without this, g6cd4c31 would shatter into ("g", 6, "cd", 4, "c", 31). The 7-character floor matches git’s default abbreviated hash length (core.abbrev, the de facto standard on GitHub/GitLab/Bitbucket). The interleaving requirement rejects coincidental hex strings like asciified Unicode (eeaccee231), that have only one transition direction.

  • Digit/letter splitting is essential. Splitting ubuntu1 into ("ubuntu", 1) enables natural numeric ordering of embedded version numbers: a4 < a10 compares correctly because 4 and 10 become integer tokens. Without this split, "a4" > "a10" lexicographically.

LimitationsΒΆ

This is a heuristic comparator, not a format-specific parser.

  • PEP 440 ordering is richer than what we implement. .devN ordering relative to pre-releases is not handled. Use packaging.version for strict PEP 440 compliance. Epochs (1!) are handled β€” see the epoch rule above.

  • Perl floating-point versions (1.1 == 1.10) are treated as (1, 1) vs (1, 10) β€” not equal. The Gentoo three-digit-group conversion scheme is not implemented.

  • Format-specific separators like Java build metadata (,) or Perl-style floats (.) are treated as plain delimiters, which can produce wrong comparison results when the separator carries structural meaning. The epoch separators : and ! are recognized.

ReferencesΒΆ

  • PEP 440 β€” Python’s version identification spec. Defines a/b/rc suffix ordering that our integer-outranks-string rule approximates.

  • Falsehoods about versions β€” 25 assumptions that break in practice. Validates our approach of not assuming any single format (falsehoods 4, 8, 13) and handling mixed numeric/string tokens (falsehoods 2, 3).

  • Gentoo Perl version scheme β€” illustrates how two incompatible formats (dotted-decimal and floating-point) require careful mapping. A reminder that version comparison cannot be reduced to β€œsplit on dots, compare integers.”

  • univers β€” scheme-aware version parsing and comparison (PEP 440, semver, Debian, RPM, Gentoo ebuild, and more) plus the vers range spec, from the same AboutCode team maintaining purl. The reference implementation to evaluate if this heuristic comparator ever needs per-scheme accuracy.

meta_package_manager.version.ALNUM_EXTRACTOR_CI = re.compile('(\n    (?= [0-9a-f]* [a-f] [0-9] )\n    (?= [0-9a-f]* [0-9] [a-f] )\n    [0-9a-f]{7,}\n    | \\d+\n    | [a-z]+\n)', re.IGNORECASE|re.VERBOSE)ΒΆ

Case-insensitive variant used to split the original string and preserve case.

meta_package_manager.version.TOKEN_ALIASES: dict[str, str] = {'alpha': 'a', 'beta': 'b', 'c': 'rc', 'preview': 'rc'}ΒΆ

Canonical short forms for pre-release tag spellings.

PEP 440 defines alpha/a, beta/b, and c/rc/preview as equivalent aliases. These appear across ecosystems: Debian uses ~alpha, npm uses -alpha, Homebrew uses alpha/beta. The long forms are always interchangeable with the short forms, so normalizing at tokenization time is safe. Normalization only affects comparison tokens, not the original string or pretty_print() output.

meta_package_manager.version.POST_RELEASE_TAGS: frozenset[str] = frozenset({'patch', 'post'})ΒΆ

Suffixes that indicate a version newer than the base release.

PEP 440 defines .postN as a post-release. patch carries the same semantics in some ecosystems (e.g., 1.0-patch1). Without this set, the prefix-comparison rule treats all string suffixes as pre-release indicators, which wrongly makes 1.0 > 1.0.post1.

This set is deliberately small. Only tags with unambiguous β€œnewer than release” semantics across multiple ecosystems belong here. Candidates like rev or p are excluded because they can also mean β€œrevision” (Gentoo -r0) or β€œpre-release patchlevel” (FreeBSD p1), depending on context.

class meta_package_manager.version.Token(value)[source]ΒΆ

Bases: object

A normalized word, persisting its lossless integer variant.

Supports natural comparison with str and int types. Used to compare versions and package IDs.

Instantiates a Token from an alphanumeric string or a non-negative integer.

static str_to_int(value)[source]ΒΆ

Convert a str or an int to a (string, integer) couple.

Returns together the original string and its integer representation if conversion is successful and lossless. Else, returns the original value and None.

Return type:

tuple[str, int | None]

string: strΒΆ
integer: int | None = NoneΒΆ
property isint: boolΒΆ

Does the Token got an equivalent pure integer representation?

class meta_package_manager.version.TokenizedString(value)[source]ΒΆ

Bases: object

Tokenize a string for user-friendly sorting.

Essentially a wrapper around a list of Token instances.

Parse and tokenize the provided raw value.

string: strΒΆ
tokens: tuple[Token, ...] = ()ΒΆ
separators: tuple[str, ...] = ()ΒΆ
original_segments: tuple[str, ...] = ()ΒΆ

Original-case token strings for lossless pretty_print().

epoch: int = 0ΒΆ

Leading epoch (N: or N!); dominates comparison, 0 when absent.

release: tuple[Token, ...] = ()ΒΆ

Comparison tokens with the epoch removed. See _split_epoch().

pretty_print()[source]ΒΆ

Reconstruct the tokenized string using original-case segments and separators.

Return type:

str

static tokenize(string)[source]ΒΆ

Tokenize a string: ignore case and split at each non-alphanumeric characters.

Returns a tuple of Token instances, separator strings between consecutive tokens, and original-case segment strings for lossless display.

re.split() with a capturing group alternates non-matching segments (even indices) and captured matches (odd indices):

ALNUM_EXTRACTOR.split("4.2.1-5666.3")
['', '4', '.', '2', '.', '1', '-', '5666', '.', '3', '']
 pre   m   sep   m   sep   m   sep    m     sep   m   suf
Return type:

tuple[tuple[Token, ...], tuple[str, ...], tuple[str, ...]]

meta_package_manager.version.parse_versionΒΆ

Alias for TokenizedString used in version-comparison contexts.

meta_package_manager.version.OPERATOR_MAP: dict[str, Callable[[TokenizedString, TokenizedString], bool]] = {'!=': <built-in function ne>, '<': <built-in function lt>, '<=': <built-in function le>, '==': <built-in function eq>, '>': <built-in function gt>, '>=': <built-in function ge>}ΒΆ

Comparison operators recognized in a version range, mapped to their callable.

meta_package_manager.version.RANGE_OPERATOR = re.compile('(?P<op>>=|<=|==|!=|>|<)\\s*(?P<version>.+)')ΒΆ

Matches a comparison operator prefix followed by a version string.

class meta_package_manager.version.VersionRange(spec)[source]ΒΆ

Bases: object

A set of version constraints parsed from a comma-separated specifier string.

Each constraint is an (operator, version) pair. A version satisfies the range only if it satisfies every constraint.

Bare version strings (no operator prefix) are treated as >=.

meta_package_manager.version.is_version(string)[source]ΒΆ

Returns True if the string looks like a version.

Heuristics: at least one token is an integer, or there is only one non-integer token.

Return type:

bool

meta_package_manager.version.diff_versions(old, new, prefix_fg='bright_black', old_fg='red', new_fg='green')[source]ΒΆ

Color the common prefix gray, the old suffix red, the new suffix green.

The split point snaps to the nearest separator boundary so the full diverging token and its preceding separator are highlighted. For 2.1.1774638290 vs 2.1.1774896198, the common part is 2.1 and the diff includes .1774638290 / .1774896198.

prefix_fg, old_fg and new_fg override the common-prefix, old-suffix and new-suffix colors, in any form accepted by click_extra.style() (a named ANSI color or an xterm-256 palette index). Renderers whose consumer maps the named defaults poorly, like the bar plugin on a light translucent menu, pass their own.

Return type:

tuple[str, str]