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.

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.

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.

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 dark_mode: bool

Detect dark mode by inspecting environment variables.

Value is sourced from two environment variables depending on the plugin:

  • OS_APPEARANCE for SwiftBar

  • XBARDarkMode for XBar

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 all print statements.

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.

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

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'
search = 'search'
install = 'install'
upgrade = 'upgrade'
upgrade_all = 'upgrade_all'
remove = 'remove'
sync = 'sync'
cleanup = 'cleanup'
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.

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.

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.

Defines the Click command group and its subcommands. Each operation subcommand (installed, outdated, install, upgrade, remove, …) 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.

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

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.is_stdout(filepath)[source]

Check if a file path is set to stdout.

Prevents the creation of a - file in the current directory.

Return type:

bool

meta_package_manager.cli.prep_path(filepath)[source]

Prepare the output file parameter for Click’s echo function.

Return type:

IO | None

meta_package_manager.cli.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.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.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. 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.

Return type:

bool

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.config module

Configuration utilities for mpm.

Hosts the schema of the [mpm] configuration section consumed by click_extra and the per-manager attribute override mechanism driven by [mpm.managers.<id>] sections of the same configuration file.

The override mechanism keeps the pool and the configuration concerns separate: meta_package_manager.pool.ManagerPool owns the live manager instances and the per-manager overridden_fields tracking dict, while this module owns the schema (which fields are overridable, how to coerce values) and the application logic. The pool is mutated through the apply_manager_overrides() helper, keeping all configuration policy out of meta_package_manager.pool.

class meta_package_manager.config.MpmConfig(all_managers=False, ignore_auto_updates=True, stop_on_error=False, dry_run=False, timeout=500, cooldown='', require_cooldown_support=True, description=False, sort_by=<factory>, stats=True, 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.) and click-extra built-in options (verbosity, table_format) are handled by the default_map pipeline and do not appear here.

all_managers: bool = False

Force evaluation of all managers, including unsupported and deprecated.

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.

timeout: int = 500

Maximum duration in seconds for each manager CLI call.

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.

stats: bool = True

Print per-manager package statistics.

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.OVERRIDABLE_FIELDS: Final[Mapping[str, Callable[[Any], Any]]] = {'cli_names': <function _to_str_tuple>, 'cli_search_path': <function _to_str_tuple>, 'deprecated': <function _to_bool>, 'dry_run': <function _to_bool>, 'extra_env': <function _to_str_dict>, 'ignore_auto_updates': <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>, 'timeout': <function _to_int>, '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.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.

Raises:

click_extra.ValidationError – when section is not a mapping, references an unknown manager ID, sets an unknown override field, or provides a value of the wrong type for a known field. 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.duration module

Parsing of release-age durations for the mpm --cooldown option.

Defines Duration, the click_extra.ParamType that turns a friendly duration, an ISO 8601 duration, or an RFC 3339 timestamp into a datetime.timedelta.

class meta_package_manager.duration.Duration[source]

Bases: ParamType

Parse a cooldown spec into a datetime.timedelta.

Accepts three input shapes:

  • Friendly duration: 7 days, 1 week, 12h, 30m, 45s, or a bare number of days like 7.

  • ISO 8601 duration: P7D, PT12H, P1WT6H. Case-insensitive.

  • RFC 3339 absolute timestamp: 2024-05-01T00:00:00Z or with an offset like +02:00. Converted at parse time to now - timestamp; a timestamp in the future disables the cooldown.

A zero duration or empty input parses to None, which disables the cooldown (handy to override a value set in the configuration file).

Note

Durations resolve to a fixed number of seconds, assuming a day is 24 hours. The local time zone, DST transitions, and calendar boundaries are ignored. Calendar units (months, years) are rejected for the same reason: 28-31 days and 365-366 days make them unsuitable for a precise release-age cutoff. Use days or weeks instead.

name: str = 'duration'

the descriptive name of this type

convert(value, param, ctx)[source]

Coerce value to a datetime.timedelta (or None).

Return type:

timedelta | None

meta_package_manager.execution module

CLI-execution engine shared by every package manager.

Two altitudes live here. The lower one 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.

The higher one schedules many managers at once: the concurrent fan-out primitives meta_package_manager.execution.collect_from_managers() and meta_package_manager.execution.collect_per_package(), the meta_package_manager.execution.effective_jobs() policy that sizes them, the up-front meta_package_manager.execution.warm_availability() probe, and the shared / ledger (meta_package_manager.execution.OperationTrail and the meta_package_manager.execution.trail_line() atom) that the concurrent and sequential paths both report through.

Note

The name and intent mirror click_extra.execution from the sibling click-extra project, which gathers options that govern how a CLI runs (parallelism, timing, exit code). Co-locating the cross-manager scheduling here realizes that alignment: mpm --jobs and the fan-out it drives now sit beside the per-call timeout and spinner they build upon.

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.

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

Highlight the binary name in the provided path.

If match_names is provided, only highlight the start of the binary name that is in the list.

Matching is insensitive to case on Windows and case-sensitive on other platforms, thanks to os.path.normcase.

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, 'install': 500, 'installed': 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.

meta_package_manager.execution.format_cli_prompt(cmd_args, extra_env=None)[source]

Render the shell prompt simulating a CLI invocation, for logs and dry-runs.

Prefixes the PROMPT to any extra_env assignments and the command line, both styled through the active theme. Reimplements the helper click-extra made private (_format_cli_prompt) in 8.0.0.

Return type:

str

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.manager.PackageManager.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.manager.PackageManager.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.manager.PackageManager.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.manager.PackageManager.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_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.manager.PackageManager.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.

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.

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.

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

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.manager.PackageManager.executable() method 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?

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:
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] <cli_path> <pre_args> <*args> <post_args>
  • self.pre_cmds is added before the CLI path.

  • self.cli_path is used as the main binary to execute.

  • self.pre_args and self.post_args globals are added before and after the provided *args.

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 linux, the command can be run with sudo if the parameter of the same name is set to True. In which case the override_pre_cmds parameter is not allowed to be set and the auto_pre_cmds parameter is forced to False.

