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¶
- meta_package_manager.managers package
- Submodules
- meta_package_manager.managers.apk module
- meta_package_manager.managers.apm module
- meta_package_manager.managers.apt module
- meta_package_manager.managers.asdf module
- meta_package_manager.managers.cargo module
- meta_package_manager.managers.chocolatey module
- meta_package_manager.managers.composer module
ComposerComposer.nameComposer.homepage_urlComposer.platformsComposer.requirementComposer.pre_argsComposer.version_regexesComposer.installedComposer.outdatedComposer.search()Composer.install()Composer.upgrade_all_cli()Composer.upgrade_one_cli()Composer.remove()Composer.cleanup()Composer.cli_namesComposer.idComposer.virtual
- meta_package_manager.managers.cpan module
- meta_package_manager.managers.dnf module
- meta_package_manager.managers.emerge module
EmergeEmerge.nameEmerge.homepage_urlEmerge.platformsEmerge.requirementEmerge.pre_argsEmerge.version_regexesEmerge.installedEmerge.outdatedEmerge.search()Emerge.install()Emerge.upgrade_all_cli()Emerge.upgrade_one_cli()Emerge.remove()Emerge.sync()Emerge.cleanup()Emerge.cli_namesEmerge.idEmerge.virtual
- meta_package_manager.managers.eopkg module
- meta_package_manager.managers.flatpak module
FlatpakFlatpak.homepage_urlFlatpak.brewfile_entry_typeFlatpak.platformsFlatpak.requirementFlatpak.version_regexesFlatpak.installedFlatpak.outdatedFlatpak.search()Flatpak.install()Flatpak.upgrade_all_cli()Flatpak.upgrade_one_cli()Flatpak.cli_namesFlatpak.idFlatpak.nameFlatpak.remove()Flatpak.virtualFlatpak.cleanup()
- meta_package_manager.managers.fwupd module
- meta_package_manager.managers.gem module
- meta_package_manager.managers.guix module
- meta_package_manager.managers.homebrew module
HomebrewHomebrew.platformsHomebrew.requirementHomebrew.virtualHomebrew.extra_envHomebrew.version_regexesHomebrew.installedHomebrew.package_metadata_batch()Homebrew.outdatedHomebrew.search()Homebrew.trust_tap()Homebrew.install()Homebrew.upgrade_all_cli()Homebrew.upgrade_one_cli()Homebrew.remove()Homebrew.sync()Homebrew.cleanup()Homebrew.cli_namesHomebrew.idHomebrew.name
BrewCask
- meta_package_manager.managers.macports module
MacPortsMacPorts.homepage_urlMacPorts.platformsMacPorts.requirementMacPorts.cli_namesMacPorts.cli_search_pathMacPorts.version_cli_optionsMacPorts.version_regexesMacPorts.installedMacPorts.outdatedMacPorts.search()MacPorts.install()MacPorts.upgrade_all_cli()MacPorts.upgrade_one_cli()MacPorts.remove()MacPorts.sync()MacPorts.cleanup()MacPorts.idMacPorts.nameMacPorts.virtual
- meta_package_manager.managers.mas module
- meta_package_manager.managers.mise module
- meta_package_manager.managers.npm module
NPMNPM.nameNPM.homepage_urlNPM.brewfile_entry_typeNPM.platformsNPM.requirementNPM.cooldown_env_varNPM.cooldown_env_value()NPM.pre_argsNPM.run_cli()NPM.installedNPM.outdatedNPM.search()NPM.install()NPM.upgrade_all_cli()NPM.cli_namesNPM.idNPM.upgrade_one_cli()NPM.virtualNPM.remove()NPM.cleanup()
- meta_package_manager.managers.opkg module
- meta_package_manager.managers.pacman module
PacmanPacman.namePacman.homepage_urlPacman.platformsPacman.requirementPacman.pre_argsPacman.version_regexesPacman.installedPacman.outdatedPacman.search()Pacman.install()Pacman.upgrade_all_cli()Pacman.upgrade_one_cli()Pacman.remove()Pacman.sync()Pacman.cleanup()Pacman.cli_namesPacman.idPacman.virtual
PacaurParuYay
- meta_package_manager.managers.pip module
PipPip.namePip.homepage_urlPip.platformsPip.requirementPip.cooldown_env_varPip.cli_namesPip.pre_argsPip.version_cli_optionsPip.version_regexesPip.search_all_cli()Pip.versionPip.installedPip.package_metadata_batch()Pip.outdatedPip.install()Pip.idPip.upgrade_one_cli()Pip.virtualPip.remove()Pip.cleanup()
- meta_package_manager.managers.pipx module
- meta_package_manager.managers.pkg module
PORTS_TREEPKGPortsPorts.namePorts.homepage_urlPorts.platformsPorts.cli_namesPorts.extra_envPorts.version_cli_optionsPorts.version_regexesPorts.availablePorts.installedPorts.idPorts.outdatedPorts.virtualPorts.install()Ports.upgrade_all_cli()Ports.upgrade_one_cli()Ports.removePorts.sync()Ports.cleanup()
- meta_package_manager.managers.pnpm module
- meta_package_manager.managers.pwsh_gallery module
PWSH_GalleryPWSH_Gallery.namePWSH_Gallery.homepage_urlPWSH_Gallery.platformsPWSH_Gallery.requirementPWSH_Gallery.cli_namesPWSH_Gallery.pre_argsPWSH_Gallery.version_regexesPWSH_Gallery.installedPWSH_Gallery.outdatedPWSH_Gallery.search()PWSH_Gallery.install()PWSH_Gallery.idPWSH_Gallery.upgrade_all_cli()PWSH_Gallery.virtualPWSH_Gallery.upgrade_one_cli()PWSH_Gallery.remove()
- meta_package_manager.managers.scoop module
ScoopScoop.nameScoop.homepage_urlScoop.platformsScoop.requirementScoop.version_regexesScoop.remove_headers()Scoop.installedScoop.outdatedScoop.search()Scoop.install()Scoop.upgrade_all_cli()Scoop.upgrade_one_cli()Scoop.remove()Scoop.sync()Scoop.cleanup()Scoop.cli_namesScoop.idScoop.virtual
- meta_package_manager.managers.sfsu module
- meta_package_manager.managers.snap module
- meta_package_manager.managers.steamcmd module
- meta_package_manager.managers.uv module
- meta_package_manager.managers.vscode module
- meta_package_manager.managers.winget module
WinGetWinGet.homepage_urlWinGet.brewfile_entry_typeWinGet.platformsWinGet.requirementWinGet.post_argsWinGet.version_regexesWinGet.windows_creation_flagsWinGet.windows_processes_to_cleanupWinGet.installedWinGet.outdatedWinGet.search()WinGet.install()WinGet.cli_namesWinGet.idWinGet.nameWinGet.upgrade_all_cli()WinGet.virtualWinGet.upgrade_one_cli()WinGet.remove()WinGet.sync()
- meta_package_manager.managers.xbps module
- meta_package_manager.managers.yarn module
YarnYarnClassicYarnClassic.idYarnClassic.nameYarnClassic.requirementYarnClassic.cli_namesYarnClassic.pre_argsYarnClassic.installedYarnClassic.global_dirYarnClassic.outdatedYarnClassic.search()YarnClassic.install()YarnClassic.upgrade_all_cli()YarnClassic.upgrade_one_cli()YarnClassic.remove()YarnClassic.virtual
YarnBerry
- meta_package_manager.managers.zypper module
ZypperZypper.nameZypper.homepage_urlZypper.platformsZypper.requirementZypper.pre_argsZypper.version_regexesZypper.installedZypper.outdatedZypper.search()Zypper.install()Zypper.upgrade_all_cli()Zypper.upgrade_one_cli()Zypper.remove()Zypper.sync()Zypper.cleanup()Zypper.cli_namesZypper.idZypper.virtual
meta_package_manager.sbompackage
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.
Xbar automatically bridge plugin options between its UI and environment variable on script execution.
This is in progress for SwiftBar.
- 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.
- 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:
objectImplements the minimal code necessary to locate and call the
mpmCLI on the system.Once
mpmis 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.
- static getenv_bool(var, default=False)[source]¶
Utility to normalize boolean environment variables.
Relies on
configparser.RawConfigParser.BOOLEAN_STATESto translate strings into boolean. See: https://github.com/python/cpython/blob/3c298e2e385fc6f462abaada2fd680deb1a2b58e/Lib/configparser.py#L596-L597- Return type:
- 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_idsset and ignores the rest. By default, onlycolor,fontandsizeare 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:
- static str_to_version(version_string)[source]¶
Transforms a string into a tuple of integers representing a version.
- static version_to_str(version_tuple)[source]¶
Transforms a tuple of integers representing a version into a string.
- Return type:
- 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.
- 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
mpmfrom the virtualenv context, orNoneif the folder is not a venv.Inspired by autoswitch_virtualenv.plugin.zsh and uv’s get_interpreter_info.py.
- 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
mpmover 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.
- 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_mpmis 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:
- 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:
Print the main menu.
- Return type:
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:
MPMPluginAll 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 atmeta_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.
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_LAYOUTenvironment variable.
- property dark_mode: bool¶
Detect dark mode by inspecting environment variables.
Value is sourced from two environment variables depending on the plugin:
OS_APPEARANCEfor SwiftBarXBARDarkModefor 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:
- 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
Optionkey, that runs silently
- Return type:
- print_upgrade_all_item(manager, submenu='')[source]¶
Print the menu entry to upgrade all outdated package of a manager.
- Return type:
- render(outdated_data)[source]¶
Wraps the
_render()method above to capture allprintstatements.- Return type:
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_typesand the extensions underLibrary/Homebrew/bundle/extensions/.tapalways comes first so that any third-party tap a downstreambreworcaskentry 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
brewenables by default. Never emitted as explicittaplines.
- meta_package_manager.brewfile.quote(value)[source]¶
Ruby-compatible double-quoted string literal.
brew bundle dumpuses Ruby’sString#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:
- 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_entryemits:bare:
brew "git"with options:
mas "Xcode", id: 497799835orflatpak "org.mozilla.firefox", with: ["flathub"]
- Return type:
- meta_package_manager.brewfile.format_header(coverage, skipped, platform)[source]¶
Render the comment block at the top of a Brewfile dump.
- Return type:
- meta_package_manager.brewfile.tap_from_package_id(package_id)[source]¶
Return
user/tapifpackage_idis tap-qualified, elseNone.Default taps in
DEFAULT_TAPSare filtered out: those are always enabled bybrewand emittingtaplines for them would be noise.
- 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_typeis 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’sinstalledis queried inline instead (the path the unit tests exercise).skipped_countsis 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:
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:
meta_package_manager.capabilities.search_capabilities()andmeta_package_manager.capabilities.version_not_implemented()flag the refinements an operation does not natively support, letting the framework compensate (refiltering search results, warning about ignored version pins).meta_package_manager.capabilities.Delegateandmeta_package_manager.capabilities.DelegatedMethodlet a manager reuse another manager’s CLI for an operation instead of reimplementing it.
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:
EnumRecognized 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:
- meta_package_manager.capabilities.search_capabilities(extended_support=True, exact_support=True)[source]¶
Decorator factory to be used on
search()operations to signalmpmframework manager’s capabilities.
- meta_package_manager.capabilities.version_not_implemented(func)[source]¶
Decorator to be used on
install()orupgrade_one_cli()operations to signal that a particular operation does not implement (yet) the version specifier parameter.
- class meta_package_manager.capabilities.DelegatedMethod(method, cli_name)[source]¶
Bases:
objectDescriptor that delegates a method call to another manager’s CLI.
When accessed on an instance, returns a wrapper that sets
_delegate_cli_pathon the instance so thatbuild_cliuses the target manager’s binary instead of the host manager’s own CLI.
- class meta_package_manager.capabilities.Delegate(source_class)[source]¶
Bases:
objectFactory that creates
DelegatedMethoddescriptors 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
--cooldownhelp text never drifts from the set of managers that actually carry acooldown_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:
- meta_package_manager.cli.prep_path(filepath)[source]¶
Prepare the output file parameter for Click’s echo function.
- meta_package_manager.cli.print_serialized_and_exit(ctx, data)[source]¶
Render
datain the active serialization format, then exit.When the global
--table-formatresolves to one of the structured serialization formats (JSON, YAML, TOML, XML, …), serializedataunder the sharedmpmroot element and stop the program. Otherwise return, so the caller falls through to its human-friendly table rendering.- Return type:
- meta_package_manager.cli.guard_existing_output(ctx, output_path, *, overwrite)[source]¶
Block clobbering an existing output file unless
overwriteis set.Warns and exits with code 2 when
output_pathalready 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:
- 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:
- 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-pathoption.Notice that the fully-qualified home directory get replaced by its shorthand (
~) if applicable:the full
/home/user/.python/site-packages/mpm/bar_plugin.pypath is simplified to~/.python/site-packages/mpm/bar_plugin.py,but
/usr/bin/python3.10/mpm/bar_plugin.pyis returned as-is.
- meta_package_manager.cli.cooldown_permits(manager)[source]¶
Decide whether a release-introducing operation may run on
manager.Returns
Truewhen no cooldown is active, when the manager can enforce it natively, or when the user opted out of the requirement with--allow-unsupported-managers. ReturnsFalse(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:
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:
objectSchema for
mpmconfiguration files.Defines the recognized options for the
[mpm](or[tool.mpm]) configuration section. Each field corresponds to a CLI option on the rootmpmgroup.Note
Dynamic manager selectors (
brew = true,pip = false, etc.) and click-extra built-in options (verbosity,table_format) are handled by thedefault_mappipeline and do not appear here.- cooldown: str = ''¶
Minimum release age (like
7 daysor1 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
Falseto run them anyway, without the safeguard.
- 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 byvalidate_manager_overrides_section()registered as aclick_extra.ConfigValidator. The field carries no CLI flag — it only exists in the schema to declare opacity and to enable--validate-configcoverage 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.PackageManagerattribute 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_urlandvirtualare 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.PackageManagerthat may have been computed from attributes covered byOVERRIDABLE_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_FIELDSwhose 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:
objectA 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.- field: str¶
Name of the overridden
PackageManagerattribute.
- 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:
- meta_package_manager.config.validate_manager_overrides_section(section, *, pool)[source]¶
Strict validator for the
[mpm.managers.<id>]configuration sub-tree.Pure function: inspects
sectionagainst the pool’s registered managers andOVERRIDABLE_FIELDS, raises the firstclick_extra.ValidationErrorit encounters, never mutates the pool. Suitable for registration as aclick_extra.ConfigValidatorand for direct invocation byapply_manager_overrides()so both the--validate-configpath and the runtime application path enforce the same rules.- Raises:
click_extra.ValidationError – when
sectionis 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. Thepathof 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:
- meta_package_manager.config.apply_manager_overrides(pool, overrides)[source]¶
Apply per-manager attribute overrides parsed from the user’s config file.
Expects
overridesto be a mapping of manager ID to a mapping of attribute name to its new value, as returned byconf["mpm"]["managers"].Noneand 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 raisesclick_extra.ValidationErroron the first issue. Both the runtime config-loading path and the explicit--validate-configpath 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_fieldssoManagerPool._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
ContributionHintentries, one per accepted override that targets aCONTRIBUTION_HINT_FIELDSfield. Each hint captures the pre-overridecli_pathso the contribution invitation can show what mpm would have detected without the user’s intervention.- Return type:
- meta_package_manager.config.build_manager_overrides_validator(pool)[source]¶
Construct a
click_extra.ConfigValidatorfor the[mpm.managers]sub-tree, bound to a specificManagerPool.Used by the CLI bootstrap (
@groupdecorator) to register a validator against the live pool. Wrappingvalidate_manager_overrides_section()in a closure satisfies theclick_extra.ConfigValidator.validatorsignature (Callable[[dict], None]) while keeping the underlying validator pool-agnostic and testable in isolation.- Return type:
- meta_package_manager.config.dump_manager_overrides(manager)[source]¶
Return the current overridable attributes of
manageras a TOML-ready dict.Walks
OVERRIDABLE_FIELDSin alphabetical order, reads each attribute from the manager instance, and converts tuples to lists sotomli_wcan serialize the result without translation. Attributes whose value isNoneare skipped: TOML cannot expressNoneand the user cannot override a field toNoneeither, 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.
- meta_package_manager.config.CTX_HINTS_KEY: Final[str] = 'mpm.contribution_hints'¶
ctx.metakey under which collectedContributionHintentries are accumulated betweenapply_manager_overrides_from_context()andprint_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 topool.Reads the full parsed config that
click_extraexposes underCONF_FULLafter configuration discovery and forwards the["mpm"]["managers"]subtree toapply_manager_overrides(). Returns silently when no configuration file was loaded or when the section is absent.Any
ContributionHintreturned byapply_manager_overrides()is stashed underCTX_HINTS_KEYforprint_contribution_hints()to surface at the end of the run.- Return type:
- meta_package_manager.config.print_contribution_hints(ctx)[source]¶
Print the collected contribution hints to
<stderr>.Reads from
CTX_HINTS_KEYand writes viaclick_extra.echo()rather than the logging module, so the message survives--verbosity CRITICALand thelogging.disable()block that suppresses log output for serialization formats. Caller is expected to gate this on the user’ssuggest_contribspreference.- Return type:
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:
ParamTypeParse 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 like7.ISO 8601 duration:
P7D,PT12H,P1WT6H. Case-insensitive.RFC 3339 absolute timestamp:
2024-05-01T00:00:00Zor with an offset like+02:00. Converted at parse time tonow - 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
daysorweeksinstead.- convert(value, param, ctx)[source]¶
Coerce
valueto adatetime.timedelta(orNone).
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:
ExceptionAn 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_namesis 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.
- 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 searchwalking every package’s metadata) while still being far belowMUTATING_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
--timeoutis 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-managertimeoutoverride).Keyed by the
meta_package_manager.capabilities.Operationsmember name, plus the special"version"detection probe. The keys are validated against theOperationsenum by the test suite so the two never drift apart. An operation absent from this map resolves toDEFAULT_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
mpmfeel 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 (aguix 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
PROMPTto anyextra_envassignments and the command line, both styled through the active theme. Reimplements the helper click-extra made private (_format_cli_prompt) in8.0.0.- Return type:
- class meta_package_manager.execution.CLIExecutor[source]¶
Bases:
objectLocate 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_errorslist.- 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
pythonorpython3.
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.
- 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_regexesbelow 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.MULTILINEonly. They are not compiled withre.VERBOSE, so literal whitespace in the pattern is significant and matches whitespace in the CLI output.
- timeout: int | None = None¶
Maximum number of seconds to wait for a CLI call to complete.
Nonemeans the user expressed no explicit preference: the effective cap is then resolved per-operation by_resolve_timeout()fromOPERATION_TIMEOUTS. A non-Nonevalue (the--timeoutflag 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
Truethe spinner still self-suppresses off a TTY: see_make_spinner(). Defaults toFalseso 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
cooldownago. 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.Nonedisables the gate.Only managers able to natively enforce a release-age limit honor this; see
cooldown_env_varandsupports_cooldown.
- require_cooldown_support: bool = True¶
Require native
cooldownsupport to run install/upgrade.By default (
True, fail-closed), when acooldownis requested, install and upgrade operations are skipped for managers lacking native release-age support, so nothing slips in unguarded. Setting this toFalseopts 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 (seesupports_cooldown); the value produced bycooldown_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 callsGenerateConsoleCtrlEvent(0)on exit will then fail silently because there is no console to broadcast to.No-op on non-Windows platforms (
getattrreturns0for 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.exeCOM server), those orphans can linger and consume resources. List the image names here so they are killed aftercommunicate()returns.No-op on non-Windows platforms.
- cooldown_env_value()[source]¶
Render
cooldownas the value ofcooldown_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:
- cooldown_rounded_up(unit_seconds)[source]¶
Render
cooldownas an integer count ofunit_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-basedmin-release-age, pnpm’s minute-basedminimumReleaseAge). Sub-unit cooldowns round up so the gate over-protects rather than silently collapsing to0(the “no cooldown” sentinel).- Return type:
- cooldown_env()[source]¶
Environment fragment enforcing the
cooldown, empty when inactive.Returns an empty mapping unless a
cooldownis set and the manager supports it. Merged into the environment of everyrun()call.
- 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.
- 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 bycli_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.TokenizedStringinstance.Skipped on platforms where the manager is not supported, even if
cli_pathresolved to an executable: that binary almost certainly belongs to a different tool that happens to share the same name (e.g. GNUmakeon macOS getting matched by the FreeBSDportsmanager), so probing it would either misreport the version or surface confusing error output.
- run(*args, extra_env=None, must_succeed=False)[source]¶
Run a shell command, return the output and accumulate error messages.
argsis allowed to be a nested structure of iterables, in which case it will be recursively flatten, thenNonewill be discarded, and finally each item casted to strings.- Running commands with that method takes care of:
adding logs at the appropriate level
removing ANSI escape codes from
subprocess.CompletedProcess.stdoutandsubprocess.CompletedProcess.stderrreturning ready-to-use normalized strings (dedented and stripped)
letting
mpm --dry-runandmpm --stop-on-errorhave expected effect on execution
- Parameters:
must_succeed (bool) – if
True, raisemeta_package_manager.manager.CLIErrorwhen the command fails, regardless of the user-facingstop_on_errorpreference, 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 (npmandpnpm outdatedexit1when updates exist); only the per-package state changers, which run under a patchedstop_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
*argswith 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
*argsand 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_cmdsis added before the CLI path.self.cli_pathis used as the main binary to execute.self.pre_argsandself.post_argsglobals are added before and after the provided*args.
Each additional set of elements can be disabled with their respective flag:
auto_pre_cmds=Falseto skip the automatic addition ofself.pre_cmdsauto_pre_args=Falseto skip the automatic addition ofself.pre_argsauto_post_args=Falseto skip the automatic addition ofself.post_args
Each global set of elements can be locally overridden with:
override_pre_cmds=tuple()override_cli_path=stroverride_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 theoverride_pre_cmdsparameter is not allowed to be set and theauto_pre_cmdsparameter is forced toFalse.- 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
*argswith 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 themeta_package_manager.manager.PackageManager.run()method, augmented with environment variables fromself.extra_env.All parameters are the same as
meta_package_manager.manager.PackageManager.build_cli(), plus:auto_extra_env=Falseto skip the automatic addition ofself.extra_envoverride_extra_env=dict()to locally overrides the laterforce_execignores thempm --dry-runandmpm --stop-on-erroroptions to force the execution and completion of the command.must_succeedraises on non-zero exit regardless ofmpm --stop-on-error. Seerun()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()andcollect_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-mintanddeb-getall reach dpkg (/var/lib/dpkg/lock).dnf,dnf5,yumandzypperall reach the RPM database.pacmanandpacstallall 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 --jobs1serializes 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
countitems.Returns the number of managers to process in parallel;
1means 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 --jobs1, orthe effective verbosity is
DEBUG(whether from--verbosityor the-v/-qcounters), where coherent per-manager log narration matters more than the speed-up (interleaved threads would scramble it).
Otherwise the
mpm --jobsvalue wins, capped atcount: there is no point spinning up more workers than there are items.- Return type:
- meta_package_manager.execution.warm_availability(managers)[source]¶
Probe several managers’
availableconcurrently.Reading
availableforces a manager’s--versiondetection, whose result (and thecli_path/executable/versionit 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, atDEBUGverbosity, for a single candidate, or atmpm --jobs1.- Return type:
None
- meta_package_manager.execution.trail_glyph(ok)[source]¶
Return the themed
✓or✗glyph for a trail line or finisher.- Return type:
- meta_package_manager.execution.trail_line(ok, message)[source]¶
Format one
✓/✗trail line: a status glyph followed bymessage.- Return type:
- class meta_package_manager.execution.OperationTrail(managers, *, label='', done_label='', unit='', total=0, jobs=1, coverage=False)[source]¶
Bases:
objectA
✓/✗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 everydispatch()fallback atmpm --jobs1orDEBUGverbosity.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’sprogress) plus an interactive stderr, so pipes, CI, serialized andDEBUGruns 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
--progressgate 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/totalcount.jobs (int) – the worker count from
effective_jobs();> 1selects the concurrent rendering.coverage (bool) – when set, a sequential run stays silent (the caller has another output, its result table). Unused when concurrent.
- 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()andcollect_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, seeSHARED_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 oneOperationTrail: 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 bympm --jobs): it collapses to a sequential pass — preserving each manager’s own per-call spinner — for a single lane, at--jobs 1, or atDEBUGverbosity.- 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 itFalse(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-unitdispatch()lane whose unit runsworkand stashes the(id, data)result in input position, so the returned list mirrorsmanagersregardless of completion order.workreturns this manager’s(id, data); it must handle its ownmeta_package_manager.execution.CLIError(each manager owns its subprocess and error list, so the call is thread-safe per manager). A truthydata["errors"](ordata["failed"]) marks that manager’s trail line✗; an optionaldata["label"]overrides its text (upgrade --alluses 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 todispatch()as the inverse ofcoverage.- 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>,restoreand the manager-tied specs ofinstall. 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 drivesdispatch(). Each task returns(ok, message)after doing its CLI call and recording its own outcome. The unmatched-package priority search ofinstallis 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
--jobsdoes not parallelize this run.Only
installwith 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, andinstallof fully manager-tied specs) now fan out throughcollect_per_package(). When the user explicitly raisedmpm --jobsabove1, say so once atINFO: the request simply has no effect on this run, which is narration, not a problem.- Return type:
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_labelsparameter, unless it is part of agroup. 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 globalLABELSregistry. Kept pure (no global mutation) so it can be called repeatedly without double-populating the registry.
- 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
mpmas 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:
EnumFilesystem scope a package manager operates within.
- SYSTEM = 'system'¶
Manages software installed globally, machine-wide.
All currently-maintained managers are system-scoped.
- PROJECT = 'project'¶
Manages dependencies confined to a project’s working tree.
Not supported yet. 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:
typeCustom metaclass used as a class factory for package managers.
Sets some class defaults, but only if they’re not redefined in the final manager class.
Also normalize list of platform, by ungrouping groups, deduplicate entries and freeze them into a set of unique platforms.
- class meta_package_manager.manager.PackageManager[source]¶
Bases:
CLIExecutorBase class from which all package manager definitions inherits.
Initialize
cli_errorslist.- scope: ClassVar[ManagerScope] = 'system'¶
Whether the manager operates on globally-installed software or project-local dependencies.
Defaults to
ManagerScope.SYSTEM, which covers every manager maintained today: they install and query software machine-wide. Project-scoped managers (Poetry, Bundler, Maven, …) resolve dependencies confined to a working tree and are not supported yet.
- 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
deprecatedflag is set, and only meaningful on such managers. Enforced bytest_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
Noneif 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 bymeta_package_manager.brewfilewhen rendering the output ofmpm 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
Platforminstances 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
Packageobject from the manager.Sets its
manage_idto the manager it belongs to.- Return type:
- brewfile_entry(package)[source]¶
Return
(entry_name, entry_options)for a Brewfile line, orNoneto skip the package.Default: emit
meta_package_manager.package.Package.idas the entry name with no options. Override on managers whose Brewfile DSL counterpart expects a different shape:masuses the app name withid: ADAM_ID,flatpakaddswith: ["remote"]. Only called whenbrewfile_entry_typeis set.
- property available: bool¶
Is the package manager available and ready-to-use on the system?
Returns
Trueonly if the main CLI:
Short, human-readable explanation of why
availableisFalse, orNoneif the manager is available.Returned in priority order so the most actionable cause is reported first: platform support, then CLI lookup, then executable bit, then version requirement.
- property installed: Iterator[Package]¶
List packages currently installed on the system.
Optional. Will be simply skipped by mpm if not implemented.
- installed_or_empty()[source]¶
Materialized
installed, or an empty tuple on CLI failure.Best-effort inventory snapshot for the
installed,dumpandsbomsubcommands: each wants “give me what’s installed, and just skip this manager if its CLI blew up” rather than re-implementing the samemeta_package_manager.execution.CLIErrorswallow. Logs one canonical warning on error and returns()so the caller carries on with the other managers.
- property installed_ids: frozenset[str]¶
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
pythonorpython3.
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
outdatedparsers that report each package’s latest version but not its currently-installed one, and so must look the latter up by ID (snap,xbps). The value mirrorsmeta_package_manager.package.Package.installed_version, whose declared type still carries the transientstrit normalizes away in__post_init__.
- package_metadata_batch(packages)[source]¶
Yield
(package, metadata)pairs enriched with whatever rich per-package data this manager can surface.Called by
mpm sbomin--bundledmode to populate licenses, checksums, download URLs, supplier/originator, and the declared dependency graph. The base implementation yieldsmeta_package_manager.package.EMPTY_METADATAfor each package and stays compatible with managers that do not (yet) expose richer metadata: their SBOM entries stay at the minimalPackagelevel, matching the historical and--minimalmodes.Manager subclasses override this with their native query path:
bulk shell-outs when the CLI accepts a package list (
brew info --json=v2 --installed,dpkg-query -W,apt-cache show);on-disk parsing when the metadata already lives on the filesystem (pip’s
.dist-infodirectories, Homebrew’s per-formulasbom.spdx.json, dpkg’s.md5sums).
The yielded pairs do not need to preserve the input order; the SBOM renderer matches by
Packageidentity. Implementations are expected to swallow per-package extraction errors and yieldmeta_package_manager.package.EMPTY_METADATAfor the affected packages rather than failing the whole scan: a single misbehaving formula must not abort an enrichment pass spanning hundreds of packages.Todo
Today every extractor is local-only (shell-outs to the manager’s CLI, plus on-disk reads). When extractors start reaching for network resources (PyPI’s JSON API, npm’s registry, crates.io, GitHub’s security advisories) the
--bundledflag will no longer be a fine-grained enough knob: some users will want enrichment but not network traffic (offline scans, CI without egress). The natural split is a future--network/--no-networkflag layered under--bundledto gate the network-touching code paths specifically, leaving local enrichment always-on for--bundled.- Return type:
- property outdated: Iterator[Package]¶
List installed packages with available upgrades.
Optional. Will be simply skipped by mpm if not implemented.
- property refiltered_outdated: Iterator[Package]¶
Wraps
outdated()with a version-equality filter.Some package managers report packages as outdated when the version strings differ at the character level but are numerically equal after parsing (e.g., Perl floating-point versions
2.0000vs2.000000). This filter drops those false positives.
- 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.
- search(query, extended, exact)[source]¶
Search packages available for install.
There is no need for this method to be perfect and sensitive to
extendedandexactparameters. If the package manager is not supporting these kind of options out of the box, just returns the closest subset of matching package you can come up with. Finer refiltering will happens in themeta_package_manager.manager.PackageManager.refiltered_search()method below.Optional. Will be simply skipped by mpm if not implemented.
- refiltered_search(query, extended, exact)[source]¶
Returns search results with extra manual refiltering to refine gross matchings.
Some package managers returns unbounded results, and/or don’t support fine search criterions. In which case we use this method to manually refilters
meta_package_manager.manager.PackageManager.search()results to either exclude non-extended or non-exact matches.Returns a generator producing the same data as the
meta_package_manager.manager.PackageManager.search()method above.Tip
If you are implementing a package manager definition, do not waste time to filter CLI results. Let this method do this job.
Instead, just implement the core
meta_package_manager.manager.PackageManager.search()method above and try to produce results as precise as possible using the native filtering capabilities of the package manager CLI.
- install(package_id, version=None)[source]¶
Install one package and one only.
Allows a specific
versionto be provided.- Return type:
- upgrade_one_cli(package_id, version=None)[source]¶
Returns the complete CLI to upgrade one package and one only.
Allows a specific
versionto be provided.
- upgrade(package_id=None, version=None)[source]¶
Perform an upgrade of either all or one package.
Executes the CLI provided by either
meta_package_manager.manager.PackageManager.upgrade_all_cli()ormeta_package_manager.manager.PackageManager.upgrade_one_cli().If the manager doesn’t provides a full upgrade one-liner (i.e. if
meta_package_manager.manager.PackageManager.upgrade_all_cli()raisesNotImplementedError), then the list of all outdated packages will be fetched (viameta_package_manager.manager.PackageManager.outdated()) and each package will be updated one by one by callingmeta_package_manager.manager.PackageManager.upgrade_one_cli().See for example the case of
meta_package_manager.managers.pip.Pip.upgrade_one_cli().- Return type:
- remove(package_id)[source]¶
Remove one package and one only.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- sync()[source]¶
Refresh package metadata from remote repositories.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- cleanup()[source]¶
Prune left-overs, remove orphaned dependencies and clear caches.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- discover_projects()[source]¶
Locate project trees this manager governs by scanning the filesystem.
Extension point reserved for
ManagerScope.PROJECTmanagers: detecting virtual environments, lockfiles, or project manifests scattered across the filesystem.Caution
Not implemented for any manager yet. System-scoped managers (the default) own no project trees to discover.
Todo
Candidate ecosystems for project-scope discovery. Listed with the project files that signal each, grouped by whether
mpmalready 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.yamlPython:
requirements.txt,pyproject.toml,poetry.lock,uv.lockRust:
Cargo.toml,Cargo.lockRuby:
Gemfile,Gemfile.lockPHP:
composer.json,composer.lockPerl:
cpanfile
No manager yet:
Java:
pom.xml(Maven),build.gradle(Gradle),ivy.xmlGo:
go.mod,go.sum.NET:
*.csproj,packages.config(NuGet)Swift:
Package.swift,Package.resolvedCocoaPods:
Podfile,Podfile.lockC/C++:
conanfile.txt(Conan),vcpkg.json(vcpkg)Conda:
conda-lock.yml
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:
objectLightweight representation of a package and its metadata.
- 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
idin output rendering, so only set this when the manager provides a name that differs from the package ID.
- 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_versionandlatest_versionare allowed to temporarily be strings between__init__and__post_init__. Once they reach the later, they’re parsed and normalized into eitherTokenizedStringor 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.
- property purl: PackageURL¶
Returns the package’s pURL object.
- static query_parts(query)[source]¶
Split
queryinto its contiguous alphanumeric segments.Contrary to
meta_package_manager.version.TokenizedString, does not split on collated number/alphabetic junctions.Canonical tokenizer behind
matches()and thesearch/installed/outdatedquery matching.meta_package_manager.manager.PackageManager.query_parts()delegates here.
- matches(query, extended=False, exact=False)[source]¶
Tell whether this package matches the free-form
query.Shared predicate behind the
search,installedandoutdatedsubcommands, so all three honor the same matching semantics:Fuzzy (default): a case-insensitive, tokenized substring match. Any alphanumeric segment of
query(seequery_parts()) found in the package ID or name counts as a match.Exact (
exact=True): the rawquerymust equal the package ID or name verbatim (case-sensitive, whole-string).Extended (
extended=True): also look into the packagedescription. Only meaningful when the description is populated, as it is forsearchresults.
A query with no alphanumeric segment (empty or punctuation-only) never matches.
- Return type:
- meta_package_manager.package.packages_asdict(packages, keep_fields)[source]¶
Returns a list of packages casted to a
dictwith only a subset of its fields.
- class meta_package_manager.package.DependencyScope(*values)[source]¶
-
Maps loosely onto SPDX
RelationshipTypevariants.SBOM renderers translate these into
RUNTIME_DEPENDENCY_OF,BUILD_DEPENDENCY_OF, etc.; CycloneDX collapses everything to its flatdependenciesgraph. 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]¶
-
Subset of algorithms shared by SPDX and CycloneDX schemas.
Used by
Checksumto 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:
objectA single
(algorithm, value)pair.- algorithm: ChecksumAlgorithm¶
- class meta_package_manager.package.Supplier(name, url=None)[source]¶
Bases:
objectDistributor of the package.
Distinct from the originator: the supplier is whoever served the bits (Homebrew, Debian, PyPI), the originator is the upstream author.
- class meta_package_manager.package.Originator(name, email=None, is_organization=False)[source]¶
Bases:
objectUpstream author or organization that produced the package.
- class meta_package_manager.package.Dependency(target_id, scope=DependencyScope.RUNTIME, version_constraint=None)[source]¶
Bases:
objectA single edge in the package’s declared dependency graph.
target_idis the dependency’s manager-native identifier (e.g.openssl@3for Homebrew). Renderers match it against the inventory’s installed packages to decide whether to emit a relationship.- scope: DependencyScope = 'runtime'¶
- class meta_package_manager.package.FileEntry(path, sha256=None, sha1=None, md5=None)[source]¶
Bases:
objectAn installed file shipped by the package.
Only populated for managers that can cheaply enumerate file contents and hashes (dpkg
.md5sums, pipRECORD). Omitted otherwise; the SBOM renderer leavesfilesAnalyzed=Falseon the SPDX Package.
- 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:
objectMaximalist metadata collected for a single installed package.
Distinct from
Packagein scope: wherePackagecarries only what the package manager itself surfaces through its inventory commands (id, name, version, arch),PackageMetadatacarries 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 maximalistmpm sbom --bundledoutput; the structure is deliberately generic so a future search, audit, or info display can reuse it.All fields are optional.
extrasis the escape hatch for manager- native fields that don’t fit the portable model: a Homebrew tap, a pip classifier list, an aptSection. SBOM renderers consult known keys and surface the rest as CycloneDXproperties.- originator: Originator | None = None¶
- dependencies: tuple[Dependency, ...] = ()¶
- external_sbom_path: Path | None = None¶
Path to an on-disk upstream SBOM document for this package.
Brew formulae installed with
HOMEBREW_SBOM=1write 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).
- 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) treatEMPTY_METADATAexactly like--minimalmode 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:
- are located in the
meta_package_manager.pool.ManagerPool.manager_subfolder subfolder, and
- are located in the
are sub-classes of
meta_package_manager.manager.PackageManager, and- are not
meta_package_manager.manager.PackageManager.virtual(i.e. have a non-null
meta_package_manager.manager.PackageManager.cli_namesproperty).
- are not
These properties are checked and enforced in unittests.
- class meta_package_manager.pool.ManagerPool[source]¶
Bases:
objectA 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_managersto 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_managerscall but were never user-overridden.
- get(key)¶
- 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 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.
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:
StrEnumFields 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
tablewith click-extra, sorting rows by thesort_bycolumns.Each
headersentry pairs a column label with theSortableFieldit carries, orNonefor a column that cannot be sorted on.sort_bylists 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; asort_bymatching no column leaves the rows in their original order.Reimplements the
print_sorted_tablehelper click-extra dropped in8.0.0, whereclick_extra.table.SortByOptioninstead bakes the sort key intoctx.print_table. That option derives its choices from a single command’s columns, whereas mpm shares one globalmpm --sort-byacross subcommands with heterogeneous tables, so the key is built here.- Return type:
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
mpmto 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_MAPmapping.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:
objectLightweight representation of a package specification.
Contains all parsed metadata to be used as constraints.
- classmethod parse_purl(spec_str)[source]¶
Resolve a pURL into its corresponding manager candidates.
Yields
Specifierobjects or returnsNone.
- 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.7If a specifier resolves to multiple constraints (as it might be the case for pURL), we produce and returns all variations. That way the
Solverbelow has all necessary details to resolve the constraints.Returns a tuple of
Specifier.
- property parsed_version: TokenizedString¶
- exception meta_package_manager.specifier.EmptyReduction[source]¶
Bases:
ExceptionRaised by the solver if no constraint can’t be met.
- class meta_package_manager.specifier.Solver(spec_strings=None, manager_priority=None)[source]¶
Bases:
objectCombine a set of
Specifierand allow for the solving of the constraints they represent.- 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_managersallows for filtering by discarding managers not in that list.
- reduce_specs(specs)[source]¶
Reduce a collection of
Specifierto its essential, minimal and unique form.This method assumes that all provided
specsare of the same package (likeresolve_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:
We remove all constraints tied to all by the top priority manager if provided.
If no manager priority is provided, we discard constraints not tied to a manager.
We discard constraints not tied to a version.
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:
- resolve_package_specs()[source]¶
Regroup specs of the pool by package IDs, and solve their constraints.
Each package ID yields one reduced spec per distinct target manager. A package therefore produces several specs when the user explicitly names several managers for it (
pkg:uv/rich pkg:brew/rich→ one spec each). In contrast, a single alias pURL that expands to several managers (pkg:rpm/ping→dnf/yum/zypper) is a set of alternatives, reduced to the top-priority one.
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:
print_summary()is the renderer.package_counts()collapses the boilerplateCounter({manager_id: len(payload[manager_id]["packages"]) for ...})pattern thatinstalled,outdated, andsearchall share.sbom_summary()adaptsmeta_package_manager.sbom.base.SBOM.stats()to the(counter, notes)shapeprint_summary()consumes, conditional on what the run actually did.
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.
countsis acollections.Counterkeyed by an opaque category label. The label is usually a package manager id, but thedump --brewfilesubcommand uses Brewfile entry types and any future caller is free to use whatever bucket makes sense. The parameter is namedcountsrather thanmanager_statsto 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).
notesis an iterable of follow-up lines printed verbatim under the count line.mpm sbomuses 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-summaryflag; this function itself is unconditional once called.- Return type:
- meta_package_manager.summary.package_counts(payload)[source]¶
Build a per-manager
Counterfrom a typical subcommand payload.installed,outdated, andsearchall stash their results in a{manager_id: {"packages": [...]}}dict. This helper turns that into the count-by-manager-idCounterthatprint_summary()accepts, eliminating theCounter({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 --brewfileline-counter pass) build theirCounterinline rather than wedging this helper into serving every shape.
- meta_package_manager.summary.sbom_summary(sbom, bundled)[source]¶
Adapt
meta_package_manager.sbom.base.SBOM.stats()to theprint_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--minimalscans, 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.
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.0and1!1.0 > 2.0because epoch2/1beats the implicit epoch0. Versions without an epoch default to0, 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) and0.1 > 0.beta2work without understanding PEP 440 or semver pre-release semantics.Trailing zeros are padding.
6.2and6.2.0compare 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,
g6cd4c31would shatter into("g", 6, "cd", 4, "c", 31). The 7-character floor matchesgit’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
ubuntu1into("ubuntu", 1)enables natural numeric ordering of embedded version numbers:a4 < a10compares correctly because4and10become 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.
.devNordering relative to pre-releases is not handled. Usepackaging.versionfor 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/rcsuffix 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, andc/rc/previewas equivalent aliases. These appear across ecosystems: Debian uses~alpha, npm uses-alpha, Homebrew usesalpha/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 orpretty_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
.postNas a post-release.patchcarries 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 makes1.0 > 1.0.post1.This set is deliberately small. Only tags with unambiguous “newer than release” semantics across multiple ecosystems belong here. Candidates like
revorpare excluded because they can also mean “revision” (Gentoo-r0) or “pre-release patchlevel” (FreeBSDp1), depending on context.
- class meta_package_manager.version.Token(value)[source]¶
Bases:
objectA normalized word, persisting its lossless integer variant.
Supports natural comparison with
strandinttypes. Used to compare versions and package IDs.Instantiates a
Tokenfrom an alphanumeric string or a non-negative integer.
- class meta_package_manager.version.TokenizedString(value)[source]¶
Bases:
objectTokenize a string for user-friendly sorting.
Essentially a wrapper around a list of
Tokeninstances.Parse and tokenize the provided raw
value.- pretty_print()[source]¶
Reconstruct the tokenized string using original-case segments and separators.
- Return type:
- static tokenize(string)[source]¶
Tokenize a string: ignore case and split at each non-alphanumeric characters.
Returns a tuple of
Tokeninstances, 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
- meta_package_manager.version.parse_version¶
Alias for
TokenizedStringused 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:
objectA 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
Trueif the string looks like a version.Heuristics: at least one token is an integer, or there is only one non-integer token.
- Return type:
- 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.1774638290vs2.1.1774896198, the common part is2.1and the diff includes.1774638290/.1774896198.