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.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.conda module
- meta_package_manager.managers.deb_get module
Deb_GetDeb_Get.nameDeb_Get.homepage_urlDeb_Get.platformsDeb_Get.default_sudoDeb_Get.version_cli_optionsDeb_Get.version_regexesDeb_Get.installedDeb_Get.outdatedDeb_Get.search()Deb_Get.install()Deb_Get.upgrade_all_cli()Deb_Get.upgrade_one_cli()Deb_Get.remove()Deb_Get.sync()Deb_Get.cleanup()Deb_Get.cli_namesDeb_Get.idDeb_Get.virtual
- meta_package_manager.managers.dnf module
- meta_package_manager.managers.emerge module
EmergeEmerge.nameEmerge.homepage_urlEmerge.platformsEmerge.default_sudoEmerge.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
EOPKGEOPKG.nameEOPKG.homepage_urlEOPKG.platformsEOPKG.default_sudoEOPKG.requirementEOPKG.pre_argsEOPKG.version_regexesEOPKG.installedEOPKG.outdatedEOPKG.search()EOPKG.install()EOPKG.upgrade_all_cli()EOPKG.upgrade_one_cli()EOPKG.remove()EOPKG.sync()EOPKG.cleanup()EOPKG.cli_namesEOPKG.idEOPKG.virtual
- 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.mas module
- meta_package_manager.managers.mise module
- meta_package_manager.managers.nix module
- meta_package_manager.managers.npm module
NPMNPM.nameNPM.homepage_urlNPM.brewfile_entry_typeNPM.platformsNPM.requirementNPM.cooldown_env_varNPM.pre_argsNPM.cooldown_env_value()NPM.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.pacman module
PacmanPacman.namePacman.homepage_urlPacman.platformsPacman.default_sudoPacman.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.pacstall module
PacstallPacstall.homepage_urlPacstall.platformsPacstall.requirementPacstall.extra_envPacstall.version_regexesPacstall.installedPacstall.outdatedPacstall.search()Pacstall.install()Pacstall.upgrade_all_cli()Pacstall.upgrade_one_cli()Pacstall.cli_namesPacstall.idPacstall.namePacstall.remove()Pacstall.virtualPacstall.sync()
- 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.idPip.install()Pip.virtualPip.upgrade_one_cli()Pip.remove()Pip.cleanup()
- meta_package_manager.managers.pipx module
- meta_package_manager.managers.pkcon module
- meta_package_manager.managers.pkg module
PORTS_TREEPKGPortsPorts.namePorts.homepage_urlPorts.platformsPorts.default_sudoPorts.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.sdkman module
SDKMANSDKMAN.homepage_urlSDKMAN.platformsSDKMAN.requirementSDKMAN.cli_namesSDKMAN.cli_search_pathSDKMAN.extra_envSDKMAN.version_cli_optionsSDKMAN.version_regexesSDKMAN.build_cli()SDKMAN.installedSDKMAN.outdatedSDKMAN.install()SDKMAN.upgrade_all_cli()SDKMAN.idSDKMAN.nameSDKMAN.upgrade_one_cli()SDKMAN.virtualSDKMAN.remove()SDKMAN.sync()SDKMAN.cleanup()
- meta_package_manager.managers.sfsu module
- meta_package_manager.managers.snap module
- meta_package_manager.managers.sun_tools module
- meta_package_manager.managers.tazpkg module
TazpkgTazpkg.nameTazpkg.homepage_urlTazpkg.platformsTazpkg.default_sudoTazpkg.extra_envTazpkg.post_argsTazpkg.version_cliTazpkg.version_cli_optionsTazpkg.version_regexesTazpkg.installedTazpkg.search()Tazpkg.install()Tazpkg.upgrade_all_cli()Tazpkg.upgrade_one_cli()Tazpkg.cli_namesTazpkg.idTazpkg.remove()Tazpkg.virtualTazpkg.sync()Tazpkg.cleanup()
- meta_package_manager.managers.uv 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.default_sudoZypper.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.
- meta_package_manager.bar_plugin.MPM_TIMEOUT = 60¶
Maximum duration in seconds the plugin lets any single
mpmcall run.Passed as
--timeoutto everympminvocation so the plugin is never at the mercy of mpm’s own per-operation defaults, which are tuned for interactive CLI use and far too long for a background menubar refresh (120s for read-only queries, 500s for state-changing operations likesync). A wedged package manager then fails the whole refresh in a minute instead of freezing the menubar for several.
- class meta_package_manager.bar_plugin.MPMPlugin[source]¶
Bases:
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. The verdict is narrated as a single answered
DEBUGline (brew implements installed.), keyed on the manager ID rather than the raw class repr.- Return type:
- meta_package_manager.capabilities.upgrade_all_is_synthesized(manager)[source]¶
Whether
mpmbackfills the manager’supgrade --all.Truewhen the manager supports the operation only through the one-by-one fallback ofmeta_package_manager.manager.PackageManager.upgrade(): it implementsoutdatedandupgrade_one_clibut no class in its hierarchy provides a nativeupgrade_all_cli.Falsewhen a native one-shot command exists, or when the operation is not supported at all.Feeds the per-manager table of
docs/augmentations.md, rendered live bydocs/docs_update.py.- 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.The flags are exposed as
extended_supportandexact_supportattributes on the wrapped method, so the documentation can derive which managers rely onmeta_package_manager.manager.PackageManager.refiltered_search()to honor the--exactand--extendedflags. An undecoratedsearchcarries no attribute and is read as natively supporting both refinements.
- meta_package_manager.capabilities.version_not_implemented(func)[source]¶
Decorator to be used on
install()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', 'yay')¶
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.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.exact_match_option(f)¶
--exactrefinement of the optional positionalQUERYofinstalledandoutdated.
- meta_package_manager.cli.query_option(f)¶
--queryfilter of the inventory exporters (dump,sbom).
- meta_package_manager.cli.query_exact_option(f)¶
--exactrefinement ofquery_option.
- meta_package_manager.cli.overwrite_option(f)¶
Opt-in clobbering of an existing output file (
dump,sbom); seeguard_existing_output().
- 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.cli.package_label(spec)[source]¶
Render a spec as
package_idorpackage_id@versionfor trail output.- Return type:
- meta_package_manager.cli.exit_on_failures(ctx, verb, failures)[source]¶
Report the per-package
failurescollected this run and exit non-zero.A no-op when
failuresis empty. Otherwise prints the durablecritical: Could not {verb}: ...summary (deduplicated and sorted), then exits with code1, following the linter convention where findings gate automation. The user opts out of that gate with-0/--zero-exit: the run then keeps its0exit code, the printed summary staying the durable record. Usage and configuration errors are unaffected: they exit2regardless, as genuine execution failures. Shared by every action command (install,remove,upgrade <packages>,restore).- 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 runtime policy around the [mpm.managers.<id>]
sections of the same configuration file: applying attribute overrides to shipped
managers, gating manager definitions on the trust of their source, and registering
them into the pool.
The concerns stay separate across three modules:
meta_package_manager.pool.ManagerPool owns the live manager instances
and the per-manager overridden_fields tracking dict;
meta_package_manager.definitions owns the declarative schema (which
fields a section may set, how to coerce values) and the class factory; this module
owns the loading policy and mutates the pool through the
apply_manager_overrides() and register_config_managers() helpers.
- class meta_package_manager.config.MpmConfig(all_managers=False, ignore_auto_updates=True, stop_on_error=False, dry_run=False, sudo=None, timeout=None, jobs='auto', cooldown='', require_cooldown_support=True, description=False, sort_by=<factory>, summary=True, network=False, suggest_contribs=True, managers=<factory>)[source]¶
Bases:
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.), click-extra built-in options (verbosity,table_format) and one-shot utility flags (--bar-plugin-path,--xkcd) are handled by thedefault_mappipeline and do not appear here.Note
Multi-word fields pin their config path through
CONFIG_PATH_METADATA_KEY: mpm’s configuration convention is underscored keys (matching Click parameter names, the--validate-configchecks and every documented example), while click-extra would otherwise kebab-case field names in the renderedclick:configreference.- sudo: bool | None = None¶
Force privileged manager operations with (
True) or without (False)sudo. Unset by default: system managers escalate, user-level managers do not. Overridden per manager by asudoentry in[mpm.managers.<id>].
- timeout: int | None = None¶
Maximum duration in seconds for each manager CLI call. When unset, a per-operation default applies:
120for read-only queries (installed,outdated,search) and500for state-changing operations. A set value overrides every operation.
- jobs: int | str = 'auto'¶
Maximum number of managers to run concurrently. Accepts an integer, or the keywords
auto(one fewer than the logical CPU count, the default) andmax(every logical CPU); set1to run sequentially.
- 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.
- summary: bool = True¶
Print an end-of-run summary on stderr: a count line of per-manager totals plus any subcommand-specific follow-up notes.
- network: bool = False¶
Opt into network calls during the run. Today this only affects
mpm sbom, which queries OSV.dev for vulnerability data.
- suggest_contribs: bool = True¶
Print a contribution invitation when a user override targets a field that likely indicates an upstream detection bug.
- managers: dict[str, dict]¶
Per-manager attribute overrides keyed by manager ID.
Typed as
dict[str, dict]so click-extra treats the sub-tree as opaque: its keys are manager IDs (data, not flag names) and its leaf entries are validated 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.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.A section keyed by a built-in manager ID is validated as an override (its fields must be a subset of
OVERRIDABLE_FIELDS). A section keyed by any other ID is validated as a brand-new manager definition viaparse_manager_definition().- Raises:
click_extra.ValidationError – when
sectionis not a mapping, an override sets an unknown field or a wrong-typed value, or a definition is malformed. 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.config.RISKY_OVERRIDE_FIELDS: Final[frozenset[str]] = frozenset({'cli_names', 'cli_search_path', 'pre_cmds', 'sudo'})¶
Override fields that can redirect mpm to run an arbitrary binary (or
sudo).When such an override is read from an untrusted config source,
apply_manager_overrides_from_context()logs a warning. Seedocs/security.md.
- meta_package_manager.config.config_file_is_trusted(path)[source]¶
Whether a config file is safe to load executable manager definitions from.
Trusted on POSIX when both the file and its parent directory are owned by the current user or root and are not group- or world-writable, mirroring how
ssh,gitandsudoreason about config-file trust: a writable file (or a writable directory that lets an attacker swap the file) could inject arbitrary commands.On platforms without
os.getuid(Windows), the POSIX ownership model does not apply and the check is skipped (returnsTrue); seedocs/security.mdfor the rationale and the residual risk.- Return type:
- meta_package_manager.config.register_config_managers(pool, definitions, *, source=None, source_is_url=False)[source]¶
Build and register config-defined managers into
pool, applying the trust gate.A definition is skipped (with a warning) when its ID collides with a built-in, when it comes from a remote URL config, or when its local config file fails
config_file_is_trusted(). Returns the IDs actually registered. Definitions whose ID is already in the pool (e.g. registered by the eager pre-load) are silently skipped so the eager and callback passes are idempotent.
- meta_package_manager.config.register_config_managers_from_context(ctx, pool)[source]¶
Register config-defined managers from the loaded config (authoritative pass).
Reads the parsed config under
CONF_FULL, parses the non-built-in[mpm.managers.<id>]sections, and registers them throughregister_config_managers(). This is the source of truth for availability: a manager defined in a config the eager pre-load could not reach (a URL, a custom path) still works from here, it just does not get a dedicated CLI flag.- Return type:
- meta_package_manager.config.discover_config_definitions(pool)[source]¶
Eagerly read new-manager definitions before the CLI group is built.
Best-effort and local-only: any error (no config, parse failure, missing reader) yields no definitions so CLI startup never breaks. URL configs are deferred to the authoritative
register_config_managers_from_context()pass. Supports both the standalone[mpm.managers]layout and[tool.mpm.managers]inpyproject.toml.
- meta_package_manager.config.register_eager_config_managers(pool)[source]¶
Register config-defined managers before the CLI group is constructed.
Called from
__main__.main()ahead of importing the Click group, so the dynamic--<id>/--no-<id>selectors enumerate the augmented pool and config-defined managers become first-class flags alongside the built-ins.- Return type:
meta_package_manager.definitions module¶
Declarative package managers: the TOML schema and its class factory.
A [mpm.managers.<id>] configuration section describes a manager as data. This
module owns everything that turns such a description into a live
PackageManager subclass:
the schema vocabulary: which manager attributes a section may set, both on a shipped manager (
OVERRIDABLE_FIELDS) and on a brand-new definition (DEFINITION_CLI_FIELDS, the operations DSL constants);the validation and parsing layer (
parse_manager_definition()), shared by--validate-configand the runtime registration path so a config that survives one survives the other;the class factory (
build_manager_class()), which synthesizes aConfigDrivenManagersubclass implementing exactly the operations the definition declares;the bundled-definition loader (
load_bundled_definitions(),build_bundled_managers()): mpm ships some managers as*.tomlpackage data undermeta_package_manager/managers/, each a single[mpm.managers.<id>]section in the exact schema a user would write.
The runtime policy around definitions stays in
meta_package_manager.config: where sections may be loaded from, the
trust gate on local files, the override-application pass and the registration
passes wired into the CLI. The split keeps this module dependent on
meta_package_manager.manager only, so the configuration layer can build
on it without a circular import.
- meta_package_manager.definitions.OVERRIDABLE_FIELDS: Final[Mapping[str, Callable[[Any], Any]]] = {'cli_names': <function _to_str_tuple>, 'cli_search_path': <function _to_str_tuple>, '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>, 'sudo': <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.definitions.VALID_PLATFORM_TOKENS: Final[frozenset[str]] = frozenset({'aix', 'all_agents', 'all_architectures', 'all_arm', 'all_ci', 'all_mips', 'all_platforms', 'all_shells', 'all_sparc', 'all_terminals', 'all_traits', 'all_windows', 'alpine', 'altlinux', 'amzn', 'android', 'arch', 'arch_32_bit', 'arch_64_bit', 'big_endian', 'bourne_shells', 'bsd', 'bsd_without_macos', 'buildroot', 'c_shells', 'cachyos', 'centos', 'chromeos', 'clearlinux', 'cloudlinux', 'cygwin', 'debian', 'dragonfly_bsd', 'exherbo', 'fedora', 'freebsd', 'generic_linux', 'gentoo', 'gpu_terminals', 'guix', 'haiku', 'hurd', 'ibm_mainframe', 'ibm_powerkvm', 'illumos', 'kali', 'kvmibm', 'linux', 'linux_layers', 'linux_like', 'linuxmint', 'little_endian', 'loongarch', 'macos', 'mageia', 'mandriva', 'manjaro', 'midnightbsd', 'multiplexers', 'native_terminals', 'netbsd', 'nixos', 'nobara', 'openbsd', 'opensuse', 'openwrt', 'oracle', 'os400', 'other_posix', 'other_shells', 'parallels', 'pidora', 'powerpc', 'raspbian', 'rhel', 'riscv', 'rocky', 'scientific', 'slackware', 'sles', 'slitaz', 'solaris', 'sourcemage', 'sunos', 'system_v', 'tumbleweed', 'tuxedo', 'ubuntu', 'ultramarine', 'unix', 'unix_layers', 'unix_without_macos', 'void', 'web_terminals', 'webassembly', 'windows', 'windows_shells', 'wsl1', 'wsl2', 'x86', 'xenserver'})¶
Platform and group IDs accepted in a definition’s
platformslist.Union of every
extra_platforms.PlatformID and every group ID, so both a specific platform (ubuntu) and a group (linux,all_platforms) resolve.
- meta_package_manager.definitions.DEFINITION_CLI_FIELDS: Final[Mapping[str, Callable[[Any], Any]]] = {'brewfile_entry_type': <function _to_str>, 'brewfile_skip_warning': <function _to_str>, 'cli_names': <function _to_str_tuple>, 'cli_search_path': <function _to_str_tuple>, 'default_sudo': <function _to_bool>, 'extra_env': <function _to_str_dict>, 'internal_sudo': <function _to_bool>, 'post_args': <function _to_str_tuple>, 'pre_args': <function _to_str_tuple>, 'pre_cmds': <function _to_str_tuple>, 'requirement': <function _to_str>, 'timeout': <function _to_int>, 'version_cli': <function _to_str>, 'version_cli_options': <function _to_str_tuple>, 'version_regexes': <function _to_str_tuple>}¶
CLI-execution attributes a definition may set, mostly reusing the override converters.
The runtime-preference fields (
deprecated,dry_run,ignore_auto_updates,stop_on_error) are excluded: they are command-line/global concerns, not part of a manager’s identity, and resolve through the usual option precedence.Five fields are definition-only:
brewfile_entry_typemaps the manager onto a Homebrew Bundle DSL entry so its installed packages joinmpm dump --brewfileexports (seebrewfile_entry_type).brewfile_skip_warningis the message emitted when the manager’s packages are deliberately left out of such an export (seebrewfile_skip_warning).default_sudois the manager’s built-in escalation policy (seedefault_sudo). Operations markedsudo = trueescalate by default, while the user’s global--no-sudoflag or asudooverride still win.internal_sudomarks a manager whose CLI invokessudoitself mid-run (seeinternal_sudo). mpm never wraps its commands insudo; priming instead reuses a warm credential cache for these internal escalations. Seedocs/sudo.md.version_clinames an alternate binary for the version probe (seeversion_cli), for suites whose own binaries expose no version flag (OpenBSD’spkg_add).
- meta_package_manager.definitions.DEFINITION_IDENTITY_FIELDS: Final[frozenset[str]] = frozenset({'homepage_url', 'name', 'operations', 'platforms'})¶
Top-level keys of a definition section that are not CLI-execution fields.
- meta_package_manager.definitions.QUERY_OPERATIONS: Final[frozenset[str]] = frozenset({'installed', 'outdated', 'search'})¶
Operations that parse the command’s stdout into packages.
- meta_package_manager.definitions.COMMAND_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup', 'install', 'remove', 'sync', 'upgrade_all', 'upgrade_one'})¶
Operations that only run a command and produce no inventory to parse.
- meta_package_manager.definitions.ALL_DEFINITION_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup', 'install', 'installed', 'outdated', 'remove', 'search', 'sync', 'upgrade_all', 'upgrade_one'})¶
Every operation name a definition may declare.
- meta_package_manager.definitions.RECOGNIZED_PARSE_FIELDS: Final[frozenset[str]] = frozenset({'installed_version', 'latest_version', 'package_id'})¶
Named regex groups / JSON field keys a query parser may map to a package.
- meta_package_manager.definitions.REQUIRED_PARSE_FIELDS: Final[Mapping[str, frozenset[str]]] = {'installed': frozenset({'package_id'}), 'outdated': frozenset({'latest_version', 'package_id'}), 'search': frozenset({'package_id'})}¶
Parse fields each query operation must extract to be useful.
installedneeds only the package ID: some tools genuinely track no per-package version (Clear Linux bundles underswupd, Cygwin listings underapt-cyg), and mpm’s package model treats the installed version as optional everywhere.outdatedwithout alatest_versionwould report nothing actionable, so there the version capture stays mandatory.
- meta_package_manager.definitions.OPERATION_ARG_PLACEHOLDER: Final[Mapping[str, str]] = {'install': 'package_id', 'remove': 'package_id', 'upgrade_one': 'package_id'}¶
Placeholder each operation’s
argsmust reference, so a value is actually passed to the CLI (aremovewith no{package_id}would target nothing).searchis deliberately absent: its{query}placeholder is optional. A tool with no real search command can still declare the operation by listing its whole catalog (opkg list,swupd bundle-list --all) and lettingmeta_package_manager.manager.PackageManager.refiltered_search()narrow the results, mirroring the search-from-scratch augmentation some built-in managers use.
- meta_package_manager.definitions.SEARCH_REFINEMENT_KEYS: Final[frozenset[str]] = frozenset({'exact_args', 'extended_args', 'id_name_only_args'})¶
Optional per-refinement argument templates of the
searchoperation.Each key holds the CLI arguments spliced into the
argstemplate — at the position of the matching{exact_args}-style marker — when the refinement is active:exact_argsfor an--exactsearch,extended_argsfor an--extendedone, andid_name_only_argsfor the default ID/name-restricted mode (mpm’s--id-name-only, for tools like Chocolatey whose unrestricted search is the default and take a flag to narrow it). An inactive refinement expands its marker to nothing.Declaring a key advertises native support for the matching mpm flag (
exact_argssets thesearchmethod’sexact_supportintrospection attribute, either of the other two setsextended_support), which feeds the augmentations documentation.meta_package_manager.manager.PackageManager.refiltered_search()still refines the results client-side either way, exactly as for the built-in managers.
- meta_package_manager.definitions.ALLOWED_ARG_PLACEHOLDERS: Final[Mapping[str, frozenset[str]]] = {'install': frozenset({'package_id'}), 'remove': frozenset({'package_id'}), 'search': frozenset({'exact_args', 'extended_args', 'id_name_only_args', 'query'}), 'upgrade_one': frozenset({'package_id'})}¶
Placeholders each operation’s
argsmay reference.Operations absent from this mapping take no placeholder at all. Any
{token}outside the operation’s set is rejected at parse time: a typoed{qeury}would otherwise reach the CLI as a literal argument and fail in silent, tool-specific ways.
- meta_package_manager.definitions.ARG_PLACEHOLDER_REGEX: Final = re.compile('\\{([a-z_]+)\\}')¶
Match
{placeholder}tokens in an operation’s args, for validation.
- meta_package_manager.definitions.JSON_FIELD_SELECTOR_REGEX: Final = re.compile('^(?P<key>[^\\[\\]]+?)(?:\\[(?P<index>\\d+)\\])?$')¶
Parse a JSON field selector: a key name with an optional
[N]list index.A bare
versionmaps the package field to the item’sversionkey. Aversions[0]selector additionally picks one element out of a list-valued key (zerobrew reports each package’s installed versions as an array). Anything more nested stays out of the DSL on purpose: a manager needing real JSON traversal is better served by a Python class.
- meta_package_manager.definitions.QUERY_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'fields', 'format', 'list_path', 'regex', 'sudo'})¶
Keys allowed in a query operation’s table.
cliis the same alternate-binary hook as on command operations.sudo = truemarks the query as privileged, for the rare tool that gates even its read-only listings behind root (deb-get’s upgradable check); escalation then follows the usual per-manager policy.
- meta_package_manager.definitions.SEARCH_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'exact_args', 'extended_args', 'fields', 'format', 'id_name_only_args', 'list_path', 'regex', 'sudo'})¶
Keys allowed in the
searchoperation’s table: a query operation plus the per-refinement argument templates ofSEARCH_REFINEMENT_KEYS.
- meta_package_manager.definitions.COMMAND_OPERATION_KEYS: Final[frozenset[str]] = frozenset({'args', 'cli', 'sudo'})¶
Keys allowed in a command operation’s table.
clinames an alternate binary for this operation, resolved on the search path at call time: it lets one definition span sibling binaries (urpmqquerying whileurpmiinstalls).sudo = truemarks the operation as privileged, mirroring thesudo=Trueflag built-in managers pass torun_cli: escalation then follows the per-manager policy (the definition’sdefault_sudo, overridden by the user’s--sudo/--no-sudo).
- class meta_package_manager.definitions.OperationSpec(args, cli=None, sudo=False, exact_args=None, extended_args=None, id_name_only_args=None, parse_mode='none', regex=None, list_path=None, fields=None)[source]¶
Bases:
objectDeclarative specification of one operation of a config-defined manager.
- args: tuple[str, ...]¶
CLI arguments appended after the resolved binary, before
post_args.May embed the
{package_id}and{query}placeholders, substituted at call time.{version}is intentionally unsupported: config-defined managers do not pin versions (see_make_install()).
- cli: str | None = None¶
Alternate binary name for this operation, or
Nonefor the manager’s maincli_path.Resolved with
which()at call time, so one definition can span sibling binaries (urpmi/urpme/urpmq,cast/dispel/gaze). The operation fails withFileNotFoundErrorwhen the binary is missing rather than silently falling back to the main CLI.
- sudo: bool = False¶
Mark the operation as privileged, mirroring the
sudo=Trueflag built-in managers pass torun_cli().Escalation still follows the per-manager policy: the definition’s
default_sudo, overridden by the user’s--sudo/--no-sudo. Command operations are the usual bearers; a query may also set it, for the rare tool that gates its read-only listings behind root (deb-get).
- exact_args: tuple[str, ...] | None = None¶
Arguments spliced at the
{exact_args}marker of asearch’sargswhen an exact match is requested, orNonewhen the tool has no native exact mode. SeeSEARCH_REFINEMENT_KEYS.
- extended_args: tuple[str, ...] | None = None¶
Arguments spliced at the
{extended_args}marker of asearch’sargswhen the extended (description-reaching) mode is requested, orNonewhen the tool has no native switch for it. SeeSEARCH_REFINEMENT_KEYS.
- id_name_only_args: tuple[str, ...] | None = None¶
Arguments spliced at the
{id_name_only_args}marker of asearch’sargswhen the default ID/name-restricted mode is requested, for tools whose unrestricted search is the default (Chocolatey’s--by-id-only), orNone. SeeSEARCH_REFINEMENT_KEYS.
- parse_mode: str = 'none'¶
How to turn the command’s stdout into packages:
"regex"(per-line named groups),"json"(structured extraction), or"none"for command-only operations that produce no inventory (install, remove, sync, …).
- regex: str | None = None¶
Regular expression matched against each stdout line in
"regex"mode.Recognized named groups:
package_id(required),installed_versionandlatest_version(optional). Compiled withre.MULTILINE.
- class meta_package_manager.definitions.ManagerDefinition(manager_id, name, platforms, homepage_url, cli_fields, operations)[source]¶
Bases:
objectA brand-new package manager declared from a
[mpm.managers.<id>]section.Produced by
parse_manager_definition()after validation, consumed bybuild_manager_class().- platforms: tuple[str, ...]¶
Platform and group ID strings, resolved to
extra_platforms.Platformmembers at build time.
- cli_fields: dict[str, object]¶
Overridable CLI-execution attributes (
cli_names,requirement,version_regexes, …), pre-coerced to their runtime types.
- operations: dict[str, OperationSpec]¶
Declared operations keyed by name (
installed,install, …).
- class meta_package_manager.definitions.ConfigDrivenManager[source]¶
Bases:
PackageManagerBase class for managers synthesized from configuration.
Carries no operation methods on purpose: only the dynamically-created subclass returned by
build_manager_class()defines the operations the user actually declared, someta_package_manager.capabilities.implements()reports an accurate capability set. Defining an operation here would make every config-defined manager falsely advertise it.Exists mainly as a marker (
isinstance(manager, ConfigDrivenManager)distinguishes user-defined managers from built-ins) and as a shared home for any future config-driven behavior.Initialize
cli_errorslist.- cli_names: tuple[str, ...] = ('configdrivenmanager',)¶
List of CLI names the package manager is known as.
This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.
- ..hint::
This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named
pythonorpython3.
By default, this property’s value is derived from the manager’s ID (see the
MetaPackageManager.__init__method above).
- id: str = 'configdrivenmanager'¶
Package manager’s ID.
Derived by defaults from the lower-cased class name in which underscores
_are replaced by dashes-.This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.
- name: str = 'ConfigDrivenManager'¶
Return package manager’s common name.
Default value is based on class name.
- virtual: bool = False¶
Should we expose the package manager to the user?
Virtual package manager are just skeleton classes used to factorize code among managers of the same family.
- definition_source: str | None = None¶
Repo-relative path to the bundled TOML file this manager was defined in.
Set by
build_bundled_managers()for the managers mpm ships as package data; staysNonefor a manager defined in a user’s own configuration file. The documentation generator links a bundled manager’s benchmark entry to this file, a config-defined manager having no Python source line to point at.
- meta_package_manager.definitions.parse_manager_definition(manager_id, section)[source]¶
Validate and parse one
[mpm.managers.<id>]definition section.Returns a
ManagerDefinitionready forbuild_manager_class(). Raisesclick_extra.ValidationError(path relative to the[mpm.managers]root) on any problem, so the same function backs both--validate-configand the runtime registration path.- Return type:
- meta_package_manager.definitions.build_manager_class(definition)[source]¶
Synthesize a
PackageManagersubclass from a validated definition.Assembles a class namespace from the definition’s identity and CLI fields, then adds one method (or property) per declared operation. Only the declared operations land in the namespace, so
meta_package_manager.capabilities.implements()reflects exactly what the user configured. Single- and all-package upgrades map toupgrade_one_cli()/upgrade_all_cli()so the inheritedupgrade()orchestrator drives them, just like the built-in managers.- Return type:
- meta_package_manager.definitions.BUNDLED_DEFINITIONS_PACKAGE: Final[str] = 'meta_package_manager.managers'¶
Import package whose
*.tomlresources hold mpm’s bundled manager definitions.
- meta_package_manager.definitions.load_bundled_definitions()[source]¶
Parse every bundled
[mpm.managers.<id>]definition shipped as package data.Reads each
*.tomlresource ofBUNDLED_DEFINITIONS_PACKAGEviaimportlib.resources(so it works the same from an unpacked install, a zip or a Nuitka onefile), and validates every section withparse_manager_definition(). Returns(definition, source)pairs, wheresourceis the repo-relative path used to link the manager’s documentation. Cached because the shipped files never change at runtime.A malformed bundled file is a packaging bug, but it is logged and skipped rather than raised so one bad resource cannot break
mpmstartup for everyone. The hermetictest_bundled_inventoryandtest_bundled_registeredkeep the shipped files valid.- Return type:
- meta_package_manager.definitions.bundled_manager_ids()[source]¶
IDs of the managers mpm ships as bundled configuration definitions.
- meta_package_manager.definitions.build_bundled_managers()[source]¶
Instantiate every bundled definition into a live, pool-ready manager.
Each
ConfigDrivenManagersubclass records the TOML file it came from inConfigDrivenManager.definition_source, so the documentation generator can link to it. Called once bymeta_package_manager.pool.ManagerPool.register.- Return type:
meta_package_manager.dispatch module¶
Cross-manager dispatch: scheduling many package managers at once.
Where meta_package_manager.execution runs one manager’s CLI in one
subprocess, this module schedules many managers concurrently: the job-count
policy that decides sequential-vs-concurrent (effective_jobs()), the
up-front availability probe used during selection (warm_availability()),
the two spinner-wrapped fan-out primitives the CLI subcommands drive
(collect_from_managers(), collect_per_package()) with their shared
dispatch() engine, the backend-lock catalog that serializes conflicting
managers (SHARED_LOCK_FAMILIES and merge_into_lock_lanes()), and
the shared ✓/✗ ledger (OperationTrail plus the
trail_line() atom) that the concurrent and sequential paths both report
through.
The generic concurrency primitives live upstream in
click_extra.execution (run_jobs/run_lanes driven by
mpm --jobs); this module keeps what is package-manager policy: which
managers must never overlap, how the trail narrates a batch, and when a batch
collapses to a sequential pass.
- meta_package_manager.dispatch.SHARED_LOCK_FAMILIES: Final[tuple[frozenset[str], ...]] = (frozenset({'apt', 'apt-mint', 'deb-get'}), frozenset({'brew', 'cask'}), frozenset({'dnf', 'dnf5', 'yum', 'zypper'}), frozenset({'pacman', 'pacstall'}))¶
Managers that contend for one shared backend lock, grouped by backend.
Different managers are otherwise independent processes over disjoint state, so running them in parallel is safe. The exception is a handful that drive a shared backend and serialize on its lock:
apt,apt-mintanddeb-getall reach dpkg (/var/lib/dpkg/lock).brewandcaskare the same brew binary and serialize on Homebrew’s own update lock: two concurrentbrew update(which mpm sync issues identically for both, as the formula/cask split does not apply to it) collide, one failing with “Another active Homebrew update process is already running”.dnf,dnf5,yumandzypperall reach the RPM database.pacmanandpacstallall reach the pacman database (/var/lib/pacman/db.lck).
Concurrency is safe across families and unsafe within one, just as it is unsafe within a single manager (which is why a manager’s own packages stay serial). When two members run at once the shared lock makes them block or fail, never corrupt.
Enforced for the mutating fan-outs only:
merge_into_lock_lanes()collapses each family’s members into a singledispatch()lane, so they run serially while distinct families still run in parallel. The read-only queries (installed/outdated/search) take no backend lock, so they keep one lane per manager and stay fully concurrent. Members of a lane also share a command cache (seeCLIExecutor.run_cache), so two that resolve to a byte-identical invocation (brewandcaskforsyncandcleanup) run the subprocess once.Adding a newly-conflicting set of managers is a one-line edit here: append a
frozensetof their ids and both the serialization and the cache pick it up.
- meta_package_manager.dispatch.effective_jobs(ctx, count)[source]¶
Resolve how many worker threads to use for a batch of
countitems.Thin wrapper over
click_extra.execution.resolve_jobs()pinning mpm’s policy: always collapse to a single (sequential) worker atDEBUGverbosity, where coherent per-manager log narration matters more than the speed-up (interleaved threads would scramble it). The base helper also collapses to sequential with no active CLI context, for a single item, or atmpm --jobs1; otherwise thempm --jobsvalue wins, capped atcount(no point spinning up more workers than there are items).- Return type:
- meta_package_manager.dispatch.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:
- meta_package_manager.dispatch.trail_glyph(ok)[source]¶
Return the themed
✓or✗glyph for a trail line or finisher.- Return type:
- meta_package_manager.dispatch.trail_line(ok, message)[source]¶
Format one
✓/✗trail line: a status glyph followed bymessage.- Return type:
- class meta_package_manager.dispatch.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 thedone/totalcount.jobs (
int) – the worker count fromeffective_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.dispatch.dispatch(label, done_label, unit, lanes, *, coverage=False, ctx=None)[source]¶
Fan a set of work lanes out across managers, narrating a
✓/✗trail.The single scheduling primitive behind both
collect_from_managers()andcollect_per_package(). A lane is one or more managers paired with a list of callables; lanes run concurrently (one worker each) while a lane’s own callables run serially, because a package manager cannot safely run two of its own invocations at once, nor can two managers sharing a backend lock (seeSHARED_LOCK_FAMILIES). A lane usually wraps a single manager;merge_into_lock_lanes()is what bundles a whole lock family into one, and such a lane also gets a shared command cache (seeCLIExecutor.run_cache) so its members collapse identical invocations.Each callable does its work, records its own outcome (output to
INFO, failures into a caller-owned list) and returns(ok, message)for the trail. The whole batch reports through 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 toOperationTrail. 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:
- meta_package_manager.dispatch.merge_into_lock_lanes(pairs)[source]¶
Group
(manager, task)pairs intodispatch()lanes, one per lock family.Managers sharing a
SHARED_LOCK_FAMILIESentry collapse into a single lane so their tasks run serially (the lane isdispatch()’s unit of mutual exclusion), while unrelated managers each keep their own lane and run concurrently. A manager not in any family keys on its own id, so its tasks still group together (a manager’s own invocations cannot overlap either). First-seen order is preserved, both across lanes and within a lane’s task list.Used by the mutating fan-outs only: the state changers through
collect_per_package(), andsync/cleanup/upgrade --allthroughcollect_from_managers(). The read commands take no backend lock and skip this, keeping one lane per manager.
- meta_package_manager.dispatch.collect_from_managers(label, done_label, managers, work, *, report_state=False, ctx=None)[source]¶
Run
work(manager)for every manager concurrently, results in input order.The fan-out primitive for the read-only commands (
installed/outdated/search) and the independent maintenance commands (sync/cleanup/upgrade --all). It adapts each manager into adispatch()unit that runsworkand stashes the(id, data)result in input position, so the returned list mirrorsmanagersregardless of completion order. The maintenance commands (report_state) then merge lock-family members into shared serial lanes (merge_into_lock_lanes()); the read commands keep one lane per manager.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, keeps the trail in the sequential fallback, and turns on lock-family serialization. Read commands leave itFalse: their table is the output, so the sequential fallback is silent and the finisher reports coverage. Passed todispatch()as the inverse ofcoverage.- Return type:
- meta_package_manager.dispatch.collect_per_package(label, done_label, tasks, *, ctx=None)[source]¶
Run per-package operations across managers concurrently, serial within each.
The fan-out primitive for the ordering-free state changers that act on many (package, manager) pairs:
remove,upgrade <packages>,restoreand the manager-tied specs ofinstall. Takes a flat list of(manager, task)pairs and groups them into lanes by lock family (merge_into_lock_lanes()) — so a manager’s own packages, and any lock-family peers, stay serial while unrelated managers run in parallel — then 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:
- meta_package_manager.dispatch.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.execution module¶
CLI-execution engine shared by every package manager.
Runs one manager’s CLI in one subprocess: the
meta_package_manager.execution.CLIExecutor mixin (which
meta_package_manager.manager.PackageManager inherits) locates the binary
and runs it, the meta_package_manager.execution.CLIError exception carries
a failed call’s result, and meta_package_manager.execution.highlight_cli_name()
themes a binary’s name.
Scheduling many managers at once is the next altitude up, and lives in
meta_package_manager.dispatch: the concurrent fan-out primitives, the
lock families and the shared ✓/✗ trail. The sudo machinery that cuts
across both altitudes (credential priming, the keepalive, the hidden-prompt stall
watchdog) lives in meta_package_manager.sudo: this module only consumes
it, to wrap escalated commands and diagnose their failures.
Note
The name and intent mirror click_extra.execution from the sibling
click-extra project, where the
generic layers now live: the concurrency primitives (run_jobs/run_lanes
driven by mpm --jobs), the single-subprocess engine
(click_extra.execution.run_cli(), which disclosed invocations and
streams output to the logs), and the Ctrl+C machinery
(click_extra.execution.install_interrupt_handler() terminating the
in-flight children registered by run_cli). This module keeps what is
package-manager policy: per-operation timeouts, sudo escalation, cooldown
enforcement and dry-run.
- 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.The name is only highlighted when it matches one of the recognized
match_names, so an unrecognized binary stays plain. Matching is insensitive to case on Windows and case-sensitive on other platforms, thanks toos.path.normcase.The rendering is delegated to
click_extra.execution.highlight_bin_name(), the same helper behind the$-prompt and spawn-trace log lines, so thempm managerstable and the logs can never drift apart.
- 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.
- 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_cli: str | None = None¶
Alternate binary probed for the manager’s version, instead of the main CLI.
Some manager suites expose no version flag on any of their own binaries (OpenBSD’s
pkg_add/pkg_info, Solaris’pkgadd/pkginfo): they ship with the base system and are versioned with the OS itself. Naming aversion_cli(likeuname) makes the version probe run that binary withversion_cli_optionsand parse its output withversion_regexes, while every operation keeps using the manager’s owncli_path. The binary is resolved withwhich(); the version resolves toNone(manager notfresh) when it is not found.
- version_regexes: tuple[str, ...] = ('(?P<version>\\S+)',)¶
Regular expressions used to extract the version number.
This property must be a tuple of strings, each of which is a valid regular expression that must contain a group named
<version>.The first of these regexes producing a match and returning non-empty
<version>group will be used as the version string of the package manager.That version string will then be sanitized and normalized by
meta_package_manager.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.
- sudo: bool | None = None¶
User escalation policy: run this manager’s privileged commands with
sudo.None(the default) means the user expressed no preference, so the built-indefault_sudodecides.True/Falseforce escalation on or off for every operation this manager marks privileged (abuild_cli(..., sudo=True)call). Set globally bympm --sudo/mpm --no-sudoand per manager by the[mpm.managers.<id>] sudoconfig key, the latter winning (seemeta_package_manager.pool.ManagerPool._select_managers()).Only privileged operations on UNIX are ever escalated. A manager that escalates internally (
internal_sudo) has no such markers and is never wrapped insudobympm: its ownsudoreuses the credential cache whenprime_sudo()finds it already warm, and is otherwise covered by the silent-call notice inrun().
- default_sudo: bool = False¶
Built-in escalation default, used when
sudoisNone.Falseon the base: most managers install into user-writable trees and never need root. The system package managers whose privileged operations require root (apt,dnf,pacman,zypper, …) set this toTrueso theirbuild_cli(..., sudo=True)operations escalate out of the box, while staying switchable off throughsudo(--no-sudoor config) for rootless setups.
- internal_sudo: bool = False¶
Marks a manager whose CLI invokes
sudoitself mid-run.Homebrew
caskruns it from installer artifacts andfinkre-execs its root commands through it. mpm never wraps such a manager’s commands: none of its operations carry abuild_cli(..., sudo=True)marker, and running the tool undersudois often forbidden outright (brewrefuses root). Consumed byprime_sudo(), whose opportunistic probe keeps an already-warm credential cache alive for these internal escalations, and by the silent-call notice inrun(), which flags a possibly-hidden password prompt on a cold cache.Forcing
sudo = trueon such a manager (config key or--sudo) still never wraps its commands, but does promote it into the up-front prompt path ofprime_sudo().
- cooldown_env_var: ClassVar[str | None] = None¶
Environment variable this manager reads to honor a
cooldown.None(the default) means the manager has no native release-age mechanism and cannot honor a cooldown. A subclass that sets this string advertises support (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.
- run_cache: dict[tuple, tuple[int, str, str]] | None = None¶
Optional cache that de-duplicates identical CLI runs within a lock family.
Noneby default, which disables caching: everyrun()call spawns its own subprocess.meta_package_manager.dispatch.dispatch()injects one shared dict into all the managers of a multi-manager lock-family lane (seemeta_package_manager.dispatch.SHARED_LOCK_FAMILIES) for the duration of that lane, so members resolving to a byte-identical command (brewandcaskboth runningbrew updatefor mpm sync) run the subprocess once and replay the cached(code, output, error)for the rest. The replay still walksrun()’s logging and failure gate, so a failed shared command is attributed to every member. Keyed on the resolved command line and its environment, so only genuinely identical invocations collapse.
- 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.
- sibling_cli(name, *, same_dir=False)[source]¶
Resolve the path of a sibling binary of the manager’s main CLI.
Some managers ship as a suite of binaries (
xbps-install/xbps-query,pkg_add/pkg_info, emerge’sqlist): an operation then runs a sibling instead of the main CLI. By default the sibling is searched like the main CLI itself (which(), honoringcli_search_path), and a missing binary raisesFileNotFoundErrorrather than silently falling back to the wrong program.same_dir=Trueinstead takes the sibling from the directory ofcli_path, without an existence probe: suites installing all their binaries side by side (XBPS, Nix) guarantee the neighbor, and resolving it from the same directory can never mix two installations. A genuinely missing file then surfaces at spawn time.- Return type:
- 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:
disclosing the invocation at
INFO(the reproducible$-prompt line with forced environment variables) and streaming the raw output live toDEBUG, prefixed with the manager ID, viaclick_extra.execution.run_cli()flagging, on a terminal, the mutating call of an internal escalator that goes silent on a cold credential cache and may be blocked on a hidden password prompt (see
_StallWatchdog)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 --non-interactive] <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 UNIX, an operation marked privileged (
sudo=True) is escalated only when the per-manager policy opts in (sudo, falling back todefault_sudo). It is then run through sudo with--non-interactive(it spends the credential cache warmed byprime_sudo()and fails fast rather than blocking on a password prompt). When escalation applies,override_pre_cmdsis not allowed to be set andauto_pre_cmdsis forced toFalse. A non-UNIX host never escalates.- 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.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', 'urpmi', 'yum', 'zypper'}), 'scoop-based': frozenset({'scoop', 'sfsu'}), 'uv-based': frozenset({'uv', 'uvx'}), 'vscode-based': frozenset({'vscode', 'vscodium'})}¶
Managers sharing the same ecosystem are grouped together under the same label.
Grouping is by ecosystem (the underlying packaging system), not by installation paradigm. For example, source-based helpers like Pacstall and AUR helpers are grouped with their ecosystem (dpkg-based and pacman-based respectively), even though they build from source rather than fetching pre-built binaries.
- meta_package_manager.labels.all_manager_label_ids = frozenset({'apk', 'apm', 'apt', 'apt-cyg', 'apt-mint', 'asdf', 'brew', 'cargo', 'cask', 'cave', 'choco', 'chromebrew', 'composer', 'conda', 'cpan', 'deb-get', 'dnf', 'dnf5', 'emerge', 'eopkg', 'fink', 'flatpak', 'fwupd', 'gem', 'gh-ext', 'guix', 'macports', 'mas', 'mise', 'mpm', 'nix', 'npm', 'opkg', 'pacaur', 'pacman', 'pacstall', 'paru', 'pip', 'pipx', 'pkcon', 'pkg', 'pkg-tools', 'pkgin', 'pnpm', 'ports', 'pwsh-gallery', 'scoop', 'sdkman', 'sfsu', 'slapt-get', 'snap', 'soar', 'sorcery', 'steamcmd', 'stew', 'sun-tools', 'swupd', 'tazpkg', 'tlmgr', 'topgrade', 'urpmi', 'uv', 'uvx', '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-cyg': '📦 manager: apt-cyg', 'apt-mint': '📦 manager: dpkg-based', 'asdf': '📦 manager: asdf', 'brew': '📦 manager: homebrew', 'cargo': '📦 manager: cargo', 'cask': '📦 manager: homebrew', 'cave': '📦 manager: cave', 'choco': '📦 manager: choco', 'chromebrew': '📦 manager: chromebrew', 'composer': '📦 manager: composer', 'conda': '📦 manager: conda', 'cpan': '📦 manager: cpan', 'deb-get': '📦 manager: dpkg-based', 'dnf': '📦 manager: rpm-based', 'dnf5': '📦 manager: rpm-based', 'emerge': '📦 manager: emerge', 'eopkg': '📦 manager: eopkg', 'fink': '📦 manager: fink', 'flatpak': '📦 manager: flatpak', 'fwupd': '📦 manager: fwupd', 'gem': '📦 manager: gem', 'gh-ext': '📦 manager: gh-ext', 'guix': '📦 manager: guix', 'macports': '📦 manager: macports', 'mas': '📦 manager: mas', 'mise': '📦 manager: mise', 'mpm': '📦 manager: mpm', 'nix': '📦 manager: nix', 'npm': '📦 manager: npm-based', 'opkg': '📦 manager: dpkg-based', 'pacaur': '📦 manager: pacman-based', 'pacman': '📦 manager: pacman-based', 'pacstall': '📦 manager: dpkg-based', 'paru': '📦 manager: pacman-based', 'pip': '📦 manager: pip-based', 'pipx': '📦 manager: pip-based', 'pkcon': '📦 manager: pkcon', 'pkg': '📦 manager: pkg-based', 'pkg-tools': '📦 manager: pkg-tools', 'pkgin': '📦 manager: pkgin', 'pnpm': '📦 manager: npm-based', 'ports': '📦 manager: pkg-based', 'pwsh-gallery': '📦 manager: pwsh-gallery', 'scoop': '📦 manager: scoop-based', 'sdkman': '📦 manager: sdkman', 'sfsu': '📦 manager: scoop-based', 'slapt-get': '📦 manager: slapt-get', 'snap': '📦 manager: snap', 'soar': '📦 manager: soar', 'sorcery': '📦 manager: sorcery', 'steamcmd': '📦 manager: steamcmd', 'stew': '📦 manager: stew', 'sun-tools': '📦 manager: sun-tools', 'swupd': '📦 manager: swupd', 'tazpkg': '📦 manager: tazpkg', 'tlmgr': '📦 manager: tlmgr', 'topgrade': '📦 manager: topgrade', 'urpmi': '📦 manager: rpm-based', 'uv': '📦 manager: uv-based', 'uvx': '📦 manager: uv-based', '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', 'ChromeOS': '🖥 platform: Linux', 'Clear Linux OS': '🖥 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', 'SliTaz GNU/Linux': '🖥 platform: Linux', 'Solaris': '🖥 platform: Unix', 'Source Mage GNU/Linux': '🖥 platform: Linux', 'SunOS': '🖥 platform: BSD', 'Tuxedo OS': '🖥 platform: Linux', 'Ubuntu': '🖥 platform: Linux', 'Ultramarine': '🖥 platform: Linux', 'Void Linux': '🖥 platform: Linux', 'Windows': '🖥 platform: Windows', 'Windows Subsystem for Linux v1': '🖥 platform: Linux', 'Windows Subsystem for Linux v2': '🖥 platform: Linux', 'XenServer': '🖥 platform: Linux', 'illumos': '🖥 platform: Unix', 'macOS': '🖥 platform: macOS', 'openSUSE': '🖥 platform: Linux', '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: apt-cyg', '#bfdadc', 'apt-cyg'), ('📦 manager: asdf', '#bfdadc', 'asdf'), ('📦 manager: cargo', '#bfdadc', 'cargo'), ('📦 manager: cave', '#bfdadc', 'cave'), ('📦 manager: choco', '#bfdadc', 'choco'), ('📦 manager: chromebrew', '#bfdadc', 'chromebrew'), ('📦 manager: composer', '#bfdadc', 'composer'), ('📦 manager: conda', '#bfdadc', 'conda'), ('📦 manager: cpan', '#bfdadc', 'cpan'), ('📦 manager: dpkg-based', '#bfdadc', 'apt, apt-mint, deb-get, opkg, pacstall'), ('📦 manager: emerge', '#bfdadc', 'emerge'), ('📦 manager: eopkg', '#bfdadc', 'eopkg'), ('📦 manager: fink', '#bfdadc', 'fink'), ('📦 manager: flatpak', '#bfdadc', 'flatpak'), ('📦 manager: fwupd', '#bfdadc', 'fwupd'), ('📦 manager: gem', '#bfdadc', 'gem'), ('📦 manager: gh-ext', '#bfdadc', 'gh-ext'), ('📦 manager: guix', '#bfdadc', 'guix'), ('📦 manager: homebrew', '#bfdadc', 'brew, cask, zerobrew'), ('📦 manager: macports', '#bfdadc', 'macports'), ('📦 manager: mas', '#bfdadc', 'mas'), ('📦 manager: mise', '#bfdadc', 'mise'), ('📦 manager: mpm', '#bfdadc', 'mpm'), ('📦 manager: nix', '#bfdadc', 'nix'), ('📦 manager: npm-based', '#bfdadc', 'npm, pnpm, yarn, yarn-berry'), ('📦 manager: pacman-based', '#bfdadc', 'pacaur, pacman, paru, yay'), ('📦 manager: pip-based', '#bfdadc', 'pip, pipx'), ('📦 manager: pkcon', '#bfdadc', 'pkcon'), ('📦 manager: pkg-based', '#bfdadc', 'pkg, ports'), ('📦 manager: pkg-tools', '#bfdadc', 'pkg-tools'), ('📦 manager: pkgin', '#bfdadc', 'pkgin'), ('📦 manager: pwsh-gallery', '#bfdadc', 'pwsh-gallery'), ('📦 manager: rpm-based', '#bfdadc', 'dnf, dnf5, urpmi, yum, zypper'), ('📦 manager: scoop-based', '#bfdadc', 'scoop, sfsu'), ('📦 manager: sdkman', '#bfdadc', 'sdkman'), ('📦 manager: slapt-get', '#bfdadc', 'slapt-get'), ('📦 manager: snap', '#bfdadc', 'snap'), ('📦 manager: soar', '#bfdadc', 'soar'), ('📦 manager: sorcery', '#bfdadc', 'sorcery'), ('📦 manager: steamcmd', '#bfdadc', 'steamcmd'), ('📦 manager: stew', '#bfdadc', 'stew'), ('📦 manager: sun-tools', '#bfdadc', 'sun-tools'), ('📦 manager: swupd', '#bfdadc', 'swupd'), ('📦 manager: tazpkg', '#bfdadc', 'tazpkg'), ('📦 manager: tlmgr', '#bfdadc', 'tlmgr'), ('📦 manager: topgrade', '#bfdadc', 'topgrade'), ('📦 manager: uv-based', '#bfdadc', 'uv, uvx'), ('📦 manager: vscode-based', '#bfdadc', 'vscode, vscodium'), ('📦 manager: winget', '#bfdadc', 'winget'), ('📦 manager: xbps', '#bfdadc', 'xbps'), ('🔌 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, ChromeOS, …'), ('🖥 platform: macOS', '#bfd4f2', 'macOS'), ('🖥 platform: Unix', '#bfd4f2', 'Cygwin, GNU/Hurd, Haiku, IBM AIX, IBM i, illumos, Solaris'), ('🖥 platform: Windows', '#bfd4f2', 'Windows')]¶
Global registry of all labels used in the project.
Structure:
("label_name", "color", "optional_description")
- meta_package_manager.labels.CONTENT_RULES_STATIC: TLabelRules = [('🔌 bar-plugin', ('plugin', 'swiftbar', 'xbar'))]¶
Content rules for labels that are not derived from the pool.
- meta_package_manager.labels.FILE_RULES_STATIC: TLabelRules = [('🔌 bar-plugin', ('meta_package_manager/bar_plugin*', 'tests/*bar_plugin*')), ('📦 manager: mpm', ('meta_package_manager/*',))]¶
File rules for labels that are not derived from the pool.
mpmgets no content rule: as the project’s own name it would match nearly every issue and PR.
- meta_package_manager.labels.MANAGER_CONTENT_KEYWORDS: dict[str, tuple[str, ...]] = {'apk': ('alpine', 'alpine linux'), 'apm': ('atom',), 'apt-cyg': ('cygwin',), 'asdf': ('asdf-vm', 'version manager'), 'cargo': ('crate', 'rust'), 'cave': ('exherbo', 'paludis'), 'choco': ('chocolatey',), 'chromebrew': ('chrome os', 'chromeos'), 'composer': ('php',), 'conda': ('anaconda', 'conda-forge', 'miniconda'), 'cpan': ('perl',), 'dpkg-based': ('aptitude', 'debian', 'dpkg', 'mint', 'ubuntu'), 'emerge': ('gentoo', 'portage'), 'eopkg': ('solus',), 'flatpak': ('flat',), 'fwupd': ('fwupdmgr', 'lvfs'), 'gem': ('ruby',), 'gh-ext': ('gh extension', 'github cli'), 'guix': ('gnu guix',), 'homebrew': ('formula', 'homebrew', 'tap', 'zb'), 'macports': ('port',), 'mas': ('app store', 'app-store'), 'nix': ('nixos', 'nixpkgs'), 'npm-based': ('node',), 'pacman-based': ('arch',), 'pkcon': ('packagekit',), 'pkg-based': ('freebsd', 'freebsd ports'), 'pkg-tools': ('openbsd', 'pkg_add'), 'pkgin': ('netbsd', 'pkgsrc'), 'pwsh-gallery': ('powershell', 'powershell gallery', 'psgallery', 'psresourceget', 'pwsh'), 'rpm-based': ('fedora', 'mageia', 'opensuse', 'redhat', 'rhel', 'rpm', 'suse'), 'sdkman': ('sdk man',), 'slapt-get': ('slackware',), 'sorcery': ('source mage',), 'steamcmd': ('steam', 'valve'), 'sun-tools': ('pkgadd', 'pkgrm', 'solaris', 'svr4'), 'swupd': ('clear linux', 'clearlinux'), 'tazpkg': ('slitaz',), 'tlmgr': ('ctan', 'tex live', 'texlive'), 'vscode-based': ('visual studio', 'visual studio code'), 'xbps': ('void', 'void linux')}¶
Curated ecosystem synonyms feeding each manager label’s content rule.
Keyed by the manager or group ID the label derives from. The rule’s baseline patterns (the member manager IDs) come for free from the pool: only add here the distro names, language names and aliases users actually type in issues.
- meta_package_manager.labels.PLATFORM_CONTENT_KEYWORDS: dict[str, tuple[str, ...]] = {'BSD': ('bsd',), 'Linux': ('linux',), 'Unix': ('unix',), 'Windows': ('c:', 'microsoft', 'windows'), 'macOS': ('apple', 'mac os', 'macos', 'os x', 'osx')}¶
Curated keyword patterns feeding each platform label’s content rule.
- meta_package_manager.labels.generate_content_rules()[source]¶
Build every content rule: static ones plus one per manager and platform label.
A manager label’s patterns are its member IDs plus the curated
MANAGER_CONTENT_KEYWORDSsynonyms. Rules are sorted by label, patterns alphabetically, both case-insensitively.
- meta_package_manager.labels.generate_file_rules()[source]¶
Build every file rule: static ones plus one per manager label.
A manager label matches its members’ definition files (Python modules and bundled TOML files alike, anchored on the full stem so
pkg.*never swallowspkgin.tomlorpkcon.py) and any test file carrying a member’s stem or ID. Platform labels have no file rule: no file is platform-specific.
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.
- split_name_version(token)[source]¶
Split a dash-joined
<package_id>-<version>token into its two parts.Matches
tokenagainst_NAME_VERSION_REGEXP(or the subclass’s override of it) and returns the(package_id, version)pair, orNonewhen the token carries no recognizable version. Shared by every manager whose listings glue the name and version together.
- parse_json(output)[source]¶
Parse a query’s JSON
output, tolerating empty and malformed captures.The shared first step of every JSON-emitting query, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._iter_parsed()). ReturnsNonewhen the command produced no output (a manager with nothing to report often prints nothing at all), and when the output is not valid JSON, which logs one warning tagged with the manager ID instead of raising: a query that cannot be parsed yields no packages, mirroring how the fan-out commands swallow a failed CLI call into an empty result.Queries whose failure semantics differ keep their own parsing: a per-line NDJSON stream (
pkg search), a hardCLIErroron malformed payloads (pwsh-gallery), a best-effort metadata enrichment logging atDEBUG(brew info).
- 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.homebrew.Cask'>, <class 'meta_package_manager.managers.composer.Composer'>, <class 'meta_package_manager.managers.conda.Conda'>, <class 'meta_package_manager.managers.deb_get.Deb_Get'>, <class 'meta_package_manager.managers.dnf.DNF'>, <class 'meta_package_manager.managers.dnf.DNF5'>, <class 'meta_package_manager.managers.emerge.Emerge'>, <class 'meta_package_manager.managers.eopkg.EOPKG'>, <class 'meta_package_manager.managers.flatpak.Flatpak'>, <class 'meta_package_manager.managers.fwupd.FWUPD'>, <class 'meta_package_manager.managers.gem.Gem'>, <class 'meta_package_manager.managers.guix.Guix'>, <class 'meta_package_manager.managers.mas.MAS'>, <class 'meta_package_manager.managers.mise.Mise'>, <class 'meta_package_manager.managers.nix.Nix'>, <class 'meta_package_manager.managers.npm.NPM'>, <class 'meta_package_manager.managers.pacman.Pacaur'>, <class 'meta_package_manager.managers.pacman.Pacman'>, <class 'meta_package_manager.managers.pacstall.Pacstall'>, <class 'meta_package_manager.managers.pacman.Paru'>, <class 'meta_package_manager.managers.pip.Pip'>, <class 'meta_package_manager.managers.pipx.Pipx'>, <class 'meta_package_manager.managers.pkcon.Pkcon'>, <class 'meta_package_manager.managers.pkg.PKG'>, <class 'meta_package_manager.managers.pnpm.PNPM'>, <class 'meta_package_manager.managers.pkg.Ports'>, <class 'meta_package_manager.managers.pwsh_gallery.PWSH_Gallery'>, <class 'meta_package_manager.managers.scoop.Scoop'>, <class 'meta_package_manager.managers.sdkman.SDKMAN'>, <class 'meta_package_manager.managers.sfsu.SFSU'>, <class 'meta_package_manager.managers.snap.Snap'>, <class 'meta_package_manager.managers.sun_tools.Sun_Tools'>, <class 'meta_package_manager.managers.tazpkg.Tazpkg'>, <class 'meta_package_manager.managers.uv.UV'>, <class 'meta_package_manager.managers.uv.UVX'>, <class 'meta_package_manager.managers.winget.WinGet'>, <class 'meta_package_manager.managers.xbps.XBPS'>, <class 'meta_package_manager.managers.yarn.YarnBerry'>, <class 'meta_package_manager.managers.yarn.YarnClassic'>, <class 'meta_package_manager.managers.pacman.Yay'>, <class 'meta_package_manager.managers.dnf.YUM'>, <class 'meta_package_manager.managers.zypper.Zypper'>)¶
The list of all classes implementing the specific package managers.
Is considered valid package manager, definitions classes which:
- 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', 'sudo', 'timeout'})¶
List of extra options that are allowed to be set on managers during the use of the
meta_package_manager.pool.ManagerPool.select_managers()helper below.
- property register: dict[str, PackageManager]¶
Instantiate all supported package managers.
Built-in classes first, then mpm’s bundled configuration-defined managers (built from shipped
*.tomlpackage data). Both land here at construction time, so the augmented pool is complete before the CLI enumerates it to build the dynamic--<id>flags, in every context including the test runner.
- property builtin_manager_ids: frozenset[str]¶
IDs of the managers shipped with mpm, taken from
manager_classes.Computed from the classes (their
idis set by the metaclass at class creation, no instantiation needed). Lets the configuration layer tell a built-in override apart from a brand-new manager definition: a[mpm.managers.<id>]section whose ID is in this set tunes a built-in, any other ID defines a new manager. Seemeta_package_manager.config.validate_manager_overrides_section().
- property config_defined_ids: set[str]¶
IDs of managers added at runtime from configuration definitions.
Populated by
add_manager(). Disjoint frombuiltin_manager_ids.
- property bundled_manager_ids: frozenset[str]¶
IDs of the managers mpm ships as bundled configuration definitions.
Config-defined (built from shipped
*.tomlpackage data, not a Python class) yet always present inregisterlike the built-ins. Disjoint frombuiltin_manager_idsandconfig_defined_ids.
- property known_manager_ids: frozenset[str]¶
Every manager ID mpm ships: built-in classes plus bundled definitions.
A
[mpm.managers.<id>]section keyed by one of these tunes a shipped manager (an override); any other ID defines a brand-new one. The configuration layer routes override-versus-definition on this set. Seemeta_package_manager.config.validate_manager_overrides_section().
- property overridden_fields: dict[str, set[str]]¶
Per-manager attribute names that the user explicitly overrode via
[mpm.managers.<id>].Populated by
meta_package_manager.config.apply_manager_overrides(). Read by_select_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)¶
- add_manager(manager)[source]¶
Register a runtime-built manager (from a config definition) into the pool.
Inserts the instance and evicts the cached ID lists so the new manager is picked up by selection, default-set computation and the dynamic CLI flags. Built into the pool (rather than mutating
registerfrom outside) so the cache invalidation stays in one place. Applied bymeta_package_manager.config.register_config_managers().- Return type:
- 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.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.sudo module¶
Privilege-escalation machinery for the mutating fan-outs.
This module owns sudo credential priming (prime_sudo()) and its
background keepalive (_start_sudo_keepalive()), escalation-policy
resolution (_resolved_sudo()), sudo-failure detection
(_is_sudo_auth_failure()), and the hidden-prompt stall watchdog
(_StallWatchdog). The execution engine
(meta_package_manager.execution) consumes the policy pieces to wrap and
diagnose escalated commands; the CLI calls prime_sudo() at the top of
each mutating subcommand.
Why priming exists: a concurrent state-changing command mutes per-manager output
and feeds each child stdin=/dev/null, so a sudo password prompt raised
mid-run (by mpm’s own sudo --non-interactive or by a manager that escalates
internally, like
Homebrew cask) lands invisibly on /dev/tty and can stall the run up to the
mutating timeout. Priming first probes the credential cache non-interactively:
found warm, it is silently kept alive for the whole run; found cold on a terminal,
the managers mpm itself escalates get a single up-front password prompt, naming
them and branded [mpm]. Internal escalators never prompt up front: their rare
cold-cache escalation is covered by the silent-call stall notice instead, raised
while the hidden prompt can still be answered.
Note
Everything in this module is UNIX-only: a Windows run returns early at
prime_sudo()’s guard and never arms the watchdog (the internal
escalators are macOS-only managers today).
- meta_package_manager.sudo.prime_sudo(ctx, managers)[source]¶
Warm the
sudocredential cache, up front, for a mutating fan-out.Probes the cache non-interactively (
sudo --non-interactive --validate) before considering any prompt. A warm cache (pre-authenticatedsudo --validate, aNOPASSWDrule, a recent run) is silently kept fresh for the whole invocation by_start_sudo_keepalive(), so every later escalation on the same terminal, mpm’s ownsudo --non-interactiveas well as a manager’s internalsudo(CLIExecutor.internal_sudo), spends the cache instead of blocking on an invisible prompt inside the concurrent fan-out. Only a cold cache, on an interactive terminal, with managers that mpm itself escalates (_resolved_sudo()), triggers the interactive path: a notice naming the managers and the subcommand, then a single brandedsudopassword prompt.Call at the top of each mutating subcommand, before the fan-out draws its spinner. Never prompts when:
Windows (no
sudo) or the process is already root,no selected manager escalates, through mpm or internally,
a dry run (no real CLI is executed),
already primed once this invocation (idempotent),
the
sudoexecutable is missing (one warning is logged),the probe finds the cache already warm (keepalive only, fully silent),
no interactive terminal is available: one warning names the managers mpm escalates and leaves them to fail fast rather than block on a prompt no one can answer, while an internal-only selection stays silent, or
only internal escalators are selected on a cold cache: most such runs never escalate, so the rare mid-run prompt is covered by the silent-call stall notice instead.
- Return type:
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.tables module¶
Table-output vocabulary and rendering plumbing shared by the subcommands.
The mpm subcommands render heterogeneous tables (different columns per command) but share the same output machinery. This module owns all of it:
SortableField, the vocabulary of the globalmpm --sort-byselector. The selector itself is click-extra’s field-vocabularySortByOption, and the per-table resolution (sort by the selected fields the table carries, keep the original row order when it carries none) happens insideclick_extra.table.print_table(), from the field each header pairs with its column in the registries below.The per-command column registries, each pairing a click-extra
ColumnSpec(whose ID addresses the column from--columns) with theSortableFieldthe column carries (Nonefor a column that cannot drive the sort). A registry is the single source of truth for its command: the same tuple feeds the@columns_optiondeclaration (which validates the user selection) andprint_projected_table()(which projects headers and rows before rendering).print_projected_table()andprint_serialized_and_exit(), the human-friendly and machine-friendly rendering paths every table-producing subcommand goes through.
Note
The registry pairs’ second element is annotated str | None rather than
SortableField | None: on Python 3.10, SortableField extends
backports.strenum.StrEnum, whose stubs type the members as plain
str, so the tighter annotation only checks under 3.11+.
StrEnum members being str subclasses, the wider annotation is
accurate on every supported version.
- class meta_package_manager.tables.SortableField(*values)[source]¶
Bases:
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.tables.MANAGERS_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='manager_id', label='Manager ID', description="Manager's identifier."), SortableField.MANAGER_ID), (ColumnSpec(id='manager_name', label='Name', description="Manager's common name."), SortableField.MANAGER_NAME), (ColumnSpec(id='supported', label='Supported', description='Support status on the current platform.'), None), (ColumnSpec(id='cli', label='CLI', description="Location of the manager's binary on the system."), None), (ColumnSpec(id='executable', label='Executable', description='Whether the binary is executable.'), None), (ColumnSpec(id='version', label='Version', description="Manager's self-reported version, and the unsatisfied requirement when stale."), SortableField.VERSION))¶
Columns of the
mpm managerstable.
- meta_package_manager.tables.INSTALLED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.'), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.'), SortableField.VERSION))¶
Columns of the
mpm installedtable.
- meta_package_manager.tables.OUTDATED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.'), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.'), SortableField.VERSION), (ColumnSpec(id='latest_version', label='Latest version', description='Version available for upgrade.'), None))¶
Columns of the
mpm outdatedtable.
- meta_package_manager.tables.SEARCH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier."), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name."), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the match.'), SortableField.MANAGER_ID), (ColumnSpec(id='latest_version', label='Latest version', description='Latest version available.'), SortableField.VERSION), (ColumnSpec(id='description', label='Description', description='Package description, for managers that provide one. Out of the default selection: select it explicitly or pass --description.'), None))¶
Columns of the
mpm searchtable.The
descriptioncolumn exists in the registry (so--columnscan select it) but stays out of the default selection unless--description(or--extended, which searches descriptions) is passed.
- meta_package_manager.tables.WHICH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='manager_id', label='Manager ID', description='Manager whose search path found the binary.'), SortableField.MANAGER_ID), (ColumnSpec(id='priority', label='Priority', description="Rank of the match in the manager's search path."), None), (ColumnSpec(id='cli_path', label='CLI path', description='Location of the matched binary.'), None), (ColumnSpec(id='symlink', label='Symlink destination', description='Resolved target when the match is a symlink.'), None))¶
Columns of the
mpm whichtable.
- meta_package_manager.tables.column_specs(columns)[source]¶
Extract the bare
ColumnSpectuple from a column registry.- Return type:
- meta_package_manager.tables.print_projected_table(ctx, columns, rows, default_ids=None)[source]¶
Render dict
rowsas a table projected through--columns.The
--columnsselection restricts and reorders the rendering, SQL-SELECT-style; click-extra’sColumnsOptionalready validated it against the samecolumnsregistry, so unknown IDs never reach this point.default_idsis the selection applied when the user passed none (searchuses it to hide the description column unless--description);Nonekeeps every column in canonical order.Sorting stays on mpm’s global
--sort-by: each header pairs its label with the sortable field the column carries, and click-extra’sprint_table()resolves the selection per table. A sort field whose column is projected out is simply skipped, and a table carrying none of the selected fields keeps its original row order.- Return type:
- meta_package_manager.tables.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.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.”
univers — scheme-aware version parsing and comparison (PEP 440, semver, Debian, RPM, Gentoo ebuild, and more) plus the
versrange spec, from the same AboutCode team maintaining purl. The reference implementation to evaluate if this heuristic comparator ever needs per-scheme accuracy.
- meta_package_manager.version.ALNUM_EXTRACTOR_CI = re.compile('(\n (?= [0-9a-f]* [a-f] [0-9] )\n (?= [0-9a-f]* [0-9] [a-f] )\n [0-9a-f]{7,}\n | \\d+\n | [a-z]+\n)', re.IGNORECASE|re.VERBOSE)¶
Case-insensitive variant used to split the original string and preserve case.
- meta_package_manager.version.TOKEN_ALIASES: dict[str, str] = {'alpha': 'a', 'beta': 'b', 'c': 'rc', 'preview': 'rc'}¶
Canonical short forms for pre-release tag spellings.
PEP 440 defines
alpha/a,beta/b, 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.