Return type:

tuple[str, …]

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.manager.PackageManager.build_cli() method, it is executed with the meta_package_manager.manager.PackageManager.run() method, augmented with environment variables from self.extra_env.

All parameters are the same as meta_package_manager.manager.PackageManager.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 and mpm --stop-on-error options to force the execution and completion of the command.

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

Return type:

str

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

Managers that contend for one shared OS-level package lock, grouped by backend.

Observed while making operations concurrent (see collect_from_managers() and collect_per_package()). 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).

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

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

Conclusion: concurrency is safe across families and unsafe within one, just as it is unsafe within a single manager (which is why collect_per_package() keeps one manager’s own packages serial). When two members of a family are co-installed and run at once, the OS lock makes them block or fail, never corrupt: the read and maintenance commands are best-effort and self-heal on re-run, but the action commands (install/remove/upgrade) fail loud, so a lost race surfaces as an error. mpm --jobs 1 serializes everything for anyone who hits it.

Note

Not yet enforced. This is the seed for a future scheduler that would serialize within a family while still parallelizing across families, instead of leaning on the OS lock plus --jobs. Re-verify membership and the exact lock paths before wiring it in (a host rarely carries two members of the same family, which is why the OS-lock fallback has sufficed so far).

meta_package_manager.execution.effective_jobs(ctx, count)[source]

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

Returns the number of managers to process in parallel; 1 means run sequentially in the calling thread. Collapses to sequential when:

  • there is no active CLI context (programmatic or test use),

  • a single item leaves nothing to parallelize,

  • the user passed mpm --jobs 1, or

  • the effective verbosity is DEBUG (whether from --verbosity or the -v/-q counters), where coherent per-manager log narration matters more than the speed-up (interleaved threads would scramble it).

Otherwise the mpm --jobs value wins, capped at count: there is no point spinning up more workers than there are items.

Return type:

int

meta_package_manager.execution.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

meta_package_manager.execution.trail_glyph(ok)[source]

Return the themed or glyph for a trail line or finisher.

Return type:

str

meta_package_manager.execution.trail_line(ok, message)[source]

Format one / trail line: a status glyph followed by message.

Return type:

str

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

Bases: object

A / progress trail and finisher for a batch of operations.

The single report surface for every fan-out command, rendered one of two ways depending on concurrency:

  • sequential (jobs <= 1): echo each outcome between the managers’ own per-call spinners, with no aggregate spinner. The ordering-bound state changers (install/remove/upgrade <packages>/restore) drive this directly, since they chain managers by priority (a hit in the first manager skips the rest) and so cannot fan out; it is also every dispatch() fallback at mpm --jobs 1 or DEBUG verbosity.

  • concurrent (jobs > 1): suppress the per-manager spinners (which would collide on stderr) and drive one aggregate spinner, buffering outcomes until it first draws, then streaming the rest live.

Both are gated on --progress (folded into each manager’s progress) plus an interactive stderr, so pipes, CI, serialized and DEBUG runs stay silent. A read command whose result table is the real output stays silent in sequential mode too (coverage=True): the per-call spinners already narrate progress, so the trail would be noise. The running /total tally is kept as outcomes land, so a caller computes no counts of its own.

