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.am module
- meta_package_manager.managers.antidote module
antidote_source_path()AntidoteAntidote.homepage_urlAntidote.logoAntidote.platformsAntidote.requirementAntidote.cli_namesAntidote.extra_envAntidote.version_regexesAntidote.idAntidote.nameAntidote.virtualAntidote.build_cli()Antidote.installedAntidote.outdatedAntidote.install()Antidote.upgrade_all_cli()Antidote.remove()
- meta_package_manager.managers.antigen module
- meta_package_manager.managers.apk module
- meta_package_manager.managers.apm module
- meta_package_manager.managers.apt module
APTAPT.nameAPT.homepage_urlAPT.logoAPT.platformsAPT.default_sudoAPT.requirementAPT.pre_argsAPT.version_regexesAPT.installedAPT.outdatedAPT.orphansAPT.search()APT.install()APT.upgrade_all_cli()APT.upgrade_one_cli()APT.remove()APT.remove_orphan()APT.sync()APT.cleanup_orphan()APT.cleanup_cache()APT.doctor_cli()APT.cli_namesAPT.idAPT.virtual
APT_Mint
- meta_package_manager.managers.asdf module
- meta_package_manager.managers.bin module
- meta_package_manager.managers.composer module
ComposerComposer.nameComposer.homepage_urlComposer.logoComposer.platformsComposer.requirementComposer.pre_argsComposer.version_regexesComposer.installedComposer.outdatedComposer.search()Composer.install()Composer.upgrade_all_cli()Composer.upgrade_one_cli()Composer.remove()Composer.cleanup_cache()Composer.cli_namesComposer.doctor_cli()Composer.idComposer.virtual
- meta_package_manager.managers.conda module
- meta_package_manager.managers.deb_get module
Deb_GetDeb_Get.nameDeb_Get.homepage_urlDeb_Get.logoDeb_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_cache()Deb_Get.cli_namesDeb_Get.idDeb_Get.virtual
- meta_package_manager.managers.dnf module
DNFDNF.maintenance_noteDNF.nameDNF.homepage_urlDNF.logoDNF.platformsDNF.default_sudoDNF.requirementDNF.cli_namesDNF.pre_argsDNF.DELIMITERDNF.installedDNF.outdatedDNF.orphansDNF.search()DNF.install()DNF.upgrade_all_cli()DNF.upgrade_one_cli()DNF.remove()DNF.remove_orphan()DNF.sync()DNF.cleanup_orphan()DNF.cleanup_cache()DNF.doctor_cli()DNF.idDNF.virtual
DNF5YUM
- meta_package_manager.managers.dotnet module
- meta_package_manager.managers.emerge module
EmergeEmerge.nameEmerge.homepage_urlEmerge.logoEmerge.platformsEmerge.default_sudoEmerge.requirementEmerge.pre_argsEmerge.version_regexesEmerge.installedEmerge.outdatedEmerge.orphansEmerge.search()Emerge.install()Emerge.upgrade_all_cli()Emerge.upgrade_one_cli()Emerge.remove()Emerge.sync()Emerge.cleanup_orphan()Emerge.cli_namesEmerge.idEmerge.virtualEmerge.cleanup_cache()
- meta_package_manager.managers.eopkg module
EOPKGEOPKG.nameEOPKG.homepage_urlEOPKG.logoEOPKG.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_cache()EOPKG.cli_namesEOPKG.idEOPKG.virtual
- meta_package_manager.managers.fisher module
- meta_package_manager.managers.flatpak module
FlatpakFlatpak.homepage_urlFlatpak.logoFlatpak.brewfile_entry_typeFlatpak.platformsFlatpak.requirementFlatpak.version_regexesFlatpak.installedFlatpak.outdatedFlatpak.search()Flatpak.install()Flatpak.upgrade_all_cli()Flatpak.upgrade_one_cli()Flatpak.remove()Flatpak.cleanup_orphan()Flatpak.cleanup_repair()Flatpak.cli_namesFlatpak.idFlatpak.nameFlatpak.virtualFlatpak.doctor_cli()
- meta_package_manager.managers.fwupd module
- meta_package_manager.managers.gem module
- meta_package_manager.managers.gext module
- meta_package_manager.managers.ghcup 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_orphan()Homebrew.cleanup_cache()Homebrew.doctor_cli()Homebrew.cli_namesHomebrew.idHomebrew.name
BrewCask
- meta_package_manager.managers.lazy module
- meta_package_manager.managers.luarocks module
- meta_package_manager.managers.mamba module
- meta_package_manager.managers.mas module
- meta_package_manager.managers.miktex module
- meta_package_manager.managers.mason module
- meta_package_manager.managers.mise module
- meta_package_manager.managers.nala module
NalaNala.nameNala.homepage_urlNala.logoNala.platformsNala.default_sudoNala.requirementNala.extra_envNala.version_regexesNala.installedNala.outdatedNala.search()Nala.cli_namesNala.idNala.install()Nala.virtualNala.upgrade_all_cli()Nala.upgrade_one_cli()Nala.remove()Nala.sync()Nala.cleanup_orphan()Nala.cleanup_cache()
- meta_package_manager.managers.nimble module
- meta_package_manager.managers.nix module
- meta_package_manager.managers.npm module
NPMNPM.nameNPM.homepage_urlNPM.logoNPM.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.upgrade_one_cli()NPM.cli_namesNPM.idNPM.remove()NPM.virtualNPM.cleanup_cache()NPM.doctor_cli()
- meta_package_manager.managers.oh_my_fish module
- meta_package_manager.managers.pacman module
PacmanPacman.namePacman.homepage_urlPacman.logoPacman.platformsPacman.default_sudoPacman.requirementPacman.pre_argsPacman.version_regexesPacman.installedPacman.outdatedPacman.orphansPacman.search()Pacman.install()Pacman.upgrade_all_cli()Pacman.upgrade_one_cli()Pacman.remove()Pacman.remove_orphan()Pacman.sync()Pacman.cleanup_cache()Pacman.doctor_cli()Pacman.cli_namesPacman.idPacman.virtual
DkpPacmanPacaurParuPikaurTrizenYay
- meta_package_manager.managers.pacstall module
PacstallPacstall.homepage_urlPacstall.platformsPacstall.internal_sudoPacstall.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.logoPip.platformsPip.requirementPip.cooldown_env_varPip.cli_namesPip.pre_argsPip.version_cli_optionsPip.version_regexesPip.search_all_cli()Pip.versionPip.installedPip.package_metadata_batch()Pip.outdatedPip.install()Pip.idPip.upgrade_one_cli()Pip.virtualPip.remove()Pip.cleanup_cache()Pip.doctor_cli()
- meta_package_manager.managers.pipx module
- meta_package_manager.managers.pixi module
- meta_package_manager.managers.pkcon module
PkconPkcon.namePkcon.homepage_urlPkcon.logoPkcon.platformsPkcon.requirementPkcon.extra_envPkcon.post_argsPkcon.version_regexesPkcon.installedPkcon.outdatedPkcon.search()Pkcon.install()Pkcon.cli_namesPkcon.idPkcon.upgrade_all_cli()Pkcon.virtualPkcon.upgrade_one_cli()Pkcon.remove()Pkcon.sync()
- meta_package_manager.managers.pkg module
PORTS_TREEPKGPKG.namePKG.homepage_urlPKG.logoPKG.platformsPKG.default_sudoPKG.requirementPKG.pre_argsPKG.installedPKG.outdatedPKG.orphansPKG.search()PKG.install()PKG.upgrade_all_cli()PKG.upgrade_one_cli()PKG.remove()PKG.sync()PKG.cleanup_orphan()PKG.cleanup_cache()PKG.doctor_cli()PKG.cli_namesPKG.idPKG.virtual
PortsPorts.namePorts.homepage_urlPorts.logoPorts.platformsPorts.default_sudoPorts.cli_namesPorts.extra_envPorts.version_cli_optionsPorts.version_regexesPorts.availablePorts.idPorts.installedPorts.virtualPorts.outdatedPorts.install()Ports.upgrade_all_cli()Ports.upgrade_one_cli()Ports.removePorts.sync()Ports.cleanup_cache()
- 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_cache()Scoop.cli_namesScoop.doctor_cli()Scoop.idScoop.virtual
- meta_package_manager.managers.sdkman module
SDKMANSDKMAN.homepage_urlSDKMAN.logoSDKMAN.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_cache()
- meta_package_manager.managers.sfsu module
- meta_package_manager.managers.sheldon module
- meta_package_manager.managers.snap module
- meta_package_manager.managers.sun_tools module
Sun_ToolsSun_Tools.maintenance_noteSun_Tools.nameSun_Tools.homepage_urlSun_Tools.platformsSun_Tools.default_sudoSun_Tools.cli_namesSun_Tools.cli_search_pathSun_Tools.version_cliSun_Tools.version_cli_optionsSun_Tools.version_regexesSun_Tools.idSun_Tools.installedSun_Tools.virtualSun_Tools.remove()
- meta_package_manager.managers.tazpkg module
TazpkgTazpkg.maintenance_noteTazpkg.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_cache()
- meta_package_manager.managers.uv module
- meta_package_manager.managers.vagrant module
VagrantVagrant.maintenance_noteVagrant.nameVagrant.homepage_urlVagrant.logoVagrant.platformsVagrant.requirementVagrant.extra_envVagrant.version_regexesVagrant.installedVagrant.outdatedVagrant.search()Vagrant.cli_namesVagrant.idVagrant.install()Vagrant.virtualVagrant.upgrade_one_cli()Vagrant.remove()Vagrant.cleanup_orphan()
- meta_package_manager.managers.vcpkg module
- meta_package_manager.managers.vim_pack module
PLUGIN_LISTlua_string()lua_command()lua_resolve()Vim_PackVim_Pack.nameVim_Pack.homepage_urlVim_Pack.logoVim_Pack.platformsVim_Pack.requirementVim_Pack.cli_namesVim_Pack.pre_argsVim_Pack.post_argsVim_Pack.version_regexesVim_Pack.idVim_Pack.installedVim_Pack.virtualVim_Pack.install()Vim_Pack.upgrade_all_cli()Vim_Pack.upgrade_one_cli()Vim_Pack.remove()
- meta_package_manager.managers.volta 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
XBPSXBPS.nameXBPS.homepage_urlXBPS.logoXBPS.platformsXBPS.default_sudoXBPS.requirementXBPS.cli_namesXBPS.installedXBPS.outdatedXBPS.orphansXBPS.search()XBPS.install()XBPS.upgrade_all_cli()XBPS.upgrade_one_cli()XBPS.remove()XBPS.idXBPS.remove_orphan()XBPS.virtualXBPS.sync()XBPS.cleanup_orphan()XBPS.cleanup_cache()XBPS.doctor_cli()
- meta_package_manager.managers.yarn module
YarnYarnClassicYarnClassic.maintenance_noteYarnClassic.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.zef module
- meta_package_manager.managers.zim module
- meta_package_manager.managers.zinit module
- meta_package_manager.managers.zplug module
- meta_package_manager.managers.zypper module
ZypperZypper.nameZypper.homepage_urlZypper.logoZypper.platformsZypper.default_sudoZypper.requirementZypper.pre_argsZypper.version_regexesZypper.installedZypper.outdatedZypper.orphansZypper.search()Zypper.install()Zypper.upgrade_all_cli()Zypper.upgrade_one_cli()Zypper.remove()Zypper.remove_orphan()Zypper.cli_namesZypper.idZypper.sync()Zypper.virtualZypper.cleanup_cache()
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, 0)¶
SwiftBar
2.1.0fixes an issue with multiple parameters in the font strings.The fix was first handed out as a
2.1.2-labelled test build, a number that never reached a release: the public train renumbered it down to2.1.0. Requiring the build we validated on would lock the plugin out of every released SwiftBar.
- 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_STATES to translate strings into boolean.
- 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 documented by both hosts:
- 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[source]¶
Aligns package names and versions, like a table, for easier visual parsing.
If
True, will aligns all items using a fixed-width font.
- property monospace_font: str[source]¶
Make it easier to change font, sizes and colors of the output.
- 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]][source]¶
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][source]¶
- static pp(label, *args)[source]¶
Print one menu-line with the Xbar/SwiftBar dialect.
First argument is the menu-line label, separated by a pipe to all other non- empty parameters, themselves separated by a space.
Skip printing of the line if label is empty. A
Noneparameter renders nothing, so a package without an upgrade CLI still gets its label-only menu line.- 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.
- meta_package_manager.bar_plugin_renderer.VERSION_PREFIX_COLOR = 245¶
Xterm-256 palette index coloring the unchanged version prefix in menu lines.
The CLI table keeps
meta_package_manager.version.diff_versions()’s defaultbright_black(SGR90), which terminals remap to their own theme. SwiftBar instead hard-maps SGR90to a fixedNSColor.darkGray, near-invisible on a dark-mode menu, while its 256-color support renders palette index245as a theme-neutral mid-gray (#8a8a8a), legible on both appearances. Xbar strips the ANSI codes it does not render, so the choice is inert there.
- meta_package_manager.bar_plugin_renderer.LIGHT_MENU_OLD_COLOR = 124¶
Palette index for the old-version (red) suffix on a light-appearance menu.
#af0000, a6.5:1contrast ratio on the cream material. SeeBarPluginRenderer.menu_diff_colors()for why the override exists.
- meta_package_manager.bar_plugin_renderer.LIGHT_MENU_NEW_COLOR = 23¶
Palette index for the new-version (green) suffix on a light-appearance menu.
#006600, a6.3:1contrast ratio on the cream material. SeeBarPluginRenderer.menu_diff_colors().
- meta_package_manager.bar_plugin_renderer.DARK_MENU_NEW_COLOR = 46¶
Palette index for the new-version (green) suffix on a dark-appearance menu.
#00ff00, lifting the worst-case contrast from4.0:1(the adaptiveNSColor.systemGreen) to5.8:1over a bright wallpaper showing through the translucent menu. The old-version (red) suffix keepssystemRed: it is already the most readable recognizable red the xterm-256 palette can express (a pure#ff0000scores lower, and brighter options read as orange). SeeBarPluginRenderer.menu_diff_colors().
- 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.
Appearance-adaptive version-diff suffix colors for the menu.
SwiftBar maps
meta_package_manager.version.diff_versions()’s default SGR31/32suffixes to the adaptiveNSColor.systemRed/systemGreen, and exports the menu appearance in theOS_APPEARANCEenvironment variable (which propagates to the mpm outdated –plugin-output subprocess). On the translucent “Liquid Glass” menus of recent macOS releases these system colors lose contrast against the material, so override them per appearance:A light menu washes out both suffixes (the green measured
1.9:1), so darken them toLIGHT_MENU_OLD_COLORandLIGHT_MENU_NEW_COLOR.A dark menu over a bright wallpaper dims the green to
4.0:1, so brighten it toDARK_MENU_NEW_COLOR; the red keepssystemRed, already the most readable red the palette allows.
The result is returned as
diff_versionskeyword arguments. When the variable is absent (a consumer like Xbar, which strips these codes anyway) return an empty mapping, keeping the system-color defaults.
- property mpm_cli: tuple[str, ...][source]¶
Absolute
mpminvocation the menu actions are routed through.Re-enters the very interpreter rendering the menu, so a click runs the
mpmthe plugin called and resolves the same configuration file. Derived fromsys.executablerather thansys.argv[0]: the former is always an absolute path to a runnable entry point, while the latter degrades to a console script, a__main__.pyor a bare-cdepending on howmpmwas started. A Nuitka-compiledmpmis its own interpreter, so it is invoked directly instead of through the module.Note
The candidates
meta_package_manager.bar_plugin.MPMPlugin.search_mpm()produces are deliberately not reused here. The venv ones lead with a bareuv/pipenv/poetrycommand name, while a bar app spawns a menu action with the barelaunchdPATH, where such a name does not resolve.
- static render_cli(cmd_args)[source]¶
Return a formatted CLI compatible with Xbar and SwiftBar plugin format.
I.e. a string with this schema:
shell=cmd_args[0] param1=cmd_args[1] param2=cmd_args[2] ...
- Return type:
- 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 its<stdout>output.Every producer down the
_renderpath (the inheritedppandprint_errorincluded) writes through bareprintcalls, so redirecting<stdout>captures the whole rendering.- Return type:
- add_upgrade_cli(outdated_data)[source]¶
Augment the outdated data from
mpm outdatedsubcommand with upgrade CLI fields for bar plugin consumption.Every menu action is an
mpm_cliinvocation restricted to the manager owning the section (mpm --brew upgrade wget), never that manager’s own native command. Going back throughmpmis what subjects a click to the same policy as the run that rendered the menu: the configuration file found on the system, and with it the release-age cooldown, the manager selection, the sudo policy and the per-manager overrides. A native command escapes all of them, silently upgrading a packagempmitself would have held back.Only the manager selector and the operation are passed, so every other setting is resolved from the user’s configuration at click time.
A manager is offered the action only when it
implements()it, which is the same predicatempmuses to route the subcommand: a manager it would skip gets aNoneCLI and renders as a label-only menu line.
- print(outdated_data)[source]¶
Print the final plugin rendering to
<stdout>.Capturing the output of the plugin and re-printing it will introduce an extra line return, hence the extra call to
rstrip().Colors are forced on
echo’s auto-detection: the bar plugin capturesmpm outdated --plugin-outputthrough a pipe, whereechowould strip every ANSI code and the version-diff colors would never reach SwiftBar or Xbar. TTY detection is meaningless for this dialect, which flags ANSI rendering per line with theansi=true/ansi=falseparameters. An explicit opt-out (--color=never,NO_COLOR) is still honored: only the automatic (None) state is overridden.- 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'¶
- orphans = 'orphans'¶
- search = 'search'¶
- install = 'install'¶
- upgrade = 'upgrade'¶
- upgrade_all = 'upgrade_all'¶
- remove = 'remove'¶
- sync = 'sync'¶
- cleanup = 'cleanup'¶
- doctor = 'doctor'¶
- meta_package_manager.capabilities.implements(manager, op)[source]¶
Inspect a manager’s implementation to check for proper support of an operation.
Accepts either a manager instance or its class; support is determined from the class hierarchy. The verdict is narrated as a single answered
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 bymeta_package_manager._docs.- Return type:
- meta_package_manager.capabilities.implements_method(manager, method_name)[source]¶
Whether a non-base class in the manager’s MRO defines
method_name.The orphan refinements
remove_orphanandcleanup_orphanare optional variants of theremoveandcleanupcommands rather than standaloneOperations, soimplements()cannot route them. This reports whether a manager overrides the base’s stub for one, delegating the MRO walk tometa_package_manager.manager.PackageManager._defines()(shared with the basecleanupcomposer), so it works for config-defined managers (whose methods live on the synthesized subclass) too.- Return type:
- meta_package_manager.capabilities.cleanup_orphan_is_synthesized(manager)[source]¶
Whether
mpmbackfills the manager’s system-wide orphan sweep.Truewhen no class in the manager’s hierarchy overridescleanup_orphanwith a native sweep, but the manager implements both theorphansquery andremove: the basemeta_package_manager.manager.PackageManager.cleanup_orphan()then synthesizes the sweep by listing the orphans and removing them one by one, the exact pattern of the synthesized fullupgrade --all.Falsewhen a native sweep exists, or when the manager lacks the building blocks.Feeds the per-manager table of
docs/augmentations.md, rendered live bymeta_package_manager._docs.- Return type:
- meta_package_manager.capabilities.supports_cleanup_cache(manager)[source]¶
Whether
mpm cleanup --cachecan drive the manager.- Return type:
- meta_package_manager.capabilities.supports_cleanup_repair(manager)[source]¶
Whether
mpm cleanup --repaircan drive the manager.- Return type:
- meta_package_manager.capabilities.exact_search_is_synthesized(manager)[source]¶
Whether
mpmbackfills the manager’ssearch --exactrefinement.Truewhen the manager’s native search cannot filter exact matches, someta_package_manager.manager.PackageManager.refiltered_search()does the narrowing itself. Feeds the per-manager table ofdocs/augmentations.mdand the per-manager operation tables, rendered live bymeta_package_manager._docs.- Return type:
- meta_package_manager.capabilities.extended_search_is_synthesized(manager)[source]¶
Whether
mpmbackfills the manager’ssearch --extendedrefinement.Truewhen the manager’s native search cannot reach descriptions, someta_package_manager.manager.PackageManager.refiltered_search()does the filtering itself. Feeds the per-manager table ofdocs/augmentations.mdand the per-manager operation tables, rendered live bymeta_package_manager._docs.- 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: the group and its shared plumbing.
Defines the Click command group (global options, manager selection, the
GlobalOptions state every subcommand reads) and the helpers several
subcommand modules share: the inventory snapshot, the per-package action
engine, the failure gates and the file-output guards.
The subcommands themselves live in one module per help section —
meta_package_manager.cli_explore (the read-only queries),
meta_package_manager.cli_maintenance (the state changers and
diagnostics), meta_package_manager.cli_snapshots (manifest export and
replay) and meta_package_manager.cli_sbom — imported at the bottom of
this module so their @mpm.command registrations run. Each subcommand
selects the managers from meta_package_manager.pool that implement the
matching meta_package_manager.capabilities.Operations action, runs it
across all of them, and renders the aggregated, multi-manager result.
- meta_package_manager.cli.XKCD_MANAGER_ORDER = ('pip', 'brew', 'npm', 'dnf', 'apt', 'steamcmd')¶
Sequence of package managers as defined by XKCD #1654: Universal Install Script.
See the corresponding implementation rationale in issue #10.
- class meta_package_manager.cli.GlobalOptions(all_managers, user_selection, user_drops, selected_managers, description, summary, network, progress, timeout)[source]¶
Bases:
objectGlobal options and selection state every subcommand reads from
ctx.obj.Built once by the
mpmgroup body, after the eager option callbacks have accumulated the manager selectors into the transientctx.objdict this instance replaces (seeupdate_manager_selection()).- user_selection: list[str] | None¶
Managers explicitly selected by the user, in priority order, or
None.
- selected_managers: Callable[[...], Iterator[PackageManager]]¶
Resolve the target managers, applying selection and manager-level options.
- progress: bool¶
Whether progress indicators may render, after mpm’s output-mode gating.
The same value the group folds into each manager’s
progressflag, exposed for the subcommands resolving their own managers instead of going throughselected_managers(managers, whose only wait is the detection round the pool runs for it). The widget still auto-gates on an interactive stderr.
- 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.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.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.package_label(spec)[source]¶
Render a spec as
package_idorpackage_id@versionfor trail output.- Return type:
- meta_package_manager.cli.fail_unless_zero_exit(ctx, message)[source]¶
Print the durable
critical: :message:` record, then exit `1unless-0/--zero-exitopted out of the gate.The shared failure gate of the action commands (
exit_on_failures()) anddoctor: the summary always prints, following the linter convention where findings gate automation, and-0keeps the exit code at0with the printed summary staying the durable record. Usage and configuration errors are unaffected: they exit2regardless, as genuine execution failures.- 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 routes the deduplicated, sortedCould not {verb}: ...summary throughfail_unless_zero_exit(). Shared by every action command (install,remove,upgrade <packages>,restore).- Return type:
meta_package_manager.cli_explore module¶
The explore subcommands: the read-only queries and inspection tools.
managers, installed, outdated, orphans, search, which and
config-template, plus the query plumbing they share: the concurrent
collect prelude, the row builders and the query-match highlighter. Every
command here only reads system state and renders a table (or its serialized
counterpart).
The mpm group itself, and the plumbing shared with the other subcommand
modules, live in meta_package_manager.cli.
- meta_package_manager.cli_explore.MANAGER_VIEWS: Final[tuple[str, ...]] = ('detected', 'supported', 'all')¶
How much of the manager pool
mpm managersreports, widest last.Three nested sets: the managers detected on the system, then those this platform supports whether or not their CLI was found, then every manager
mpmimplements (unsupported and unmaintained ones included). Each maps onto the pool’s own selection knobs rather than filtering after the fact, so the views are thedrop_not_foundandkeep_unmaintainedcombinations named.Unrelated to
meta_package_manager.manager.ManagerScope, which is about the filesystem a manager operates on: hence view rather than scope.
- meta_package_manager.cli_explore.exact_match_option(f)¶
--exactrefinement of the optional positionalQUERYofinstalledandoutdated.
meta_package_manager.cli_maintenance module¶
The maintenance subcommands: the state changers and diagnostics.
install, upgrade, remove, sync, cleanup and doctor, plus
the machinery only they need: the cooldown gate, the sourced-operation
dispatch that resolves each package spec to its source managers, and the
cleanup category selection.
The mpm group itself, and the per-package action engine restore also
drives, live in meta_package_manager.cli.
- meta_package_manager.cli_maintenance.cooldown_permits(manager)[source]¶
Decide whether a release-introducing operation may run on
manager.Returns
Truewhen no cooldown is active, when the manager can enforce it natively, or when the user opted out of the requirement with--allow-unsupported-managersor itsrequire_cooldown_supportconfiguration key. 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.The skip message names both remedies, the one-shot flag and the persistent configuration key: opting out of a supply-chain safeguard is a standing policy decision, not something to re-type on every run.
- Return type:
- meta_package_manager.cli_maintenance.CLEANUP_CATEGORIES = ('orphans', 'cache', 'repair')¶
Cumulative categories the
cleanupsubcommand decomposes into.Each category has a two-sided
--<category>/--skip-<category>flag pair. Positive flags narrow the run to exactly the listed categories; skip flags subtract categories from the default selection.
- meta_package_manager.cli_maintenance.DEFAULT_CLEANUP_CATEGORIES = frozenset({'cache', 'repair'})¶
Categories a plain
cleanup(no category flag) runs.The orphan sweep is deliberately absent: it removes packages, where cache pruning and state repair only reclaim disk and fix metadata. Keeping it strictly behind an explicit
--orphansmakes the default non-destructive and identical on every manager, native sweep or not, mirroring howremovekeeps its cascade behind the same flag.
meta_package_manager.cli_sbom module¶
The SBOM subcommand: export the package inventory as a standard document.
sbom renders the installed inventory as a SPDX or CycloneDX file, with
optional metadata enrichment and an opt-in OSV vulnerability scan.
The mpm group itself, and the plumbing shared with the other subcommand
modules, live in meta_package_manager.cli.
Important
The two writer modules are imported inside sbom(), the one place in the
codebase deliberately breaking the imports-at-module-level rule.
meta_package_manager.sbom.spdx and meta_package_manager.sbom.cyclonedx
each pull a heavy third-party library at import time (spdx-tools, itself dragging
beartype and license-expression, plus cyclonedx-python-lib), and cli.py
imports every subcommand module up front to register it. So a module-level import
here spent that cost on every mpm invocation, mpm --version included, for a
library only this subcommand ever touches: deferring them took mpm --version from
750 ms to 440 ms on a machine carrying the optional [sbom-offline] extra.
Both are cheap no-ops for anyone without that extra, and the rest of this module
still imports at the top: meta_package_manager.sbom.base costs under a
millisecond and holds the ExportFormat
enum the option declarations need at decoration time.
tests.test_cli_sbom.test_cli_import_defers_sbom_libraries fails if the imports
migrate back up.
meta_package_manager.cli_snapshots module¶
The package snapshots subcommands: manifest export and replay.
dump (TOML manifest or Brewfile) and restore (install back the
packages a TOML manifest references, through the shared per-package action
engine).
The mpm group itself, and the plumbing shared with the other subcommand
modules, live in meta_package_manager.cli.
meta_package_manager.config module¶
Configuration utilities for mpm.
Hosts the schema of the [mpm] configuration section consumed by
click_extra and the runtime policy around the [mpm.managers.<id>]
sections of the same configuration file: applying attribute overrides to shipped
managers, gating manager definitions on the trust of their source, and registering
them into the pool.
The concerns stay separate across three modules:
meta_package_manager.pool.ManagerPool owns the live manager instances
and the per-manager overridden_fields tracking dict;
meta_package_manager.definitions owns the declarative schema (which
fields a section may set, how to coerce values) and the class factory; this module
owns the loading policy and mutates the pool through the
apply_manager_overrides() and register_config_managers() helpers.
- class meta_package_manager.config.MpmConfig(all_managers=False, ignore_auto_updates=True, stop_on_error=False, dry_run=False, plan=False, sudo=None, timeout=None, jobs='auto', cooldown='', require_cooldown_support=True, description=False, sort_by=<factory>, summary=True, network=False, suggest_contribs=True, managers=<factory>)[source]¶
Bases:
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.- all_managers: bool = False¶
Force evaluation of all managers, including unsupported and unmaintained.
- 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
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 CRITICAL``and the``logging.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>, 'dry_run': <function _to_bool>, 'extra_env': <function _to_str_dict>, 'ignore_auto_updates': <function _to_bool>, 'plan': <function _to_bool>, 'post_args': <function _to_str_tuple>, 'pre_args': <function _to_str_tuple>, 'pre_cmds': <function _to_str_tuple>, 'requirement': <function _to_str>, 'stop_on_error': <function _to_bool>, 'sudo': <function _to_bool>, 'timeout': <function _to_int>, 'unmaintained': <function _to_bool>, 'version_cli_options': <function _to_str_tuple>, 'version_regexes': <function _to_str_tuple>}¶
Per-manager attributes a user is allowed to override from the
[mpm.managers.<id>]configuration section.Each entry maps a
meta_package_manager.manager.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', 'almalinux', 'alpine', 'altlinux', 'amzn', 'android', 'arch', 'arch_32_bit', 'arch_64_bit', 'big_endian', 'bourne_shells', 'bsd', 'bsd_without_macos', 'buildroot', 'c_shells', 'cachyos', 'centos', 'chromeos', 'clearlinux', 'cloudlinux', 'cygwin', 'debian', 'dragonfly_bsd', 'endeavouros', 'exherbo', 'fedora', 'freebsd', 'generic_linux', 'gentoo', 'gpu_terminals', 'guix', 'haiku', 'hurd', 'ibm_mainframe', 'ibm_powerkvm', 'illumos', 'kali', 'kvmibm', 'linux', 'linux_layers', 'linux_like', 'linuxmint', 'little_endian', 'loongarch', 'macos', 'mageia', 'mandriva', 'manjaro', 'midnightbsd', 'multiplexers', 'native_terminals', 'netbsd', 'nixos', 'nobara', 'openbsd', 'opensuse', 'openwrt', 'oracle', 'os400', 'other_posix', 'other_shells', 'parallels', 'pidora', 'pikaos', 'powerpc', 'raspbian', 'rhel', 'riscv', 'rocky', 'scientific', 'slackware', 'sles', 'slitaz', 'solaris', 'sourcemage', 'sunos', 'system_v', 'tuxedo', 'ubuntu', 'ultramarine', 'unix', 'unix_layers', 'unix_without_macos', 'void', 'web_terminals', 'webassembly', 'windows', 'windows_shells', 'wsl1', 'wsl2', 'x86', 'xenserver'})¶
Platform and group IDs accepted in a definition’s
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>, 'maintenance_note': <function _to_str>, 'post_args': <function _to_str_tuple>, 'pre_args': <function _to_str_tuple>, 'pre_cmds': <function _to_str_tuple>, 'requirement': <function _to_str>, 'timeout': <function _to_int>, 'unmaintained': <function _to_bool>, 'unmaintained_message': <function _to_str>, 'version_cli': <function _to_str>, 'version_cli_options': <function _to_str_tuple>, 'version_regexes': <function _to_str_tuple>}¶
CLI-execution attributes a definition may set, mostly reusing the override converters.
The runtime-preference fields (
dry_run,ignore_auto_updates,plan,stop_on_error) are excluded: they are command-line/global concerns, not part of a manager’s identity, and resolve through the usual option precedence.unmaintainedis reused from the override converters so a TOML-defined manager can flag its own upstream as abandoned (seedocs/cooldown.mdfor the affected managers).Seven 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.maintenance_noterenders a{note}admonition atop the manager’s page for a still-maintained upstream under watch (seemaintenance_note).unmaintained_messagedocuments an abandoned upstream, rendering a{warning}admonition and the⚠️table markers (seeunmaintained_message).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', 'logo', 'name', 'operations', 'platforms'})¶
Top-level keys of a definition section that are not CLI-execution fields.
- meta_package_manager.definitions.QUERY_OPERATIONS: Final[frozenset[str]] = frozenset({'installed', 'orphans', 'outdated', 'search'})¶
Operations that parse the command’s stdout into packages.
- meta_package_manager.definitions.COMMAND_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup_cache', 'cleanup_orphan', 'cleanup_repair', 'doctor', 'install', 'remove', 'remove_orphan', 'sync', 'upgrade_all', 'upgrade_one'})¶
Operations that only run a command and produce no inventory to parse.
cleanupitself is deliberately absent: it is not an operation a manager defines anymore, but the fixed composition of the declared cleanup categories (seemeta_package_manager.manager.PackageManager.cleanup()). A definition declaring it is rejected with a targeted error.
- meta_package_manager.definitions.ALL_DEFINITION_OPERATIONS: Final[frozenset[str]] = frozenset({'cleanup_cache', 'cleanup_orphan', 'cleanup_repair', 'doctor', 'install', 'installed', 'orphans', 'outdated', 'remove', 'remove_orphan', 'search', 'sync', 'upgrade_all', 'upgrade_one'})¶
Every operation name a definition may declare.
- meta_package_manager.definitions.RECOGNIZED_PARSE_FIELDS: Final[frozenset[str]] = frozenset({'installed_version', 'latest_version', 'package_id'})¶
Named regex groups / JSON field keys a query parser may map to a package.
- meta_package_manager.definitions.REQUIRED_PARSE_FIELDS: Final[Mapping[str, frozenset[str]]] = {'installed': frozenset({'package_id'}), 'orphans': frozenset({'package_id'}), 'outdated': frozenset({'latest_version', 'package_id'}), 'search': frozenset({'package_id'})}¶
Parse fields each query operation must extract to be useful.
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', 'remove_orphan': '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'}), 'remove_orphan': frozenset({'package_id'}), 'search': frozenset({'exact_args', 'extended_args', 'id_name_only_args', 'query'}), 'upgrade_one': frozenset({'package_id'})}¶
Placeholders each operation’s
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.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, logo, 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() tuple[tuple[ManagerDefinition, str], ...][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 progress-wrapped fan-out primitives the CLI subcommands drive
(collect_from_managers(), collect_per_package()) with their shared
dispatch() engine, the backend-lock catalog that serializes conflicting
managers (SHARED_LOCK_FAMILIES and merge_into_lock_lanes()), and
the manager-bound ✓/✗ ledger (OperationTrail) that the
concurrent and sequential paths both report through.
The generic layers live upstream in click-extra: the concurrency primitives in
click_extra.execution (run_lanes driven by
mpm --jobs) and the batch-reporting trail in click_extra.spinner
(OperationTrail with its
trail_glyph/trail_line atoms). This module keeps what is package-manager
policy: which managers must never overlap, how the trail binds to the pool’s
--progress state, and when a batch collapses to a sequential pass.
- class meta_package_manager.dispatch.LockFamily(backend, members, contention)[source]¶
Bases:
objectA set of managers contending for one backend lock.
- backend: str¶
Short name of the contended resource, labelling the family in the docs.
Rendered as the middle level of
meta_package_manager._docs.lock_families_sankey()and as the first column ofmeta_package_manager._docs.lock_families_table(), so it reads as a thing managers queue on rather than as a tool: pacman database, not pacman.That phrasing is also load-bearing for the diagram. Mermaid identifies a sankey node by its label alone, so a family named after a member (
pkg,conda,pacmanandscoopall name both) would fold the two levels into one self-linked node.test_lock_family_backends_are_distinctholds every name clear of the pool.
- contention: str¶
Why they collide, written to complete the sentence “``mpm`` never runs X at the same time as Y: …”.
Rendered on every member’s own documentation page by
meta_package_manager._docs.manager_concurrency(), so it addresses a user of those managers rather than a reader of this module: keep it to the fact and its consequence, and leave the maintenance rationale to the notes below.
- meta_package_manager.dispatch.SHARED_LOCK_FAMILIES: Final[tuple[LockFamily, ...]] = (LockFamily(backend='dpkg lock', members=frozenset({'apt-mint', 'deb-get', 'apt', 'pacstall', 'nala'}), contention='they all install through `dpkg` and serialize on its `/var/lib/dpkg/lock`'), LockFamily(backend='Homebrew update lock', members=frozenset({'brew', 'cask'}), contention="they are the same `brew` binary, and two concurrent `brew update` collide on Homebrew's own update lock"), LockFamily(backend='conda environment prefix', members=frozenset({'micromamba', 'mamba', 'conda'}), contention='they act on one environment prefix and one package cache, and `conda` honors none of the locks `mamba` takes on them'), LockFamily(backend='RPM database', members=frozenset({'yum', 'urpmi', 'zypper', 'dnf5', 'dnf'}), contention='they all reach the RPM database'), LockFamily(backend='pacman database', members=frozenset({'paru', 'pacaur', 'yay', 'pacman', 'pikaur', 'pamac', 'trizen'}), contention='they all reach the pacman database (`/var/lib/pacman/db.lck`), and two of them mutating at once fail to init their transaction'), LockFamily(backend='pkg install database', members=frozenset({'ports', 'pkg'}), contention='`ports` keeps no registry of its own and registers what it builds through `pkg`, whose advisory lock on that shared install database refuses a second writer'), LockFamily(backend='Scoop tree', members=frozenset({'scoop', 'sfsu'}), contention='they work on the same `~/scoop` tree, `sfsu` delegating its mutating operations to the `scoop` binary itself'))¶
Managers that contend for one shared backend lock, grouped by backend.
Different managers are otherwise independent processes over disjoint state, so running them in parallel is safe. The exception is a handful that drive a shared backend and serialize on its lock:
apt,apt-mint,deb-get,nalaandpacstallall reach dpkg (/var/lib/dpkg/lock).pacstallbelongs here despite itspacprefix and its AUR-inspired design: it builds its pacscripts into.debarchives and installs those, so it contends with the Debian family and never touches pacman’s database.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”.conda,mambaandmicromambaact on one environment prefix and one package cache. This is the family that does not get the guarantee below: mamba takes a real lock on the prefix and on every cache directory for the length of a transaction, and conda honors none of them, its own locking covering the repodata cache alone. Concurrent runs corrupt rather than block, which upstream closed as not planned (conda/conda#13037). Serializing them here is what keeps that out of reach.dnf,dnf5,yum,zypperandurpmiall reach the RPM database.urpmifrontslibrpmdirectly, having no listing of its own, and the Mandriva lineage it serves shipsdnfalongside it, so the two genuinely coexist on one host.pacmanand the AUR helperspacaur,pamac,paru,pikaur,trizenandyayall reach the pacman database (/var/lib/pacman/db.lck). The helpers are front-ends rather than reimplementations: each shells out tosudo pacmanfor the privileged steps,pamacreaching the samelibalpmthrough Manjaro’slibpamac. Two of them mutating at once fail to init their transaction.pkgandportsshare the install databasepkgmaintains, which every mutating operation butsyncreaches:portshas no registry of its own, builds from/usr/portsand registers the result throughpkg, whose advisory lock on that database refuses a second writer. Theirsyncis the one pair that would not collide, refreshing a git tree and a package catalog respectively, and is serialized along with the rest rather than splitting the family per operation.scoopandsfsuwork on the same~/scooptree. sfsu reimplements Scoop’s read paths only, delegatinginstall,removeand both upgrades to thescoopbinary itself, so those are literally the same command twice; its ownupdateandcleanupthen reach the same buckets and cache Scoop’s do. Concurrent bucket refreshes are twogit pullin one repository, which fails on the index lock.
dkp-pacmanis deliberately not in the pacman family, and it is the one exclusion worth stating: it isPacmanby inheritance and would look like an oversight. But devkitPro ships it precisely so it can sit beside a distribution’s ownpacmanwithout colliding, pointed at its own repositories and its own database, so it contends with nothing.pkconis the case this model cannot express, and it is left out knowingly rather than filed under a guess. PackageKit is a client for whatever backend the host provides (apt, dnf, zypp, alpm), so the family it belongs to is a property of the machine rather than of the manager, and afrozensethere is fixed at import. It needs no protection from itself,packagekitdqueuing its own transactions, but apkconmutation still contends with a native manager mpm drives in the same run. Expressing that would mean resolving the backend at dispatch time.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). For every family above except the conda one, two members running at once block or fail rather than corrupt: each backend holds a real lock and the loser is told so. That is a property of those backends rather than of this mechanism, so a family added later earns the guarantee only by inspection, and conda is the standing proof that some do not.
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 (seerun_cache), so two that resolve to a byte-identical invocation (brewandcaskforsyncandcleanup) run the subprocess once.Adding a newly-conflicting set of managers is one entry here: a
LockFamilynaming the backend and its members, after which the serialization, the command cache, the Concurrency section of every member’s documentation page and both renderings ofdocs/concurrency.mdall pick it up.
- meta_package_manager.dispatch.FAN_OUT_CONCURRENT: Final[str] = 'concurrent'¶
Every selected manager runs at once, one
dispatch()lane each.
- meta_package_manager.dispatch.FAN_OUT_GROUPED: Final[str] = 'grouped'¶
Same, but
SHARED_LOCK_FAMILIESmembers are merged into one lane.
- meta_package_manager.dispatch.FAN_OUT_SEQUENTIAL: Final[str] = 'sequential'¶
One manager at a time, whatever
mpm --jobssays.
- meta_package_manager.dispatch.FAN_OUT_NONE: Final[str] = 'none'¶
Runs no package operation, so there is nothing to spread.
- class meta_package_manager.dispatch.FanOut(invocation, mode)[source]¶
Bases:
objectHow one way of invoking a subcommand spreads over the selected managers.
- meta_package_manager.dispatch.COMMAND_FAN_OUT: Final[tuple[FanOut, ...]] = (FanOut(invocation='cleanup', mode='grouped'), FanOut(invocation='config-template', mode='none'), FanOut(invocation='doctor', mode='grouped'), FanOut(invocation='dump', mode='concurrent'), FanOut(invocation='help', mode='none'), FanOut(invocation='install', mode='grouped'), FanOut(invocation='install <untied package>', mode='sequential'), FanOut(invocation='installed', mode='concurrent'), FanOut(invocation='managers', mode='none'), FanOut(invocation='orphans', mode='concurrent'), FanOut(invocation='outdated', mode='concurrent'), FanOut(invocation='remove', mode='grouped'), FanOut(invocation='restore', mode='grouped'), FanOut(invocation='sbom', mode='concurrent'), FanOut(invocation='search', mode='concurrent'), FanOut(invocation='sync', mode='grouped'), FanOut(invocation='upgrade', mode='grouped'), FanOut(invocation='which', mode='none'))¶
Fan-out mode of every
mpmsubcommand, rendered ondocs/concurrency.md.The three fan-out shapes above are visible from inside this module; which subcommand takes which is not, being an argument at each call site. This is where the two meet, so a reader can answer “does this command parallelize?” without following
report_state=Truethrough four CLI modules.Kept complete rather than restricted to the commands that fan out:
test_fan_out_covers_every_subcommandholds it equal to the CLI’s own command list, so a new subcommand fails the suite until someone decides its mode. TheFAN_OUT_NONEentries are that decision recorded, andmeta_package_manager._docs.concurrency_table()leaves them out of the rendered table.Caution
Hand-maintained, and the one thing here that can drift from the code silently. A subcommand switching between
collect_from_managers()andcollect_per_package(), or gainingreport_state=True, has to be reflected in the same commit: no test can read the mode back off a call site.
- 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.probe_signature(manager)[source]¶
Static signature of the command
manager’s version probe would spawn.Built from class attributes alone: no filesystem lookup, no subprocess. Two managers sharing a signature search the same directories for the same binary names and pass it the same arguments, so their probes may resolve to a byte-identical command line.
brewandcaskdo, and so douv/uvxandyarn/yarn-berry.Deliberately conservative in the safe direction. It never splits two managers that would spawn the same command, which is what
merge_into_probe_lanes()needs to put them on one lane; it may however merge two that turn out to differ (the Zsh plugin managers all probezsh, some with--versionand some withversion). A wrong merge costs the two a shared lane, where the second simply misses the cache and spawns as it would have anyway.Not the resolved command line, on purpose: resolving it means walking
PATHfor every candidate up front, which measured slower than the redundant subprocesses it would save.- Return type:
- meta_package_manager.dispatch.merge_into_probe_lanes(managers)[source]¶
Group
managersintowarm_availability()lanes byprobe_signature().The probe counterpart of
merge_into_lock_lanes(): managers whose version probe may resolve to the same command line land on one lane and run serially, while unrelated managers keep a lane each and run concurrently. Lanes come out in first-seen order, so a run is reproducible.- 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.Managers on distinct lanes are distinct instances with their own cached attributes and subprocess, so their 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.
Probes run in
merge_into_probe_lanes()lanes rather than one flat batch, so the managers that would spawn a byte-identical--versioncall take turns on one worker and share arun_cache: the first spawns, the rest replay its result. That is the same mechanismdispatch()gives a lock family, and it needs the lane for the same reason — a cache handed to managers running concurrently would just race them both into spawning.Sized by
effective_jobs()over the lane count: a no-op (leaving the probes to lazy, sequential evaluation) without an active context, atDEBUGverbosity, for a single lane, or atmpm --jobs1.- Return type:
- class meta_package_manager.dispatch.OperationTrail(managers, *, label='', unit='', total=0, jobs=1, coverage=False)[source]¶
Bases:
OperationTrailclick_extra.spinner.OperationTrailbound to the manager pool.The upstream class owns the two renderings (sequential echoed lines, or one aggregate indicator with buffered-then-streamed lines) and the interactive gating; this subclass supplies mpm’s policy around it:
Enablement follows ``–progress``, folded into each manager’s
progressflag by the CLI (a TTY, no serialized output, not atDEBUGverbosity): any enabled manager turns the trail on, auto-gated on an interactive stderr.A concurrent batch mutes the managers’ own per-call spinners (which would collide on stderr) for the duration of the aggregate one.
A concurrent batch’s aggregate indicator is a determinate progress bar, not an indeterminate spinner: every
dispatch()batch counts its work up front (one task per manager, or per package-manager pair), so the bar always has a length to render against.``coverage`` keeps the read-command semantics: their result table is the real output and each manager keeps its per-call spinner, so the sequential rendering stays silent (upstream’s
echo_sequential=False).
The ordering-bound sequential state changers (
install’s priority search) construct it bare; everydispatch()batch drives it as a context manager.- 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 indicator (“Searching”).unit (
str) – the noun counted in the indicator tally (“managers”, “packages”).total (
int) – how many outcomes are expected, for thedone/totalcount and the progress bar’s length.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.
Configure (but do not start) the trail.
- Parameters:
label (
str) – present-tense verb for the running aggregate indicator ("Fetching"), composed into its{label} {done}/{total} {unit}tally.unit (
str) – the noun counted in the tally ("files","feeds").total (
int) – how many outcomes are expected, for thedone/totalcount.jobs (
int) – the batch’s worker count;> 1selects the concurrent rendering (one aggregate spinner),<= 1the sequential one (plain echoed lines).spinner – a
SpinnerPresetfrom theSPINNERScatalog (spinner=SPINNERS["moon"]) for the concurrent aggregate spinner. Ignored by the sequential and progress-bar renderings, and mutually exclusive withprogress_bar.progress_bar – render the aggregate indicator as a determinate
click.progressbar()instead of a spinner, for a sequential or concurrent batch alike. Requires a positivetotal(a bar needs a length) and is mutually exclusive withspinner.timer – append each operation’s and the batch’s elapsed time to the trail lines and the finisher.
None(the default) follows the CLI’s--time/--no-timeflag;Trueforces timing on withformat_duration()’s compact clock, a callable(seconds: float) -> strforces it on with a custom format, andFalseforces it off. Per-operation times come from asecondsargument tomark(), filled in automatically by anoperation()handle.clock – whether a running aggregate indicator shows elapsed time (
"elapsed", the default: a stopwatch counting up, visible from the start) or remaining time ("eta": an estimate from the batch’s rate, appearing only once an outcome lets it be computed). Both the progress bar and the concurrent spinner honor"eta"(the spinner reuses Click’s progress-bar estimate, since the trail knows itstotal). Per-operation and finisher times are always elapsed.enabled – force the trail on or off.
None(the default) auto-detects: the sequential echo renders only on an interactive stream, and the aggregate indicator applies its own TTY gate.echo_sequential – whether a sequential batch echoes its outcome lines and finisher at all. Turn it off when the batch has another output that is the real product (a result table) and the trail would be noise; an aggregate indicator is unaffected.
delay – seconds before the aggregate indicator first draws: a fast batch then completes without ever flashing one.
stream – where to render; defaults to
sys.stderrso the trail never mixes intostdoutdata.
- Raises:
ValueError – if
progress_baris set without a positivetotal, or together withspinner, or ifclockis neither"elapsed"nor"eta".
- meta_package_manager.dispatch.dispatch(label, done_label, unit, lanes, *, coverage=False, ctx=None)[source]¶
Fan a set of work lanes out across managers, narrating a
✓/✗trail.The single scheduling primitive behind both
collect_from_managers()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 (seerun_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 progress bar 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.docstring_corpus module¶
Harvest the CLI-session samples documented in manager source docstrings.
Every query method (and the version_regexes attribute) documents a sample
invocation and its output in a MyST {code-block} shell-session fence
sitting right next to the regex (or JSON parser) that consumes it. This module
reads those blocks straight from the source and exposes them, with a shared
notion of which ones are literal, replayable fixtures.
It has two consumers:
tests.test_docstring_corpusreplays each literal block back through the parser it illustrates, asserting the documented example still yields well-formed packages.meta_package_manager._docsrenders the literal blocks as the reference traces of a class-based manager’s documentation page, the config-defined twin of the[samples]fixtures shipped alongside TOML-defined managers.
Blocks are harvested from raw source, not the escape-processed __doc__:
inspect.cleandoc() expands tabs and the compiler collapses \\, either
of which would rewrite a tab-delimited or escaped-JSON fixture into
something the parser rejects. Everything here reads static source through
ast/inspect, so it is host-independent: safe to call at
documentation build time on any machine.
- meta_package_manager.docstring_corpus.FENCE_OPENERS = ('```{code-block} shell-session', '```{code-block} pwsh-session')¶
MyST fence openers introducing a captured CLI session.
PowerShell sessions use
>as their prompt, which the dissector already recognizes, so both flavors share one extraction path.Important
These two openers are the fixture fences: every
installed/outdated/orphans/version_regexesblock written under one is a complete sample that must parse (the corpus round-trip enforces it) and is rendered as a reference trace. An illustration that is not a literal fixture (a human-readable variant, an interactive prompt, a narrative before/after transcript) uses a non-harvested{code-block} consolefence instead, so it stays out of the corpus and the traces while still rendering in the API docs.
- meta_package_manager.docstring_corpus.extract_blocks(docstring)[source]¶
Return the dedented body of every
shell-sessionfence in a docstring.A fence body runs from the opener to the first closing `
` ` line, and shares the fence's indentation. The blank line the MyST syntax puts between a ``{code-block}opener and its content is stripped along with the common indentation.
- meta_package_manager.docstring_corpus.dissect(block)[source]¶
Split a shell-session block into its command tokens and its output.
$starts a command and>continues it (the shell’s secondary prompt). A command may also continue onto unprefixed lines via a trailing backslash, so those are absorbed too. Every remaining line is output.
- meta_package_manager.docstring_corpus.split_session(block)[source]¶
Return just the command output of a shell-session block.
- Return type:
- meta_package_manager.docstring_corpus.block_commands(block)[source]¶
Return each documented command of a block as its own token list.
Unlike
dissect(), which pools every command of a block, this keeps commands separate so a block documenting several invocations (anaptcleanup runningautoremovethenautoclean) yields one list each. Prompt flavor is per-block:$-primary with>continuations for shell sessions,>-primary for PowerShell sessions.
- meta_package_manager.docstring_corpus.block_language(block)[source]¶
Return the fenced-code language matching a block’s prompt flavor.
A shell session opens on a
$prompt, a PowerShell session on>. The documented reference traces are re-fenced with the flavor they were captured under so their prompts keep highlighting correctly.- Return type:
- meta_package_manager.docstring_corpus.class_blocks(cls: type) dict[str, list[str]][source]¶
Map
{member: [blocks]}kept in raw source form for the corpus.Escapes and tabs survive verbatim so the round-trip feeds each block to the parser exactly as the CLI emits it. Rendered documentation wants the terminal-facing form instead: see
class_display_blocks().
- meta_package_manager.docstring_corpus.class_display_blocks(cls: type) dict[str, list[str]][source]¶
Map
{member: [blocks]}in compiled form for rendered documentation.The reference-traces generator reads these so a transcript shows single backslashes and resolved escapes, matching what a reader would see running the command, rather than the doubled source escapes
class_blocks()preserves for the parser.
- meta_package_manager.docstring_corpus.is_fixture(output)[source]¶
A block is a fixture when it carries sample output to parse.
A
shell-sessionblock showing only a command (no output, an empty system) illustrates an invocation but has nothing for a parser to consume, so it is not a fixture.- Return type:
- meta_package_manager.docstring_corpus.literal_blocks(cls, members)[source]¶
Return
(member, index, block)for a class’s replayable fixture blocks.A block qualifies when it carries sample output (
is_fixture()). The index is its position within the member’s full block list. Blocks come in compiled, terminal-facing form (class_display_blocks()): the escape/tab differences from the raw corpus form never touch a directive, so the same blocks are selected either way.
meta_package_manager.execution module¶
CLI-execution engine shared by every package manager.
Runs one manager’s CLI in one subprocess: the
meta_package_manager.execution.CLIExecutor mixin (which
meta_package_manager.manager.PackageManager inherits) locates the binary
and runs it, the meta_package_manager.execution.CLIError exception carries
a failed call’s result, and meta_package_manager.execution.highlight_cli_name()
themes a binary’s name.
Scheduling many managers at once is the next altitude up, and lives in
meta_package_manager.dispatch: the concurrent fan-out primitives, the
lock families and the shared ✓/✗ trail. The sudo machinery that cuts
across both altitudes (credential priming, the keepalive, the hidden-prompt stall
watchdog) lives in meta_package_manager.sudo: this module only consumes
it, to wrap escalated commands and diagnose their failures.
Note
The name and intent mirror click_extra.execution from the sibling
click-extra project, where the
generic layers now live: the concurrency primitives (run_jobs/run_lanes
driven by mpm --jobs), the single-subprocess engine
(click_extra.execution.run_cli(), which disclosed invocations and
streams output to the logs), and the Ctrl+C machinery
(click_extra.execution.install_interrupt_handler() terminating the
in-flight children registered by run_cli). This module keeps what is
package-manager policy: per-operation timeouts, sudo escalation, cooldown
enforcement and dry-run.
- meta_package_manager.execution.DIAGNOSIS_TAIL_LINES: Final = 10¶
Trailing lines of a failed command’s report relayed at
WARNING.CLIs conclude with their actual error, so the tail is where the diagnosis lives; the cap keeps a verbose failure (a source build’s compiler spew) from flooding the default view. The raw streams are always available in full, live, at
DEBUG.
- exception meta_package_manager.execution.CLIError(code, output, error)[source]¶
Bases:
ExceptionAn error occurred when running package manager CLI.
The exception internally keeps the result of CLI execution.
- property diagnosis: str¶
The command’s own account of its failure, capped for log relay.
Prefers
<stderr>, the conventional stream for error reporting, and falls back on<stdout>for the tools that report failures there (steamcmd); a command that died silently is reduced to its exit code. Only the lastDIAGNOSIS_TAIL_LINESlines are kept, behind a counter of the truncated ones: errors conclude streams.
- meta_package_manager.execution.VERSION_PROBE: Final = 'version'¶
Pseudo-operation stamped on
CLIExecutor._active_operationduring version detection.Not a member of
meta_package_manager.capabilities.Operations(no subcommand routes it), but it participates in the same per-operation machinery:OPERATION_TIMEOUTSbinds it to the short read-only cap, andCLIExecutor.run()demotes its command disclosure toDEBUGso the per-candidate probes cannot drown theINFOnarration.
- meta_package_manager.execution.format_plan_command(cmd_args, extra_env=None)[source]¶
Render a captured
mpm --plancommand as a copy-pasteable shell line.Unlike
click_extra.execution.format_cli_prompt()(styled, and prefixed with a$prompt sigil for logs and dry-runs), this returns a plain, unstyled, shell-quoted line: the forced environment assignments followed by the resolved binary and its arguments, ready to paste into a terminal or pipe into a shell. See the plan-mode branch ofCLIExecutor.run().- Return type:
- meta_package_manager.execution.PLAN_RECORDER: Final = <meta_package_manager.execution._PlanRecorder object>¶
Process-wide sink for
CLIExecutor.run()’s plan-mode captures.A module-level singleton because
runexecutes in the fan-out’s worker threads, where the click context is not reliably reachable. See_PlanRecorder.
- meta_package_manager.execution.highlight_cli_name(path, match_names)[source]¶
Highlight the binary name in the provided
path.The name is only highlighted when it matches one of the recognized
match_names, so an unrecognized binary stays plain. Matching is insensitive to case on Windows and case-sensitive on other platforms, thanks 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, 'doctor': 500, 'install': 500, 'installed': 120, 'orphans': 120, 'outdated': 120, 'remove': 500, 'search': 120, 'sync': 500, 'upgrade': 500, 'upgrade_all': 500, 'version': 120}¶
Per-operation timeout defaults, applied only when the user has set no explicit
--timeout(or per-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.execution.CLIExecutor.cli_pathworks well on all platforms.
- extra_env: ClassVar[Mapping[str, str | None] | None] = None¶
Additional environment variables to add to the current context.
Automatically applied on each
meta_package_manager.execution.CLIExecutor.run_cli()calls.
- pre_cmds: tuple[str, ...] = ()¶
Global list of pre-commands to add before before invoked CLI.
Automatically added to each
meta_package_manager.execution.CLIExecutor.run_cli()call.Used to prepend sudo or other system utilities.
- post_args: tuple[str, ...] = ()¶
Global list of options used before and after the invoked package manager CLI.
Automatically added to each
meta_package_manager.execution.CLIExecutor.run_cli()call.Essentially used to force silencing, low verbosity or no-color output.
- version_cli_options: tuple[str, ...] = ('--version',)¶
CLI options used to produce the version of the package manager.
The raw output produced by the package manager CLI will be parsed with the
version_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.execution.CLIExecutor.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.
- plan: bool = False¶
Capture state-changing CLI calls for inspection instead of running them.
Set by
mpm --plan. Unlikedry_run(which simulates every call, read-only queries included), plan mode lets the read-only queries (installed,outdated,search) run for real so the resolved plan reflects actual system state, and records only the state-changing commands (see_MUTATING_OPERATIONS) intoPLAN_RECORDER.
- 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,finkre-execs its root commands through it, and the AUR helpers callsudo pacmanfor their install steps. mpm never wraps such a manager’s commands: either none of its operations carry abuild_cli(..., sudo=True)marker (cask,fink), or itsdefault_sudo = Falsepolicy leaves the markers it inherits unescalated (the AUR helpers). Running the tool undersudois often forbidden outright (brewrefuses root,makepkgrefuses to build). 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 across a lane’s managers.
Noneby default, which disables caching: everyrun()call spawns its own subprocess. Two callers install a shared dict, each for the duration of one lane:meta_package_manager.dispatch.dispatch(), on every multi-manager lock-family lane (seemeta_package_manager.dispatch.SHARED_LOCK_FAMILIES), sobrewandcaskboth runningbrew updatefor mpm sync spawn it once.meta_package_manager.dispatch.warm_availability(), on every version-probe lane (seemeta_package_manager.dispatch.merge_into_probe_lanes()), sobrewandcaskboth probingbrew --versionspawn it once too.
The replay still walks
run()’s logging and failure gate, so a failed shared command is attributed to every member. Keyed on the resolved command line and its environment, so only genuinely identical invocations collapse.Caution
Not thread-safe, and it does not need to be: both callers bind a cache to a lane, and
click_extra.execution.run_lanes()runs a lane’s items serially on a single worker. Handing one dict to managers that run concurrently would race two peers into spawning the same command anyway, losing the de-duplication rather than corrupting anything, so the lane is what makes the cache work at all.
- cli_errors: list[CLIError]¶
Accumulate all CLI errors encountered by the package manager.
Every
CLIErrorproduced byrun()lands here, whether or not it is also raised: a failure the caller goes on to swallow (installed_or_empty()and its peers) is still a failure this manager committed, and the end-of-run summary, the serializederrorspayload and the ✓/✗ trail all read this list to say so.Recording only the non-raising half made a manager’s visibility hinge on whether its query happened to pass
must_succeed: mpm list printed “Could not list installed packages.” and still scored the manager ✓, leaving it out of the closing count.
- 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[source]¶
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.execution.CLIExecutor.executableproperty below.
- property version: TokenizedString | None[source]¶
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.
- acting_as(operation=None, *, stop_on_error=None)[source]¶
Temporarily adjust the manager’s execution state, restoring it on exit.
operationre-stamps_active_operation(the per-operation timeout and watchdog key) for the duration of the block;Noneleaves the current stamp untouched.stop_on_errorlikewise overrides the failure policy when set: the per-package state changers run their action understop_on_error=Trueso a botched operation raises and is recorded by the caller instead of being silently accumulated.The public seam for callers needing a scoped state override: the CLI layer must never poke
_active_operationorstop_on_errordirectly.
- 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)detaching every other call into its own POSIX session and process group, so a timeout or Ctrl+C reaps the whole process tree and a wedged grandchild cannot linger as an orphan; the flagged call above keeps the controlling terminal so its
sudoprompt stays answerableremoving 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) – ifTrue, raisemeta_package_manager.execution.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:
- 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.
- 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.execution.CLIExecutor.build_cli()method, it is executed with themeta_package_manager.execution.CLIExecutor.run()method, augmented with environment variables fromself.extra_env.All parameters are the same as
meta_package_manager.execution.CLIExecutor.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-run,mpm --stop-on-errorandmpm --planoptions to force the execution and completion of the command. It is used for reads whose output is needed regardless (version detection,yarn global dir), which must run for real even when the user asked to simulate or to only plan mutations.must_succeedraises on non-zero exit regardless ofmpm --stop-on-error. Seerun()for details.
- Return type:
meta_package_manager.labels module¶
Utilities to generate the extra labels and labeller rules for GitHub issues and PRs.
The content and file rules produced here are a convenience: they pre-label a
freshly filed issue or PR to save the maintainer a first pass. They never replace
the manual review and classification, and nothing downstream treats them as
authoritative. They are therefore tuned for precision over recall: a rule is
encoded only when its signal is unambiguous (see generate_content_rules()
and generate_file_rules()), and a manager with no unambiguous term simply
gets no content rule and is labelled by hand.
- meta_package_manager.labels.generate_labels(all_labels, groups, prefix, color)[source]¶
Generate labels.
A dedicated label is produced for each entry of the
all_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_COLOR = '#bfdadc'¶
Color GitHub paints every
📦 manager: *label with.Also the color of the badge each manager’s documentation page links its label with, hard-coded in
docs/_static/custom.csssince a stylesheet cannot read this module;test_manager_label_badge_colorkeeps the two in step.
- meta_package_manager.labels.MANAGER_LABEL_GROUPS: TLabelGroup = {'asdf-based': frozenset({'asdf', 'mise'}), 'conda-based': frozenset({'conda', 'mamba', 'micromamba', 'pixi'}), 'dpkg-based': frozenset({'apt', 'apt-mint', 'deb-get', 'fink', 'nala', 'opkg', 'pacstall'}), 'fish-based': frozenset({'fisher', 'oh-my-fish'}), 'homebrew': frozenset({'brew', 'cask', 'zerobrew'}), 'npm-based': frozenset({'bun', 'npm', 'pnpm', 'volta', 'yarn', 'yarn-berry'}), 'pacman-based': frozenset({'dkp-pacman', 'pacaur', 'pacman', 'pamac', 'paru', 'pikaur', 'trizen', 'yay'}), 'pkg-based': frozenset({'pkg', 'ports'}), 'pypi-based': frozenset({'pip', 'pipx', 'uv', 'uvx'}), 'rpm-based': frozenset({'dnf', 'dnf5', 'urpmi', 'yum', 'zypper'}), 'scoop-based': frozenset({'scoop', 'sfsu'}), 'vim-based': frozenset({'lazy', 'vim-pack'}), 'vscode-based': frozenset({'vscode', 'vscodium'}), 'zsh-based': frozenset({'antidote', 'antigen', 'zim', 'zinit', 'zplug'})}¶
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.
finkfollows the same rule over its platform: it manages.debpackages through dpkg, so it groups with the other dpkg front-ends despite being the only macOS one among them.pypi-basedis named for the registry rather than for a tool, because no single tool is common to it:uvreimplements resolution and installation from scratch and touches nopipcode. What the four share is the index they all resolve against, which is the level a report lands at.vim-basedandzsh-basedwiden that reading, and deliberately so: plugin managers share no backend at all, each cloning straight from upstream Git into its own tree. What they share is the host program a report is about, which is what the label has to answer. An issue mentioning a Vim plugin is about the same corner ofmpmwhether it arrives throughvim-packorlazy, so both carry one label and one tracker search. The two groups stay separate along that same line:zinitandantidotehost Zsh plugins, not Vim ones, and a report about either belongs nowhere near the editor label.asdf-basedis the plain reading again:miseresolves asdf’s plugins directly through itsasdf:backend, so the two share one plugin ecosystem and one class of report.voltastays undernpm-baseddespite also managing a runtime, because what it installs is npm packages.conda-basedfollowspypi-based:pixishares no code withconda, reimplementing resolution and installation on top of rattler, but both resolve conda packages from the same channels,conda-forgeby default. The registry is the level a report lands at, so the two share one label even though their CLIs have nothing else in common.
- meta_package_manager.labels.all_manager_label_ids = frozenset({'am', 'antidote', 'antigen', 'apk', 'apm', 'apt', 'apt-cyg', 'apt-mint', 'asdf', 'basalt', 'bin', 'bpkg', 'brew', 'bun', 'cargo', 'cask', 'cave', 'choco', 'chromebrew', 'clib', 'composer', 'conda', 'cpan', 'deb-get', 'dkp-pacman', 'dnf', 'dnf5', 'dotnet', 'emerge', 'eopkg', 'fink', 'fisher', 'flatpak', 'fwupd', 'gcloud', 'gem', 'gext', 'gh-ext', 'ghcup', 'guix', 'haxelib', 'jpm', 'juliaup', 'krew', 'lazy', 'luarocks', 'macports', 'mamba', 'mas', 'mason', 'micro', 'micromamba', 'miktex', 'mise', 'mpm', 'nala', 'nimble', 'nix', 'npm', 'oh-my-fish', 'ollama', 'opam', 'opkg', 'pacaur', 'pacman', 'pacstall', 'pamac', 'paru', 'pikaur', 'pip', 'pipx', 'pixi', 'pkcon', 'pkg', 'pkg-tools', 'pkgin', 'pnpm', 'ports', 'pwsh-gallery', 'pyenv', 'rustup', 'scoop', 'sdkman', 'sfsu', 'sheldon', 'slapt-get', 'snap', 'soar', 'sorcery', 'steamcmd', 'stew', 'sun-tools', 'swupd', 'tazpkg', 'tlmgr', 'topgrade', 'trizen', 'urpmi', 'uv', 'uvx', 'vagrant', 'vcpkg', 'vim-pack', 'volta', 'vscode', 'vscodium', 'winget', 'xbps', 'xcodes', 'yarn', 'yarn-berry', 'yay', 'yazi', 'yum', 'zef', 'zerobrew', 'zim', 'zinit', 'zplug', 'zypper'})¶
Adds
mpmas its own manager alongside all those implemented.
- meta_package_manager.labels.MANAGER_LABELS = {'am': '📦 manager: am', 'antidote': '📦 manager: zsh-based', 'antigen': '📦 manager: zsh-based', 'apk': '📦 manager: apk', 'apm': '📦 manager: apm', 'apt': '📦 manager: dpkg-based', 'apt-cyg': '📦 manager: apt-cyg', 'apt-mint': '📦 manager: dpkg-based', 'asdf': '📦 manager: asdf-based', 'basalt': '📦 manager: basalt', 'bin': '📦 manager: bin', 'bpkg': '📦 manager: bpkg', 'brew': '📦 manager: homebrew', 'bun': '📦 manager: npm-based', 'cargo': '📦 manager: cargo', 'cask': '📦 manager: homebrew', 'cave': '📦 manager: cave', 'choco': '📦 manager: choco', 'chromebrew': '📦 manager: chromebrew', 'clib': '📦 manager: clib', 'composer': '📦 manager: composer', 'conda': '📦 manager: conda-based', 'cpan': '📦 manager: cpan', 'deb-get': '📦 manager: dpkg-based', 'dkp-pacman': '📦 manager: pacman-based', 'dnf': '📦 manager: rpm-based', 'dnf5': '📦 manager: rpm-based', 'dotnet': '📦 manager: dotnet', 'emerge': '📦 manager: emerge', 'eopkg': '📦 manager: eopkg', 'fink': '📦 manager: dpkg-based', 'fisher': '📦 manager: fish-based', 'flatpak': '📦 manager: flatpak', 'fwupd': '📦 manager: fwupd', 'gcloud': '📦 manager: gcloud', 'gem': '📦 manager: gem', 'gext': '📦 manager: gext', 'gh-ext': '📦 manager: gh-ext', 'ghcup': '📦 manager: ghcup', 'guix': '📦 manager: guix', 'haxelib': '📦 manager: haxelib', 'jpm': '📦 manager: jpm', 'juliaup': '📦 manager: juliaup', 'krew': '📦 manager: krew', 'lazy': '📦 manager: vim-based', 'luarocks': '📦 manager: luarocks', 'macports': '📦 manager: macports', 'mamba': '📦 manager: conda-based', 'mas': '📦 manager: mas', 'mason': '📦 manager: mason', 'micro': '📦 manager: micro', 'micromamba': '📦 manager: conda-based', 'miktex': '📦 manager: miktex', 'mise': '📦 manager: asdf-based', 'mpm': '📦 manager: mpm', 'nala': '📦 manager: dpkg-based', 'nimble': '📦 manager: nimble', 'nix': '📦 manager: nix', 'npm': '📦 manager: npm-based', 'oh-my-fish': '📦 manager: fish-based', 'ollama': '📦 manager: ollama', 'opam': '📦 manager: opam', 'opkg': '📦 manager: dpkg-based', 'pacaur': '📦 manager: pacman-based', 'pacman': '📦 manager: pacman-based', 'pacstall': '📦 manager: dpkg-based', 'pamac': '📦 manager: pacman-based', 'paru': '📦 manager: pacman-based', 'pikaur': '📦 manager: pacman-based', 'pip': '📦 manager: pypi-based', 'pipx': '📦 manager: pypi-based', 'pixi': '📦 manager: conda-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', 'pyenv': '📦 manager: pyenv', 'rustup': '📦 manager: rustup', 'scoop': '📦 manager: scoop-based', 'sdkman': '📦 manager: sdkman', 'sfsu': '📦 manager: scoop-based', 'sheldon': '📦 manager: sheldon', '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', 'trizen': '📦 manager: pacman-based', 'urpmi': '📦 manager: rpm-based', 'uv': '📦 manager: pypi-based', 'uvx': '📦 manager: pypi-based', 'vagrant': '📦 manager: vagrant', 'vcpkg': '📦 manager: vcpkg', 'vim-pack': '📦 manager: vim-based', 'volta': '📦 manager: npm-based', 'vscode': '📦 manager: vscode-based', 'vscodium': '📦 manager: vscode-based', 'winget': '📦 manager: winget', 'xbps': '📦 manager: xbps', 'xcodes': '📦 manager: xcodes', 'yarn': '📦 manager: npm-based', 'yarn-berry': '📦 manager: npm-based', 'yay': '📦 manager: pacman-based', 'yazi': '📦 manager: yazi', 'yum': '📦 manager: rpm-based', 'zef': '📦 manager: zef', 'zerobrew': '📦 manager: homebrew', 'zim': '📦 manager: zsh-based', 'zinit': '📦 manager: zsh-based', 'zplug': '📦 manager: zsh-based', 'zypper': '📦 manager: rpm-based'}¶
Maps all manager IDs to their labels.
- meta_package_manager.labels.PLATFORM_LABELS = {'ALT Linux': '🖥 platform: Linux', 'AlmaLinux': '🖥 platform: Linux', 'Alpine Linux': '🖥 platform: Linux', 'Amazon Linux': '🖥 platform: Linux', 'Android': '🖥 platform: Linux', 'Arch Linux': '🖥 platform: Linux', 'Buildroot': '🖥 platform: Linux', 'CachyOS': '🖥 platform: Linux', 'CentOS': '🖥 platform: Linux', 'ChromeOS': '🖥 platform: Linux', 'Clear Linux OS': '🖥 platform: Linux', 'CloudLinux OS': '🖥 platform: Linux', 'Cygwin': '🖥 platform: Unix', 'Debian': '🖥 platform: Linux', 'DragonFly BSD': '🖥 platform: BSD', 'EndeavourOS': '🖥 platform: Linux', 'Exherbo Linux': '🖥 platform: Linux', 'Fedora': '🖥 platform: Linux', 'FreeBSD': '🖥 platform: BSD', 'GNU/Hurd': '🖥 platform: Unix', 'Generic Linux': '🖥 platform: Linux', 'Gentoo Linux': '🖥 platform: Linux', 'Guix System': '🖥 platform: Linux', 'Haiku': '🖥 platform: Unix', 'IBM AIX': '🖥 platform: Unix', 'IBM PowerKVM': '🖥 platform: Linux', 'IBM i': '🖥 platform: Unix', 'KVM for IBM z Systems': '🖥 platform: Linux', 'Kali Linux': '🖥 platform: Linux', 'Linux Mint': '🖥 platform: Linux', 'Mageia': '🖥 platform: Linux', 'Mandriva Linux': '🖥 platform: Linux', 'Manjaro Linux': '🖥 platform: Linux', 'MidnightBSD': '🖥 platform: BSD', 'NetBSD': '🖥 platform: BSD', 'NixOS': '🖥 platform: Linux', 'Nobara': '🖥 platform: Linux', 'OpenBSD': '🖥 platform: BSD', 'OpenWrt': '🖥 platform: Linux', 'Oracle Linux': '🖥 platform: Linux', 'Parallels': '🖥 platform: Linux', 'Pidora': '🖥 platform: Linux', 'PikaOS': '🖥 platform: Linux', 'Raspbian': '🖥 platform: Linux', 'RedHat Enterprise Linux': '🖥 platform: Linux', 'Rocky Linux': '🖥 platform: Linux', 'SUSE Linux Enterprise Server': '🖥 platform: Linux', 'Scientific Linux': '🖥 platform: Linux', 'Slackware': '🖥 platform: Linux', 'SliTaz GNU/Linux': '🖥 platform: Linux', 'Solaris': '🖥 platform: Unix', 'Source Mage GNU/Linux': '🖥 platform: Linux', 'SunOS': '🖥 platform: BSD', 'Tuxedo OS': '🖥 platform: Linux', 'Ubuntu': '🖥 platform: Linux', 'Ultramarine': '🖥 platform: Linux', 'Void Linux': '🖥 platform: Linux', 'Windows': '🖥 platform: Windows', 'Windows Subsystem for Linux v1': '🖥 platform: Linux', 'Windows Subsystem for Linux v2': '🖥 platform: Linux', 'XenServer': '🖥 platform: Linux', 'illumos': '🖥 platform: Unix', 'macOS': '🖥 platform: macOS', 'openSUSE': '🖥 platform: Linux'}¶
Maps all platform names to their labels.
- meta_package_manager.labels.LABELS: list[tuple[str, str, str]] = [('📦 manager: am', '#bfdadc', 'am'), ('📦 manager: apk', '#bfdadc', 'apk'), ('📦 manager: apm', '#bfdadc', 'apm'), ('📦 manager: apt-cyg', '#bfdadc', 'apt-cyg'), ('📦 manager: asdf-based', '#bfdadc', 'asdf, mise'), ('📦 manager: basalt', '#bfdadc', 'basalt'), ('📦 manager: bin', '#bfdadc', 'bin'), ('📦 manager: bpkg', '#bfdadc', 'bpkg'), ('📦 manager: cargo', '#bfdadc', 'cargo'), ('📦 manager: cave', '#bfdadc', 'cave'), ('📦 manager: choco', '#bfdadc', 'choco'), ('📦 manager: chromebrew', '#bfdadc', 'chromebrew'), ('📦 manager: clib', '#bfdadc', 'clib'), ('📦 manager: composer', '#bfdadc', 'composer'), ('📦 manager: conda-based', '#bfdadc', 'conda, mamba, micromamba, pixi'), ('📦 manager: cpan', '#bfdadc', 'cpan'), ('📦 manager: dotnet', '#bfdadc', 'dotnet'), ('📦 manager: dpkg-based', '#bfdadc', 'apt, apt-mint, deb-get, fink, nala, opkg, pacstall'), ('📦 manager: emerge', '#bfdadc', 'emerge'), ('📦 manager: eopkg', '#bfdadc', 'eopkg'), ('📦 manager: fish-based', '#bfdadc', 'fisher, oh-my-fish'), ('📦 manager: flatpak', '#bfdadc', 'flatpak'), ('📦 manager: fwupd', '#bfdadc', 'fwupd'), ('📦 manager: gcloud', '#bfdadc', 'gcloud'), ('📦 manager: gem', '#bfdadc', 'gem'), ('📦 manager: gext', '#bfdadc', 'gext'), ('📦 manager: gh-ext', '#bfdadc', 'gh-ext'), ('📦 manager: ghcup', '#bfdadc', 'ghcup'), ('📦 manager: guix', '#bfdadc', 'guix'), ('📦 manager: haxelib', '#bfdadc', 'haxelib'), ('📦 manager: homebrew', '#bfdadc', 'brew, cask, zerobrew'), ('📦 manager: jpm', '#bfdadc', 'jpm'), ('📦 manager: juliaup', '#bfdadc', 'juliaup'), ('📦 manager: krew', '#bfdadc', 'krew'), ('📦 manager: luarocks', '#bfdadc', 'luarocks'), ('📦 manager: macports', '#bfdadc', 'macports'), ('📦 manager: mas', '#bfdadc', 'mas'), ('📦 manager: mason', '#bfdadc', 'mason'), ('📦 manager: micro', '#bfdadc', 'micro'), ('📦 manager: miktex', '#bfdadc', 'miktex'), ('📦 manager: mpm', '#bfdadc', 'mpm'), ('📦 manager: nimble', '#bfdadc', 'nimble'), ('📦 manager: nix', '#bfdadc', 'nix'), ('📦 manager: npm-based', '#bfdadc', 'bun, npm, pnpm, volta, yarn, yarn-berry'), ('📦 manager: ollama', '#bfdadc', 'ollama'), ('📦 manager: opam', '#bfdadc', 'opam'), ('📦 manager: pacman-based', '#bfdadc', 'dkp-pacman, pacaur, pacman, pamac, paru, pikaur, trizen, yay'), ('📦 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: pyenv', '#bfdadc', 'pyenv'), ('📦 manager: pypi-based', '#bfdadc', 'pip, pipx, uv, uvx'), ('📦 manager: rpm-based', '#bfdadc', 'dnf, dnf5, urpmi, yum, zypper'), ('📦 manager: rustup', '#bfdadc', 'rustup'), ('📦 manager: scoop-based', '#bfdadc', 'scoop, sfsu'), ('📦 manager: sdkman', '#bfdadc', 'sdkman'), ('📦 manager: sheldon', '#bfdadc', 'sheldon'), ('📦 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: vagrant', '#bfdadc', 'vagrant'), ('📦 manager: vcpkg', '#bfdadc', 'vcpkg'), ('📦 manager: vim-based', '#bfdadc', 'lazy, vim-pack'), ('📦 manager: vscode-based', '#bfdadc', 'vscode, vscodium'), ('📦 manager: winget', '#bfdadc', 'winget'), ('📦 manager: xbps', '#bfdadc', 'xbps'), ('📦 manager: xcodes', '#bfdadc', 'xcodes'), ('📦 manager: yazi', '#bfdadc', 'yazi'), ('📦 manager: zef', '#bfdadc', 'zef'), ('📦 manager: zsh-based', '#bfdadc', 'antidote, antigen, zim, zinit, zplug'), ('🔌 plugin', '#fef2c0', 'Xbar/SwiftBar/GNOME Shell plugin code, documentation and features'), ('🖥 platform: BSD', '#bfd4f2', 'DragonFly BSD, FreeBSD, MidnightBSD, NetBSD, OpenBSD, SunOS'), ('🖥 platform: Linux', '#bfd4f2', 'AlmaLinux, Alpine Linux, ALT Linux, Amazon Linux, Android, Arch Linux, Buildroot, CachyOS, CentOS, …'), ('🖥 platform: macOS', '#bfd4f2', 'macOS'), ('🖥 platform: Unix', '#bfd4f2', 'Cygwin, GNU/Hurd, Haiku, IBM AIX, IBM i, illumos, Solaris'), ('🖥 platform: Windows', '#bfd4f2', 'Windows')]¶
Global registry of all labels used in the project.
Structure:
("label_name", "color", "optional_description")
- meta_package_manager.labels.LABEL_RENAMES: dict[str, tuple[str, ...]] = {'📦 manager: conda-based': ('📦 manager: conda',), '📦 manager: pypi-based': ('📦 manager: pip-based',)}¶
Labels a renamed one supersedes, emitted as labelmaker’s
rename-from.Folding managers into a group orphans the labels they used to carry, and the issues already filed against those.
rename-frommigrates one in place, which is the only lossless move available: GitHub keeps every issue and pull request attached across a rename, whereas creating the new label and deleting the old one drops the association.The mechanism is strictly one-to-one, so this map cannot express a merge: labelmaker errors outright when two
rename-fromlabels both exist, and falls toon-rename-clash(error, repomatic’s default) when the target already exists. An N-to-1 fold therefore names the single source carrying the most history, leaving the remainder to a hand migration, and a fold whose target label already exists names none at all.conda-basedis the same lossless shape: onlycondacarries history,pixiarrives with no label of its own, and the target does not exist yet, so the fold is genuinely one-to-one.dpkg-based,vim-based,zsh-basedandasdf-basedwere all synced into existence before their predecessors were retired, so a declared rename would only error; their orphans (fink,vim-pack,zinit,asdf,mise) carry no issue or pull request at all and are deleted rather than migrated.
- meta_package_manager.labels.CONTENT_RULES_STATIC: TLabelRules = [('🔌 plugin', ('gnome shell', 'gnome-shell', 'plugin', 'swiftbar', 'xbar'))]¶
Curated keywords feeding the content rules of labels not derived from the pool.
Holds bare keywords, like every other content rule: repomatic’s
apply-labelsdoes the anchoring and case-folding (seegenerate_content_rules()).
- meta_package_manager.labels.FILE_RULES_STATIC: TLabelRules = [('🔌 plugin', ('gnome-shell/**', 'meta_package_manager/bar_plugin*', 'tests/*bar_plugin*', 'tests/*gnome*', 'tests/gnome/**')), ('📦 manager: mpm', ('meta_package_manager/*',))]¶
File rules for labels that are not derived from the pool.
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-based': ('asdf-vm', 'mise-en-place'), 'cargo': ('crate', 'rust'), 'cave': ('exherbo', 'paludis'), 'choco': ('chocolatey',), 'chromebrew': ('chrome os', 'chromeos'), 'composer': ('php',), 'conda-based': ('anaconda', 'conda-forge', 'miniconda', 'prefix.dev'), 'cpan': ('perl',), 'dotnet': ('nuget',), 'dpkg-based': ('aptitude', 'debian', 'dpkg', 'ubuntu'), 'emerge': ('gentoo', 'portage'), 'eopkg': ('solus',), 'flatpak': ('flathub',), 'fwupd': ('lvfs',), 'gem': ('ruby',), 'gh-ext': ('gh extension', 'github cli'), 'guix': ('gnu guix',), 'homebrew': ('homebrew',), 'mas': ('app store', 'app-store'), 'nix': ('nixos', 'nixpkgs'), 'npm-based': ('node.js', 'nodejs'), 'pacman-based': ('arch',), 'pkcon': ('packagekit',), 'pkg-based': ('freebsd', 'freebsd ports'), 'pkg-tools': ('openbsd',), 'pkgin': ('netbsd', 'pkgsrc'), 'pwsh-gallery': ('powershell', 'powershell gallery', 'psgallery', 'psresourceget'), 'pypi-based': ('pypi',), 'rpm-based': ('fedora', 'mageia', 'opensuse', 'redhat', 'rhel', 'rpm', 'suse'), 'sdkman': ('sdk man',), 'slapt-get': ('slackware',), 'snap': ('snapcraft',), 'sorcery': ('source mage',), 'steamcmd': ('valve',), 'sun-tools': ('solaris', 'svr4'), 'swupd': ('clear linux', 'clearlinux'), 'tazpkg': ('slitaz',), 'tlmgr': ('ctan', 'tex live', 'texlive'), 'vscode-based': ('visual studio', 'visual studio code'), 'xbps': ('void linux',)}¶
Curated ecosystem keywords feeding each manager label’s content rule.
Keyed by the manager or group ID the label derives from. These are the only content patterns a manager label gets: the bare manager IDs are deliberately left out (see
generate_content_rules()). Add only terms that are both unambiguously about this manager and absent from anything mpm prints itself: the✓ <id>trail, the<id>: <count>summary line, themanagerstable (which lists every manager’s ID and CLI binary) and the$-prompt command disclosure. That rules out manager IDs and CLI names (fwupdmgr,pwsh), leaving the distro, language and brand names a human types in an issue. A manager with no such term gets no content rule and is labelled by hand.Skip anything that doubles as a common word even once word-anchored (
port,flat,mint,void): dropping the ID removed the implicit AND-guard those leaned on, so on their own they match unrelated prose.
- meta_package_manager.labels.PLATFORM_CONTENT_KEYWORDS: dict[str, tuple[str, ...]] = {'BSD': ('bsd',), 'Linux': ('linux',), 'Unix': ('unix',), 'Windows': ('c:', 'microsoft', 'windows'), 'macOS': ('apple', 'mac os', 'macos', 'os x', 'osx')}¶
Curated keywords feeding each platform label’s content rule.
- meta_package_manager.labels.generate_content_rules()[source]¶
Build every content rule: the static ones plus one per manager or platform label that has curated keywords.
Manager labels are driven solely by
MANAGER_CONTENT_KEYWORDS, never by the bare manager IDs or CLI names. mpm enumerates every installed manager in its own output (the✓ <id>trail, the<id>: <count>summary line, themanagerstable), so a pasted trace would otherwise make every manager on the user’s system match at once: acpan-only report came back taggedmise,pipanduvmerely because they sat in the trace. The keywords are the distro, language and brand names a human types, which mpm never prints.Keywords are emitted raw, one pattern each, because repomatic’s
apply-labelsapplies a label as soon as any one of its patterns matches, and compiles a bare pattern case-insensitively with a\banchor on each edge that is itself a word character. That is what the retiredgithub/issue-labelerneeded hand-built here: its all-of semantics forced a/…/ialternation to mean “any keyword wins”, and its case-sensitive default forced theiflag. Keep the keywords bare, and reach for the/body/flagsescape hatch only for something an anchored literal cannot express.A label with no keyword is skipped: that manager gets no content rule, only its file rule. Rules are sorted by label, and each label’s keywords among themselves, both cases folded.
- 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.logo module¶
Terminal rendition of the mpm brand mark, and the --version screen it decorates.
The mark is the isometric open box of docs/assets/logo-square.svg, redrawn as a
half-block raster: two sub-pixel rows share one terminal line, so LOGO paints
as half as many lines as it has rows.
Half-blocks are the primitive because a terminal cell is twice as tall as it is
wide, which makes their sub-pixels square. That also rules out the alternative worth
naming, since it looks obvious and is not: drawing the mark as directional line-art
characters (╱, ╲, │), the way edge-detection ASCII art does. An isometric edge
sits at slope 0.25 in cell space, far too shallow for ╱, and that figure is
scale-invariant — no height fixes it. Stretching the projection to 45° earns the
diagonals but leaves the mark only ~12 columns wide, too few for the four flaps.
Caution
The mark’s structure is carried by color alone: strip the ANSI codes and it
collapses into one solid silhouette. LogoVersionOption therefore only draws
it when color reaches the output, falling back to click-extra’s plain rendering
everywhere else — which is also the form machine readers parse, the bar plugin
probing mpm --no-color --version.
- meta_package_manager.logo.DOCS_URL = 'https://mpm.run'¶
Canonical documentation host, advertised on the version screen.
- meta_package_manager.logo.TAGLINE = 'Meta Package Manager'¶
What the
mpmacronym stands for, as spelled out under the logo’s wordmark.
- meta_package_manager.logo.LOGO: tuple[str, ...] = (' .. .. ', ' ..... ..... ', ' ......::++...... ', ' ......::::++++...... ', ' ....::::....++++.... ', ' .:::::++::+++++. ', ' ...::::++::++++... ', ' ......:::+:+++...... ', ' ......:::+++...... ', ' +......:+......: ', ' +++....+:....::: ', ' ++++++++:::::::: ', ' ++++++++:::::::: ', ' ++++++++:::::::: ', ' +++++++::::::: ', ' +++++::::: ', ' +++::: ', ' +: ')¶
The mark, as one shading tone per sub-pixel, two rows to a rendered line.
A space is transparent and leaves the terminal’s own background alone. The rest name the plane the sub-pixel faces, which is what an isometric solid shades by:
.up (the flap tops and the cube’s lid),:right,+left. No outlines: the solid is reconstructed from the three shades its planes catch, the way an unlit render reads.Rasterized from a model re-derived from the SVG’s polygon geometry — rim, flap fold depth, body height and cube placement all measured off it — then mirrored about its vertical axis, swapping
+for:since what faces left on one side faces right on the other. Symmetry is imposed at that point rather than left to the rasterizer, which drifted three separate ways: a column count rounded off the row scale left the sampling grid off-centre, paint order tilted mirror-paired surfaces, and an odd column count gave the axis a column that would have had to face both ways at once.The box is open, so the two far walls show through its rim, and they carry the plane opposite the one they sit behind: the far-left wall is lit as a right-facing face, which is why the interior’s left half is
:where the exterior’s is+. That mirroring is what keeps the floating cube legible against the wall behind it, each of its faces landing on the other tone. The interior cells were filled by testing every empty cell’s centre against the rim quadrilateral in the artwork’s own coordinates, so they cannot drift from the SVG the way an eyeballed fill would.Hand-editing a row is fine, but keep the rows equal in length and even in number, keep the width even, and keep every row symmetric.
- meta_package_manager.logo.BRAND_WASH = '#d3d3f6'¶
The pale lavender of the artwork’s lit planes.
One of the two colors the whole identity is drawn from, with
BRAND_INK. Declared here as well as indocs/brand_update.pybecause runtime code cannot read the SVG sources, which are not shipped in the wheel;test_ansi_logo_tracks_the_brand_palettefails if the two ever disagree.
- meta_package_manager.logo.BRAND_INK = '#2d2364'¶
The deep purple of the artwork’s shadowed planes, and of its lettering.
The second brand color. It used to be the wordmark’s alone, the box outline carrying a third, lighter purple that this mark’s shadow faces stood in for, so the terminal rendition inherited a mismatch the artwork has since dropped.
- meta_package_manager.logo.BRAND_MID = '#807bad'¶
The midpoint of the two, for the third plane an isometric solid needs.
Computed rather than chosen, so the palette remains two colors and a derivation. The artwork gained it when the mark went flat: with no outline left to separate the faces, a right-facing plane can no longer share a value with a left-facing one.
- meta_package_manager.logo.TONES: dict[str, int] = {'+': 17, '.': 189, ':': 103}¶
Xterm-256 palette index per shading tone of
LOGO.One index per plane, mapped to the closest thing the palette can render: the lit faces take
BRAND_WASH, the shadowed onesBRAND_INK, and the right-hand planesBRAND_MID.Indices rather than truecolor: the 256-color cube is the widest-supported palette, and click emits no downgrade of its own for a terminal that cannot do 24-bit — including Apple’s own Terminal, where a 24-bit sequence would take the mark with it. The cost is borne entirely by the ink: the cube resolves dark colors coarsely, so
#00005fis as close as it gets (a perceptual distance of ~24, against ~3 for the wash). It is the nearest entry that keeps any chroma at all, the closer ones by raw distance all being grays, which would paint a purple mark in ash.
- meta_package_manager.logo.LOGO_WIDTH = 22¶
Columns the mark occupies, every rendered line being padded to it.
- meta_package_manager.logo.LOGO_LINES = 9¶
Terminal lines the mark renders to, two sub-pixel rows making one.
- meta_package_manager.logo.GUTTER = ' '¶
Blank columns separating the mark from the metadata column beside it.
- meta_package_manager.logo.LABEL_WIDTH = 10¶
Column width the metadata keys are padded to, aligning their values.
- meta_package_manager.logo.Colors¶
A cell’s foreground and background palette indices.
Noneis transparent, leaving the terminal’s own color to show through.
- meta_package_manager.logo.env_summary()[source]¶
One-line interpreter and platform summary.
Feeds both the plain
--versionoutput (as click-extra’senv_infotemplate field) and the version screen’s ownPythonandPlatformrows, so the two renderings can never disagree on what they report.- Return type:
- meta_package_manager.logo.platform_label()[source]¶
Current platform and CPU architecture, as displayed to the user.
- Return type:
- meta_package_manager.logo.render_logo()[source]¶
Paint
LOGOinto styled lines, one per pair of sub-pixel rows.A cell whose two sub-pixels carry different tones paints the top one as foreground over the bottom one as background, which is what fits two independent colors on one line. Runs of cells sharing a color pair are styled together rather than one escape sequence per character, keeping the mark from tripling in size.
- meta_package_manager.logo.version_screen(prog_name, version)[source]¶
Compose the mark and the metadata column into the full version screen.
The metadata is centred against the mark’s height, and either column may be the taller of the two: a line missing from one side simply renders blank.
Returns
Nonewhen the terminal is too narrow to seat the two columns side by side, leaving the caller to fall back rather than emit a wrapped mess. The threshold is measured off the metadata actually built, since its widest row grows with the manager count and the platform name. A non-interactive stream reportsshutil’s 80-column default, wide enough that a redirected-but-forced-color run still gets the screen it asked for.
- meta_package_manager.logo.colors_reach_output()[source]¶
Will ANSI codes survive all the way to the user’s terminal?
Resolves click-extra’s color tri-state, deferring to the output stream’s TTY status on its
autodefault, exactly asclick.echodoes when it decides whether to strip the codes itself.- Return type:
- class meta_package_manager.logo.LogoVersionOption(param_decls=None, message=None, fields=None, styles=None, message_style=None, is_flag=True, expose_value=False, is_eager=True, help='Show the version and exit.', **kwargs)[source]¶
Bases:
VersionOption--version, upgraded to the full version screen when the terminal allows it.Three conditions gate the screen, and failing any one of them falls back to click-extra’s plain
messagetemplate unchanged — which is a deliberate guarantee, not just a default: that plain form is the one every machine reader parses.Color reaches the output. The mark keeps its structure in its colors alone, so a stripped one is an unreadable blob.
The terminal is wide enough to seat the metadata column beside the mark without wrapping it.
Accessible mode is off. A raster read out cell by cell is noise to a screen reader, so
--accessiblekeeps the plain two lines.
Preconfigured as a
--versionoption flag.- Parameters:
message (
str|None) – the message template to print, in format string syntax. Defaults to{prog_name}, version {version}.fields (
Mapping[str,Any] |None) – mapping of template field name to a forced value, overriding the value auto-computed for that field. Keys must be members oftemplate_fields(for example{"version": "1.2.3"}).styles (
Mapping[str,Callable[[str],str] |None] |None) – mapping of template field name to itsStyle, merged overdefault_styles. PassNoneas a value to clear a field’s default style. Keys must be members oftemplate_fields.message_style (
Callable[[str],str] |None) – fallback style for the message literals and for any field that has no style of its own.
- meta_package_manager.logo.version_screen_params()[source]¶
click-extra’s default parameters, with
--versionswapped for our own.Passed to the CLI’s
@group(params=…), the documented hook for tuning that list. Swapping the instance in place keeps click-extra’s carefully ordered parameter sequence, and keepsversion_fields=working: it targets whichever parameter is aVersionOption, which the subclass still is.
meta_package_manager.manager module¶
Abstract base class tying together every package manager definition.
Defines meta_package_manager.manager.PackageManager, the class each concrete
manager in meta_package_manager.managers inherits from, together with its
meta_package_manager.manager.MetaPackageManager metaclass and the
meta_package_manager.manager.ManagerScope classification.
A subclass declares its identity (supported platforms, version requirement, maintenance
status) and implements the operations it supports (installed, outdated,
install, upgrade, …). The CLI-execution engine it inherits lives in
meta_package_manager.execution, the operation vocabulary in
meta_package_manager.capabilities, and the package objects operations yield in
meta_package_manager.package. On top of the engine, this module adds the
availability policy: whether the manager is supported, fresh, and ready to use.
- meta_package_manager.manager.JSON_FIELD_SELECTOR_REGEX = re.compile('^(?P<key>[^\\[\\]]+?)(?:\\[(?P<index>\\d+)\\])?$')¶
Parse a JSON field selector: a key name with an optional
[N]list index.A bare
versionmaps the package field to the item’sversionkey. Aversions[0]selector additionally picks one element out of a list-valued key (zerobrew reports each package’s installed versions as an array). Anything more nested stays out on purpose: a query needing real JSON traversal is better served by a custom parser. Shared byPackageManager.parse_json_items()and the declarative-manager validation inmeta_package_manager.definitions.
- class meta_package_manager.manager.ManagerScope(*values)[source]¶
Bases:
EnumFilesystem scope a package manager operates within.
- SYSTEM = 'system'¶
Manages software installed globally, machine-wide.
All currently-maintained managers are system-scoped.
- PROJECT = 'project'¶
Manages dependencies confined to a project’s working tree.
Not supported yet. The user-facing rationale, the ecosystems this would cover and the architectural work it waits on are catalogued once in Unsupported managers; the extension point is
meta_package_manager.manager.PackageManager.discover_projects().
- class meta_package_manager.manager.MetaPackageManager(name, bases, dct)[source]¶
Bases:
typeCustom metaclass used as a class factory for package managers.
Sets some class defaults, but only if they’re not redefined in the final manager class.
Also normalize list of platform, by ungrouping groups, deduplicate entries and freeze them into a set of unique platforms.
- class meta_package_manager.manager.PackageManager[source]¶
Bases:
CLIExecutorBase class from which all package manager definitions inherits.
Initialize
cli_errorslist.- scope: ClassVar[ManagerScope] = 'system'¶
Whether the manager operates on globally-installed software or project-local dependencies.
Defaults to
ManagerScope.SYSTEM, which covers every manager maintained today: they install and query software machine-wide. Project-scoped managers (Poetry, Bundler, Maven, …) resolve dependencies confined to a working tree and are not supported yet.
- unmaintained: bool = False¶
A manager whose upstream project is no longer maintained.
Covers projects that are officially retired and those we infer are abandoned: archived on their forge, left without a release or commit for years, formally superseded by a successor, or part of a discontinued platform. See the stability policy in
CLAUDE.mdfor the full criteria.An unmaintained manager is hidden from package selection by default (you can still use it by explicitly calling for it on the command line), and is exempt from the project stability policy: it may be dropped, in part or in full, in any release and without notice, once keeping it working becomes too burdensome.
Unmaintained managers are kept out of the functional and integration test matrices, so an unreliable or flaky one never blocks a release and we save CI resources. The commitment is to keep the wrapper for as long as that stays cheap: the cheap static invariants (ID format, attribute ordering, …) still apply for as long as the manager’s code lives in the source tree, to keep that code valid.
Every unmaintained manager must document itself through
unmaintained_message.
- unmaintained_message: str | None = None¶
Evidence and rationale for the
unmaintainedflag, as a MyST markdown block.Rendered into the documentation (the manager’s page, and a
⚠️marker in the manager tables). May embed markdown links to the archival notice, the successor project, or the discontinuation announcement. Required for every manager whoseunmaintainedflag is set, and only meaningful on such managers. Enforced bytest_unmaintained.
- maintenance_note: str | None = None¶
A watch note about a still-maintained upstream whose activity is slowing or whose status is ambiguous, as a MyST markdown block.
Unlike
unmaintained, this is purely informational: the manager stays in the default selection and in the test matrices. It renders as a{note}admonition atop the manager’s documentation page, flagging upstreams worth keeping an eye on (a slow release cadence, superseded-but-still-shipped tools, a discontinued platform still under vendor support). May embed markdown links. Mutually exclusive withunmaintained: a confirmed-dead manager carries anunmaintained_messageinstead. Enforced bytest_maintenance_note.
- id: str = 'packagemanager'¶
Package manager’s ID.
Derived by defaults from the lower-cased class name in which underscores
_are replaced by dashes-.This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.
- name: str = 'PackageManager'¶
Return package manager’s common name.
Default value is based on class name.
- homepage_url: str | None = None¶
Home page of the project, only used in documentation for reference.
- logo: str | None = None¶
Slug of the brand mark standing for this manager in the documentation.
Names an SVG vendored under
docs/assets/managers/, whose provenance and license are recorded indocs/assets/managers/logos.yaml. Inlined at the top of the manager’s page bymeta_package_manager._docs; a manager leaving it unset keeps the page’s default package glyph.Several managers legitimately share one slug, either because they wrap the same upstream (
brewandcask) or because the tool has no mark of its own and its ecosystem’s stands in (aptunder Debian’s swirl,cargounder Rust’s gear). Documentation-only, likehomepage_url: no CLI output reads it.
- brewfile_entry_type: ClassVar[str | None] = None¶
Name of the Brewfile DSL entry type this manager maps to, or
Noneif the manager has no Brewfile equivalent.Set by the subset of managers Homebrew Bundle’s DSL covers, and consumed by
meta_package_manager.brewfilewhen rendering the output ofmpm dump --brewfile. Which manager maps to which entry is tabulated from these declarations in Snapshot and export, section “Brewfile”, where the export’s own quirks are documented too.
- brewfile_skip_warning: ClassVar[str | None] = None¶
Optional stderr warning emitted when this manager’s installed packages are excluded from a Brewfile dump.
Set on managers where silently dropping the entries would mislead the user. The string supports a single
{count}placeholder for the installed-package count.
- platforms: frozenset[Platform] | Group | Platform | Iterable[Platform | Group] = frozenset({})¶
List of platforms supported by the manager.
Allows for a mishmash of platforms and groups of platforms. Will be normalized into a
frozensetofPlatforminstances at instantiation.
- requirement: str | None = None¶
Version requirement specifier.
Supports a comma-separated range of constraints (e.g.
">=1.20.0,<2.0.0"). A bare version string like"1.20.0"is treated as>=1.20.0.Parsed by
meta_package_manager.version.VersionRange.Defaults to
None, which deactivates version check entirely.
- virtual: bool = True¶
Should we expose the package manager to the user?
Virtual package manager are just skeleton classes used to factorize code among managers of the same family.
- ignore_auto_updates: bool = True¶
Some managers can report or ignore packages which have their own auto-update mechanism.
- split_name_version(token)[source]¶
Split a dash-joined
<package_id>-<version>token into its two parts.Matches
tokenagainst_NAME_VERSION_REGEXP(or the subclass’s override of it) and returns the(package_id, version)pair, orNonewhen the token carries no recognizable version. Shared by every manager whose listings glue the name and version together.
- parse_json(output)[source]¶
Parse a query’s JSON
output, tolerating empty and malformed captures.The shared first step of every JSON-emitting query, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._parse_spec_output()). ReturnsNonewhen the command produced no output (a manager with nothing to report often prints nothing at all), and when the output is not valid JSON, which logs one warning tagged with the manager ID instead of raising: a query that cannot be parsed yields no packages, mirroring how the fan-out commands swallow a failed CLI call into an empty result.Queries whose failure semantics differ keep their own parsing: a per-line NDJSON stream (
pkg search), a hardCLIErroron malformed payloads (pwsh-gallery), a best-effort metadata enrichment logging atDEBUG(brew info).
- parse_regex_lines(pattern, output)[source]¶
Yield one package per line of
outputmatchingpattern.The shared engine of every line-oriented text listing, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._make_query_property()). The pattern is searched in each line, and its named groups map straight onto the package fields:package_id(required: a match without one is skipped),installed_version,latest_version,name,descriptionandarch, empty and absent groups being dropped.Managers whose listings need per-line post-processing (multi-version reduction, name/version splitting, cross-query joins) keep their own loop and this stays their reference semantics.
- parse_json_items(output, *, list_path=None, fields)[source]¶
Yield one package per item of a JSON listing.
The shared engine of every flat-JSON query, for built-in managers and config-defined operations alike (see
meta_package_manager.definitions._make_query_property()). The document is parsed throughparse_json()(so a malformed payload warns and yields nothing), the package array is reached by walking the dottedlist_path(Nonewhen the document is itself the array), andfieldsmaps each package field (package_id, required, plus any ofinstalled_version,latest_version,name,description,arch) to its JSON selector: a key name with an optional[N]list index, likeversionorversions[0](seeJSON_FIELD_SELECTOR_REGEX). Items missing theirpackage_idand fields resolving toNoneare dropped.
- package(**kwargs)[source]¶
Instantiate a
Packageobject from the manager.Sets its
manage_idto the manager it belongs to.- Return type:
- brewfile_entry(package)[source]¶
Return
(entry_name, entry_options)for a Brewfile line, orNoneto skip the package.Default: emit
meta_package_manager.package.Package.idas the entry name with no options. Override on managers whose Brewfile DSL counterpart expects a different shape:masuses the app name withid: ADAM_ID,flatpakaddswith: ["remote"]. Only called whenbrewfile_entry_typeis set.
- property available: bool[source]¶
Is the package manager available and ready-to-use on the system?
Returns
Trueonly if the main CLI:
Short, human-readable explanation of why
availableisFalse, orNoneif the manager is available.Returned in priority order so the most actionable cause is reported first: platform support, then CLI lookup, then executable bit, then version requirement.
- property installed: Iterator[Package]¶
List packages currently installed on the system.
Optional. Will be simply skipped by mpm if not implemented.
- installed_or_empty()[source]¶
Materialized
installed, or an empty tuple on CLI failure.Best-effort inventory snapshot for the
installed,dumpandsbomsubcommands, and for theinstalled_idslookup behindremoveandupgrade <packages>: each wants “give me what’s installed, and just skip this manager if its CLI blew up” rather than re-implementing the samemeta_package_manager.execution.CLIErrorswallow. Logs one canonical warning on error and returns()so the caller carries on with the other managers.
- property installed_ids: frozenset[str][source]¶
Installed package IDs, materialized once from
installed_or_empty().Routed through the tolerant
installed_or_empty()rather thaninstalled()because its callers ask a discovery question: which managers have this package? A manager whose CLI just failed has no answer to give, which is not the same as a fatal error. Sourcing a spec forremoveandupgrade <packages>reads this for every selected manager, so a single broken CLI would otherwise abort the whole command before the managers that do have the package are ever tried.Contrast
installed_version_map, which deliberately keeps raising: it is read from inside anoutdatedparser, where an empty map does not mean “no answer” but silently reports every outdated package with an unknown installed version.
- property installed_version_map: dict[str, TokenizedString | str | None][source]¶
Installed versions keyed by package ID, materialized once from
installed().Convenience for
outdatedparsers that report each package’s latest version but not its currently-installed one, and so must look the latter up by ID (snap,xbps). The value mirrorsmeta_package_manager.package.Package.installed_version, whose declared type still carries the transientstrit normalizes away in__post_init__.
- package_metadata_batch(packages)[source]¶
Yield
(package, metadata)pairs enriched with whatever rich per-package data this manager can surface.Called by
mpm sbomin--bundledmode to populate licenses, checksums, download URLs, supplier/originator, and the declared dependency graph. The base implementation yieldsmeta_package_manager.package.EMPTY_METADATAfor each package and stays compatible with managers that do not (yet) expose richer metadata: their SBOM entries stay at the minimalPackagelevel, matching the historical and--minimalmodes.Manager subclasses override this with their native query path:
bulk shell-outs when the CLI accepts a package list (
brew info --json=v2 --installed,dpkg-query -W,apt-cache show);on-disk parsing when the metadata already lives on the filesystem (pip’s
.dist-infodirectories, Homebrew’s per-formulasbom.spdx.json, dpkg’s.md5sums).
The yielded pairs do not need to preserve the input order; the SBOM renderer matches by
Packageidentity. Implementations are expected to swallow per-package extraction errors and yieldmeta_package_manager.package.EMPTY_METADATAfor the affected packages rather than failing the whole scan: a single misbehaving formula must not abort an enrichment pass spanning hundreds of packages.Todo
Today every extractor is local-only (shell-outs to the manager’s CLI, plus on-disk reads). When extractors start reaching for network resources (PyPI’s JSON API, npm’s registry, crates.io, GitHub’s security advisories) the
--bundledflag will no longer be a fine-grained enough knob: some users will want enrichment but not network traffic (offline scans, CI without egress). The natural split is a future--network/--no-networkflag layered under--bundledto gate the network-touching code paths specifically, leaving local enrichment always-on for--bundled.- Return type:
- property outdated: Iterator[Package]¶
List installed packages with available upgrades.
Optional. Will be simply skipped by mpm if not implemented.
- property refiltered_outdated: Iterator[Package]¶
Wraps
outdated()with a version-equality filter.Some package managers report packages as outdated when the version strings differ at the character level but are numerically equal after parsing (e.g., Perl floating-point versions
2.0000vs2.000000). This filter drops those false positives.
- 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 orphans: Iterator[Package]¶
List packages installed as dependencies that nothing requires anymore.
The read-only counterpart of the
--orphansaction flags: wherempm cleanup --orphansremoves the orphans, this query only reports them, through the manager’s native listing (pacman --query --deps --unrequired,brew autoremove --dry-run,dnf repoquery --unneeded, …). mpm builds no dependency graph: the manager decides what is orphaned.Optional. Will be simply skipped by mpm if not implemented.
- search(query, extended, exact)[source]¶
Search packages available for install.
There is no need for this method to be perfect and sensitive to
extendedandexactparameters. If the package manager is not supporting these kind of options out of the box, just returns the closest subset of matching package you can come up with. Finer refiltering will happens in themeta_package_manager.manager.PackageManager.refiltered_search()method below.Optional. Will be simply skipped by mpm if not implemented.
- 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:
- remove_orphan(package_id)[source]¶
Remove one package together with the dependencies it alone pulled in.
The opt-in counterpart to
meta_package_manager.manager.PackageManager.remove(), surfaced asmpm remove --orphans. It maps to the manager’s native “remove and drop now-unneeded dependencies” verb (apt remove --auto-remove,pacman --remove --recursive,dnf autoremove, …), so mpm builds no dependency graph of its own.Optional. A manager with no such native verb leaves this
NotImplementedError;mpm remove --orphansthen falls back tometa_package_manager.manager.PackageManager.remove()and logs oneINFOcapability-skip.- Return type:
- sync()[source]¶
Refresh package metadata from remote repositories.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- cleanup()[source]¶
Run the manager’s non-destructive cleanup categories.
Not an operation managers define anymore:
cleanupis the fixed composition of the non-destructive category methods a manager overrides (cleanup_cache(), thencleanup_repair()). The orphan sweep never joins in, native or synthesized: it is the one category that removes packages, so it only runs on an explicitmpm cleanup --orphans(or a directcleanup_orphan()call), keeping a plaincleanuppackage-preserving on every manager.A manager overriding no category method does not advertise the
cleanupoperation at all (seemeta_package_manager.capabilities.implements()) and this composer is then a no-op.- Return type:
- cleanup_orphan()[source]¶
Remove every orphaned package on the system, sparing the caches.
The system-wide “remove all packages nothing depends on anymore” sweep (
apt autoremove,brew autoremove,flatpak uninstall --unused, …). The one cleanup category that removes packages, so it is deliberately kept out of the plaincleanup()composition and only runs on an explicitmpm cleanup --orphans.Distinct from
meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As withcleanup(), mpm builds no dependency graph: the manager decides what is orphaned.A manager with no native sweep verb is backfilled by this base implementation when it supports both the
orphansquery and package removal: list the orphans, remove each one (withremove_orphan()when available, so every listed root takes its own now-orphaned subtree along), then re-query and repeat until the listing settles, since removing an orphan can orphan its own dependencies. The exact pattern of the synthesized fullupgrade --all, and the in-process equivalent of Arch’s classicpacman -Rns $(pacman -Qtdq)idiom. The re-query loop stops as soon as a round makes no progress, so removal failures cannot spin it forever.A manager implementing neither a native sweep nor the
orphansquery propagatesNotImplementedError, andmpm cleanup --orphanssimply skips it.- Return type:
- cleanup_cache()[source]¶
Prune the manager’s caches, downloads and other left-over artifacts.
The cache category of
cleanup(), surfaced asmpm cleanup --cacheand subtracted by--skip-cache(apt clean,dnf clean all,brew cleanup,npm cache clean, …). The broadest category: for most managers the whole cleanup amounts to it.Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- cleanup_repair()[source]¶
Verify and repair the manager’s local installation state.
The repair category of
cleanup(), surfaced asmpm cleanup --repairand subtracted by--skip-repair(flatpak repair --user).Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- doctor_cli()[source]¶
Returns the complete CLI running the manager’s native self-diagnosis.
The invocation must be read-only (
brew doctor,pip check,pacman --database --check, …):doctor()runs it, never mpm’s mutating machinery. The surveyed doctor verbs share one convention this contract leans on: a non-zero exit code means problems were found.Optional. Will be simply skipped by mpm if not implemented.
- doctor()[source]¶
Run the native self-diagnosis, returning
(healthy, report).Runs
doctor_cli()and interprets the outcome with a contract of its own, distinct from every other operation:Health is the exit code alone.
run()’s failure gate tolerates a non-zero exit with a silent<stderr>(a benign status for query parsers), but for a diagnosis that exit is the verdict:pip checkreports its conflicts on<stdout>only and would read as healthy under the gate.The report merges both streams. The tools split their findings across them (
brew doctorwarns on<stderr>), and the report is relayed verbatim to the user: there is nothing to parse.The diagnosis is not an error. The failure-gate entry an unhealthy exit may have accumulated is reclaimed from
cli_errors, so the end-of-run error summary is not inflated by a verdict mpm doctor``already reports on its own. The gate’s``WARNING diagnosis relay is skipped for the same reason (doctorsits in the gate’s_DIAGNOSIS_EXEMPT_OPERATIONS): the findings land in the report, verbatim. A run that never completed (timeout, interrupt, missing binary) keeps its entry: that is a genuine plumbing error, and the manager reports unhealthy.
- discover_projects()[source]¶
Locate project trees this manager governs by scanning the filesystem.
Extension point reserved for
ManagerScope.PROJECTmanagers: detecting virtual environments, lockfiles, or project manifests scattered across the filesystem.Caution
Not implemented for any manager yet. System-scoped managers (the default) own no project trees to discover.
Todo
Implement project-scope discovery. The candidate ecosystems, the project files that signal each and the architecture this waits on are catalogued in Unsupported managers.
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 eitherTokenizedStringorNone. 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.
- 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.
- 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.am.AM'>, <class 'meta_package_manager.managers.antidote.Antidote'>, <class 'meta_package_manager.managers.antigen.Antigen'>, <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.bin.Bin'>, <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.pacman.DkpPacman'>, <class 'meta_package_manager.managers.dnf.DNF'>, <class 'meta_package_manager.managers.dnf.DNF5'>, <class 'meta_package_manager.managers.dotnet.DotNet'>, <class 'meta_package_manager.managers.emerge.Emerge'>, <class 'meta_package_manager.managers.eopkg.EOPKG'>, <class 'meta_package_manager.managers.fisher.Fisher'>, <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.gext.Gext'>, <class 'meta_package_manager.managers.ghcup.GHCup'>, <class 'meta_package_manager.managers.guix.Guix'>, <class 'meta_package_manager.managers.lazy.Lazy'>, <class 'meta_package_manager.managers.luarocks.LuaRocks'>, <class 'meta_package_manager.managers.mamba.Mamba'>, <class 'meta_package_manager.managers.mas.MAS'>, <class 'meta_package_manager.managers.mason.Mason'>, <class 'meta_package_manager.managers.mamba.Micromamba'>, <class 'meta_package_manager.managers.miktex.MiKTeX'>, <class 'meta_package_manager.managers.mise.Mise'>, <class 'meta_package_manager.managers.nala.Nala'>, <class 'meta_package_manager.managers.nimble.Nimble'>, <class 'meta_package_manager.managers.nix.Nix'>, <class 'meta_package_manager.managers.npm.NPM'>, <class 'meta_package_manager.managers.oh_my_fish.OhMyFish'>, <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.pacman.Pikaur'>, <class 'meta_package_manager.managers.pip.Pip'>, <class 'meta_package_manager.managers.pipx.Pipx'>, <class 'meta_package_manager.managers.pixi.Pixi'>, <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.sheldon.Sheldon'>, <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.pacman.Trizen'>, <class 'meta_package_manager.managers.uv.UV'>, <class 'meta_package_manager.managers.uv.UVX'>, <class 'meta_package_manager.managers.vagrant.Vagrant'>, <class 'meta_package_manager.managers.vcpkg.VCPKG'>, <class 'meta_package_manager.managers.vim_pack.Vim_Pack'>, <class 'meta_package_manager.managers.volta.Volta'>, <class 'meta_package_manager.managers.winget.WinGet'>, <class 'meta_package_manager.managers.xbps.XBPS'>, <class 'meta_package_manager.managers.yarn.YarnBerry'>, <class 'meta_package_manager.managers.yarn.YarnClassic'>, <class 'meta_package_manager.managers.pacman.Yay'>, <class 'meta_package_manager.managers.dnf.YUM'>, <class 'meta_package_manager.managers.zef.Zef'>, <class 'meta_package_manager.managers.zim.Zim'>, <class 'meta_package_manager.managers.zinit.Zinit'>, <class 'meta_package_manager.managers.zplug.Zplug'>, <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.managerssubpackage, andare sub-classes of
meta_package_manager.manager.PackageManager, and- are not
meta_package_manager.manager.PackageManager.virtual, meaning they 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', 'plan', 'progress', 'require_cooldown_support', 'stop_on_error', 'sudo', 'timeout'})¶
List of extra options that are allowed to be set on managers during the use of the
meta_package_manager.pool.ManagerPool.select_managers()helper below.
- property register: dict[str, PackageManager][source]¶
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][source]¶
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][source]¶
IDs of managers added at runtime from configuration definitions.
Populated by
add_manager(). Disjoint frombuiltin_manager_ids.
- property bundled_manager_ids: frozenset[str][source]¶
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][source]¶
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]][source]¶
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, ...][source]¶
All recognized manager IDs.
Returns a list of sorted items to provide consistency across all UI, and reproducibility in the order package managers are evaluated.
- property maintained_manager_ids: tuple[str, ...][source]¶
All manager IDs which are not unmaintained.
- property default_manager_ids: tuple[str, ...][source]¶
All manager IDs supported on the current platform and not unmaintained.
Must keep the same order defined by
meta_package_manager.pool.ManagerPool.all_manager_ids.
- property unsupported_manager_ids: tuple[str, ...][source]¶
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', 'pamac', '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': {'conda', 'pixi'}, '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': {'haxelib'}, 'helm': None, 'hex': None, 'huggingface': None, 'julia': None, 'luarocks': {'luarocks'}, 'maven': None, 'melpa': None, 'meteor': None, 'mlflow': None, 'nim': {'nimble'}, 'nix': {'nix'}, 'npm': {'bun', 'npm', 'pnpm', 'volta', 'yarn', 'yarn-berry'}, 'nuget': {'dotnet'}, 'oci': None, 'opam': None, 'openwrt': {'opkg'}, 'osgi': None, 'p2': None, 'pear': None, 'pecl': None, 'perl6': {'zef'}, '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[source]¶
- 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 or a plan run (no state-changing 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.", max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='manager_name', label='Name', description="Manager's common name.", max_width=None), SortableField.MANAGER_NAME), (ColumnSpec(id='supported', label='Supported', description='Support status on the current platform.', max_width='auto'), None), (ColumnSpec(id='cli', label='CLI', description="Location of the manager's binary on the system.", max_width='auto'), None), (ColumnSpec(id='executable', label='Executable', description='Whether the binary is executable.', max_width=None), None), (ColumnSpec(id='version', label='Version', description="Manager's self-reported version, and the unsatisfied requirement when stale.", max_width=None), SortableField.VERSION))¶
Columns of the
mpm managerstable.supportedandcliare the two whose content no manager bounds: the first enumerates every platform a manager runs on when they do not collapse to a group name, the second holds a filesystem path. Both takeAUTO_WIDTHso they share whatever the fixed columns leave on the terminal and wrap inside their own cell, instead of stretching the table past the edge and mangling every border.
- meta_package_manager.tables.MANAGERS_DETECTED_COLUMNS: tuple[str, ...] = ('manager_id', 'manager_name', 'cli', 'version')¶
Columns kept by the default, detected-only view of the
mpm managerstable.A detected manager is by definition supported on this platform, found and executable, so
supportedandexecutablerender the same ✓ on every row and carry no information. Both come back in the wider views, where an unsupported platform or a missing binary makes them vary again. Only the default selection narrows:--columnsstill addresses every column ofMANAGERS_COLUMNS.
- meta_package_manager.tables.INSTALLED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.', max_width='auto'), SortableField.VERSION))¶
Columns of the
mpm installedtable.Important
package_idis deliberately the one column of every package table left uncapped, and the rule holds wherever these specs are reused. It is the value the user copies back into anmpm install,mpm removeormpm upgradeinvocation, and a cell wrapped over two lines cannot be selected in one go. So it never wraps, and the shrinking falls on its neighbors instead: the name is free prose, and a version is read rather than retyped.The trade is explicit. A package ID wider than the terminal on its own still pushes the table past the edge, because the alternative is handing the user a broken identifier. Both halves of the trade are real here:
vim-packnames its plugins by GitHub URL (50 characters), and Homebrew Cask reports versions like1.26832.0,056ee2be623b207f6a4d24dfb1b2fb5a82db0ecf.
- meta_package_manager.tables.OUTDATED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.', max_width='auto'), SortableField.VERSION), (ColumnSpec(id='latest_version', label='Latest version', description='Version available for upgrade.', max_width='auto'), None))¶
Columns of the
mpm outdatedtable.Inherits the width policy documented on
INSTALLED_COLUMNS: the second version column wraps like the first,package_idstill does not.
- meta_package_manager.tables.SEARCH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the match.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='latest_version', label='Latest version', description='Latest version available.', max_width='auto'), 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.', max_width='auto'), 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.It is the column holding the longest free prose, of a length no manager bounds: a single verbose match used to stretch the table far past the terminal and wrap every row at the edge, mangling the borders.
AUTO_WIDTHcaps it at whatever the other columns leave on the terminal, so the description wraps inside its own cell. The name and version columns share that treatment, andpackage_idis exempt from it, per the width policy documented onINSTALLED_COLUMNS.
- 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.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='priority', label='Priority', description="Rank of the match in the manager's search path.", max_width=None), None), (ColumnSpec(id='cli_path', label='CLI path', description='Location of the matched binary.', max_width=None), None), (ColumnSpec(id='symlink', label='Symlink destination', description='Resolved target when the match is a symlink.', max_width=None), 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()reads the selection (with the--table-formatone) from the shared contextmetaand resolves it per table. A sort field whose column is projected out is simply skipped, and a table carrying none of the selected fields keeps its original row order.Note
The width limits are forwarded by hand, from the same specs the projection just resolved. click-extra reads them off
ColumnSpecheaders on its own, but mpm’s headers are(label, sortable field)pairs instead: a spec’s ID addresses the column for--columnswhile the field it sorts on may differ (installed_versionsorts onversion) or be absent altogether, which a bare spec cannot express.- 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, prefix_fg='bright_black', old_fg='red', new_fg='green')[source]¶
Color the common prefix gray, the old suffix red, the new suffix green.
The split point snaps to the nearest separator boundary so the full diverging token and its preceding separator are highlighted. For
2.1.1774638290vs2.1.1774896198, the common part is2.1and the diff includes.1774638290/.1774896198.prefix_fg,old_fgandnew_fgoverride the common-prefix, old-suffix and new-suffix colors, in any form accepted byclick_extra.style()(a named ANSI color or an xterm-256 palette index). Renderers whose consumer maps the named defaults poorly, like the bar plugin on a light translucent menu, pass their own.