Thread-safe: mark() may be called from worker threads. Use it as a context manager whenever it may run concurrently, to bound the aggregate spinner’s life; a purely sequential caller (install’s priority search) may construct it bare.

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 spinner (“Searching”).

  • done_label (str) – past-tense verb for the finisher (“Searched”).

  • unit (str) – the noun counted in the spinner and finisher (“managers”, “packages”).

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

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

property ok_count: int

How many marked outcomes have succeeded so far.

mark(ok, message)[source]

Record one / outcome: tally it and render its trail line.

Return type:

None

finish(ok, summary)[source]

Render the persistent / {summary} finisher.

Return type:

None

meta_package_manager.execution.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 manager 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 (they serialize on its lock, see SHARED_LOCK_FAMILIES).

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 spinner 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.execution.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 single-unit dispatch() lane whose unit runs work and stashes the (id, data) result in input position, so the returned list mirrors managers regardless of completion order.

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 and keeps the trail in the sequential fallback. 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.execution.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 per-manager lanes — so a manager’s own packages stay serial while 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.execution.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.labels module

Utilities to generate extra labels to use for GitHub issues and PRs.

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', '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', '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-mint', 'asdf', 'brew', 'cargo', 'cask', 'choco', 'composer', 'cpan', 'deb-get', 'dnf', 'dnf5', 'emerge', 'eopkg', 'flatpak', 'fwupd', 'gem', 'guix', 'macports', 'mas', 'mise', 'mpm', 'nix', 'npm', 'opkg', 'pacaur', 'pacman', 'pacstall', 'paru', 'pip', 'pipx', 'pkg', 'pnpm', 'ports', 'pwsh-gallery', 'scoop', 'sdkman', 'sfsu', 'snap', 'steamcmd', 'stew', 'topgrade', 'uv', 'uvx', '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-mint': '📦 manager: dpkg-based', 'asdf': '📦 manager: asdf', 'brew': '📦 manager: homebrew', 'cargo': '📦 manager: cargo', 'cask': '📦 manager: homebrew', 'choco': '📦 manager: choco', 'composer': '📦 manager: composer', 'cpan': '📦 manager: cpan', 'deb-get': '📦 manager: dpkg-based', 'dnf': '📦 manager: rpm-based', 'dnf5': '📦 manager: rpm-based', 'emerge': '📦 manager: emerge', 'eopkg': '📦 manager: eopkg', 'flatpak': '📦 manager: flatpak', 'fwupd': '📦 manager: fwupd', 'gem': '📦 manager: gem', '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', 'pkg': '📦 manager: pkg-based', 'pnpm': '📦 manager: npm-based', 'ports': '📦 manager: pkg-based', 'pwsh-gallery': '📦 manager: pwsh-gallery', 'scoop': '📦 manager: scoop-based', 'sdkman': '📦 manager: sdkman', 'sfsu': '📦 manager: scoop-based', 'snap': '📦 manager: snap', 'steamcmd': '📦 manager: steamcmd', 'stew': '📦 manager: stew', 'topgrade': '📦 manager: topgrade', 'uv': '📦 manager: uv-based', 'uvx': '📦 manager: uv-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', 'Alpine Linux': '🖥 platform: Linux', 'Amazon Linux': '🖥 platform: Linux', 'Android': '🖥 platform: Linux', 'Arch Linux': '🖥 platform: Linux', 'Buildroot': '🖥 platform: Linux', 'CachyOS': '🖥 platform: Linux', 'CentOS': '🖥 platform: Linux', 'CloudLinux OS': '🖥 platform: Linux', 'Cygwin': '🖥 platform: Unix', 'Debian': '🖥 platform: Linux', 'DragonFly BSD': '🖥 platform: BSD', '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', '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', 'Solaris': '🖥 platform: Unix', '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', 'openSUSE Tumbleweed': '🖥 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: asdf', '#bfdadc', 'asdf'), ('📦 manager: cargo', '#bfdadc', 'cargo'), ('📦 manager: choco', '#bfdadc', 'choco'), ('📦 manager: composer', '#bfdadc', 'composer'), ('📦 manager: cpan', '#bfdadc', 'cpan'), ('📦 manager: dpkg-based', '#bfdadc', 'apt, apt-mint, deb-get, opkg, pacstall'), ('📦 manager: emerge', '#bfdadc', 'emerge'), ('📦 manager: eopkg', '#bfdadc', 'eopkg'), ('📦 manager: flatpak', '#bfdadc', 'flatpak'), ('📦 manager: fwupd', '#bfdadc', 'fwupd'), ('📦 manager: gem', '#bfdadc', 'gem'), ('📦 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, yarn, yarn-berry'), ('📦 manager: pacman-based', '#bfdadc', 'pacaur, pacman, paru, yay'), ('📦 manager: pip-based', '#bfdadc', 'pip, pipx'), ('📦 manager: pkg-based', '#bfdadc', 'pkg, ports'), ('📦 manager: pwsh-gallery', '#bfdadc', 'pwsh-gallery'), ('📦 manager: rpm-based', '#bfdadc', 'dnf, dnf5, yum, zypper'), ('📦 manager: scoop-based', '#bfdadc', 'scoop, sfsu'), ('📦 manager: sdkman', '#bfdadc', 'sdkman'), ('📦 manager: snap', '#bfdadc', 'snap'), ('📦 manager: steamcmd', '#bfdadc', 'steamcmd'), ('📦 manager: stew', '#bfdadc', 'stew'), ('📦 manager: topgrade', '#bfdadc', 'topgrade'), ('📦 manager: uv-based', '#bfdadc', 'uv, uvx'), ('📦 manager: vscode-based', '#bfdadc', 'vscode, vscodium'), ('📦 manager: winget', '#bfdadc', 'winget'), ('📦 manager: xbps', '#bfdadc', 'xbps'), ('🔌 bar-plugin', '#fef2c0', 'Xbar/SwiftBar plugin code, documentation and features'), ('🖥 platform: BSD', '#bfd4f2', 'DragonFly BSD, FreeBSD, MidnightBSD, NetBSD, OpenBSD, SunOS'), ('🖥 platform: Linux', '#bfd4f2', '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.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, deprecation 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.

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.

deprecated: bool = False

A manager marked as deprecated is hidden from package selection by default.

You can still use it by explicitly calling for it on the command line.

A deprecated manager 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. Every deprecation must be documented through deprecation_url.

Deprecated managers are kept out of the functional and integration test matrices, so an unreliable or flaky deprecated manager never blocks a release. 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.

deprecation_url: str | None = None

Announcement from the official project, or evidence of abandonment of maintenance.

Required for every manager whose deprecated flag is set, and only meaningful on such managers. Enforced by test_deprecated.

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.

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

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

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.

classmethod query_parts(query)[source]

Returns a set of all contiguous alphanumeric string segments.

Thin delegator to meta_package_manager.package.Package.query_parts(), the canonical tokenizer, kept here as a convenience for manager-side search code.

Return type:

set[str]

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

sync()[source]

Refresh package metadata from remote repositories.

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

Return type:

None

cleanup()[source]

Prune left-overs, remove orphaned dependencies and clear caches.

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

Return type:

None

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. meta_package_manager.manager.PackageManager.query_parts() delegates here.

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.cargo.Cargo'>, <class 'meta_package_manager.managers.homebrew.Cask'>, <class 'meta_package_manager.managers.chocolatey.Choco'>, <class 'meta_package_manager.managers.composer.Composer'>, <class 'meta_package_manager.managers.cpan.CPAN'>, <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.macports.MacPorts'>, <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.opkg.OPKG'>, <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.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.steamcmd.SteamCMD'>, <class 'meta_package_manager.managers.stew.Stew'>, <class 'meta_package_manager.managers.topgrade.Topgrade'>, <class 'meta_package_manager.managers.uv.UV'>, <class 'meta_package_manager.managers.uv.UVX'>, <class 'meta_package_manager.managers.vscode.VSCode'>, <class 'meta_package_manager.managers.vscode.VSCodium'>, <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.zerobrew.ZeroBrew'>, <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', 'progress', 'require_cooldown_support', 'stop_on_error', '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.

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]
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 deprecated.

property default_manager_ids: tuple[str, ...]

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

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.sorting module

Field-aware sorting of the multi-manager result tables.

The mpm subcommands render heterogeneous tables (different columns per command) but share one global mpm --sort-by selector. This module owns the vocabulary of sortable fields (SortableField) and the row-sort key builder (print_sorted_table()) that maps the selected fields onto whichever columns a given table happens to carry.

class meta_package_manager.sorting.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.sorting.print_sorted_table(headers, table, sort_by, *, table_format=None)[source]

Render table with click-extra, sorting rows by the sort_by columns.

Each headers entry pairs a column label with the SortableField it carries, or None for a column that cannot be sorted on. sort_by lists the fields to order by, in priority order: rows sort by the first field this table carries, then each subsequent field as a tie-breaker, with any remaining columns breaking further ties in their natural left-to-right order. Fields the table does not carry are skipped; a sort_by matching no column leaves the rows in their original order.

Reimplements the print_sorted_table helper click-extra dropped in 8.0.0, where click_extra.table.SortByOption instead bakes the sort key into ctx.print_table. That option derives its choices from a single command’s columns, whereas mpm shares one global mpm --sort-by across subcommands with heterogeneous tables, so the key is built here.

Return type:

None

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', '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/pingdnf/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.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.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.”

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

Return type:

tuple[str, str]