meta_package_manager package#

Expose package-wide elements.

Subpackages#

Submodules#

meta_package_manager.bar_plugin module#

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

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

meta_package_manager.bar_plugin.PYTHON_MIN_VERSION = (3, 8, 0)#

Minimal requirement is aligned to mpm.

meta_package_manager.bar_plugin.MPM_MIN_VERSION = (5, 0, 0)#

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

class meta_package_manager.bar_plugin.Venv(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)#

Bases: Enum

Type of virtualenv we are capable of detecting.

PIPENV = 1#
POETRY = 2#
VIRTUALENV = 3#
class meta_package_manager.bar_plugin.MPMPlugin[source]#

Bases: object

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

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

The output must supports both Xbar dialect and SwiftBar dialect.

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

Utility to get environment variables.

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

Return type:

str | None

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

Utility to normalize boolean environment variables.

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

Return type:

bool

static normalize_params(font_string, valid_ids=None)[source]#
Return type:

str

static v_to_str(version_tuple)[source]#

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

Return type:

str

property table_rendering: bool#

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

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

property default_font: str#

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

property monospace_font: str#

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

property error_font: str#

Error font is based on monospace font.

property is_swiftbar: bool#

SwiftBar is kind enough to tell us about its presence.

property all_pythons: list[str]#

Search for any Python on the system.

Returns a generator of normalized and deduplicated Path to Python binaries.

Filters out old Python interpreters.

We first try to locate Python by respecting the environment variables as-is, i.e. as defined by the user. Then we return the Python interpreter used to execute this script.

TODO: try to tweak the env vars to look for homebrew location etc?

static search_venv(folder)[source]#

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

Returns the type of the detected venv and CLI arguments that can be used to run a command from the virtualenv context.

Returns (None, None) if the folder is not a venv.

Return type:

tuple[Venv, tuple[str, ...]] | None

search_mpm()[source]#

Iterare over possible CLI commands to execute mpm.

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

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

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

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

Return type:

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

check_mpm(mpm_cli_args)[source]#

Test-run mpm execution and extract its version.

Return type:

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

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

Rank the mpm candidates we found on the system.

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

On tie, the order from search_mpm is respected.

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

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

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

Skip printing of the line if label is empty.

Return type:

None

static print_error_header()[source]#

Generic header for blocking error.

Return type:

None

print_error(message, submenu='')[source]#

Print a formatted error message line by line.

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

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

Return type:

None

print_menu()[source]#

Print the main menu.

Return type:

None

meta_package_manager.base module#

class meta_package_manager.base.Operations(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)#

Bases: Enum

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

Each operation has its own CLI subcommand.

installed = 1#
outdated = 2#
search = 3#
install = 4#
upgrade = 5#
upgrade_all = 6#
remove = 7#
sync = 8#
cleanup = 9#
exception meta_package_manager.base.CLIError(code, output, error)[source]#

Bases: Exception

An error occurred when running package manager CLI.

The exception internally keeps the result of CLI execution.

class meta_package_manager.base.Package(id, name=None, description=None, installed_version=None, latest_version=None)[source]#

Bases: object

Lightweight representation of a package and its metadata.

id: str#

ID is required and is the primary key used by the manager.

name: str | None = None#
description: str | None = None#
installed_version: TokenizedString | str | None = None#
latest_version: TokenizedString | str | None = None#

Installed and latest versions are optional: they’re not always provided by the package manager.

installed_version and latest_version are allowed to temporarily be strings between __init__ and __post_init__. Once they reach the later, they’re parsed and normalized into either TokenizedString or None. They can’t be strings beyond that point, i.e. after the Package instance has been fully instantiated. We don’t know how to declare this transient state with type hints, so we’re just going to allow string type.

meta_package_manager.base.packages_asdict(packages, keep_fields)[source]#

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

class meta_package_manager.base.MetaPackageManager(name, bases, dct)[source]#

Bases: type

Custom metaclass used as a class factory for package managers.

Sets some class defaults, but only if they’re not redefined in the final manager class.

Also normalize list of platform, by ungrouping groups, deduplicate entries and freeze them into a set of unique platforms.

meta_package_manager.base.highlight_cli_name(path, match_names)[source]#

Highlight the binary name in the provided path.

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

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

Return type:

str | None

class meta_package_manager.base.PackageManager[source]#

Bases: object

Base class from which all package manager definitions inherits.

Initialize cli_errors list.

deprecated: bool = False#

A manager marked as deprecated will be hidden from all package selection by default.

You can still use it but need to explicitly call for it on the command line.

Implementation of a deprecated manager will be kept within mpm source code, but some of its features or total implementation are allowed to be scraped in the face of maintenance pain and adversity.

Integration tests and unittests for deprecated managers can be removed. We do not care if a deprecated manager is not 100% reliable. A flakky deprecated manager should not block a release due to flakky tests.

deprecation_url: str | None = None#

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

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.

platforms: Union[Group, Platform, Iterable[Group | Platform], frozenset[Platform]] = frozenset({})#

List of platforms supported by the manager.

Allows for a mishmash of platforms and groups. Will be normalized into a frozen set of Platform instances at instantiation.

requirement: str | None = None#

Minimal required version.

Should be a string parseable by meta_package_manager.version.parse_version.

Defaults to None, which deactivate version check entirely.

cli_names: tuple[str, ...] = ('packagemanager',)#

List of CLI names the package manager is known as.

The supported CLI names are ordered by priority. This is used for example to help out the search of the right binary in the case of the python3/python2 transition.

Is derived by default from the manager’s ID.

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.

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.base.PackageManager.cli_path() works well on all platforms.

extra_env: Optional[Mapping[str, Optional[str]]] = None#

Additional environment variables to add to the current context.

Automatically applied on each meta_package_manager.base.PackageManager.run_cli() calls.

pre_cmds: tuple[str, ...] = ()#

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

Automatically added to each meta_package_manager.base.PackageManager.run_cli() call.

Used to prepend sudo or other system utilities.

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

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

Automatically added to each meta_package_manager.base.PackageManager.run_cli() call.

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

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

List of options to get the version from the package manager CLI.

version_regex: str = '(?P<version>\\S+)'#

Regular expression used to extract the version number from the result of the CLI run with the options above. It doesn’t matter if the regex returns unsanitized and crappy string. The meta_package_manager.base.PackageManager.version() method will clean and normalized it.

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

stop_on_error: bool = False#

Tell the manager to either raise or continue on errors.

ignore_auto_updates: bool = True#

Some managers can report or ignore packages which have their own auto-update mechanism.

dry_run: bool = False#

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

timeout: int | None = None#

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

package#

The dataclass to use to produce Package objects from the manager.

alias of Package

cli_errors: list[CLIError]#

Accumulate all CLI errors encountered by the package manager.

classmethod implements(op)[source]#

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

Return type:

bool

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.

Return type:

Generator[Path, None, None]

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.

which(cli_name)[source]#

Emulates the which command.

Based on the search_all_cli() method.

Return type:

Path | None

property cli_path: Path | None#

Fully qualified path to the canonical package manager binary.

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

Executability of the CLI will be separately assessed later by the meta_package_manager.base.PackageManager.executable() method below.

property version: TokenizedString | None#

Invoke the manager and extract its own reported version string.

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

property supported: bool#

Is the package manager supported on that platform?

property executable: bool#

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

property fresh: bool#

Does the package manager match the version requirement?

property available: bool#

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

Returns True only if the main CLI:

  1. is supported on the current platform,

  2. was found on the system,

  3. is executable, and

  4. match the version requirement.

run(*args, extra_env=None)[source]#

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

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

Return type:

str

Running commands with that method takes care of:
build_cli(*args, auto_pre_cmds=True, auto_pre_args=True, auto_post_args=True, override_pre_cmds=None, override_cli_path=None, override_pre_args=None, override_post_args=None, sudo=False)[source]#

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

Returns a tuple of strings.

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

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

tuple[str, ...]

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

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

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

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

Each global set of elements can be locally overridden with:

  • override_pre_cmds=tuple()

  • override_cli_path=str

  • override_pre_args=tuple()

  • override_post_args=tuple()

On linux, the command can be run with sudo if the parameter of the same name is set to True. In which case the override_pre_cmds parameter is not allowed to be set and the auto_pre_cmds parameter is forced to False.

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, sudo=False)[source]#

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

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

All parameters are the same as meta_package_manager.base.PackageManager.build_cli(), plus: :rtype: str

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

  • override_extra_env=dict() to locally overrides the later

  • force_exec ignores the mpm --dry-run and mpm --stop-on-error options to force the execution and completion of the command.

property installed: Iterator[Package]#

List packages currently installed on the system.

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

property outdated: Iterator[Package]#

List installed packages with available upgrades.

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

classmethod query_parts(query)[source]#

Returns a set of all contiguous alphanumeric string segments.

Contrary to meta_package_manager.version.TokenizedString, do no splits on colated number/alphabetic junctions.

Return type:

set[str]

search(query, extended, exact)[source]#

Search packages available for install.

There is no need for this method to be perfect and sensitive to extended and exact parameters. If the package manager is not supporting these kind of options out of the box, just returns the closest subset of matching package you can come up with. Finer refiltering will happens in the meta_package_manager.base.PackageManager.refiltered_search() method below.

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

Return type:

Iterator[Package]

Returns search results with extra manual refiltering to refine gross matchings.

Some package managers returns unbounded results, and/or don’t support fine search criterions. In which case we use this method to manually refilters meta_package_manager.base.PackageManager.search() results to either exclude non-extended or non-exact matches.

Returns a generator producing the same data as the meta_package_manager.base.PackageManager.search() method above. :rtype: Iterator[Package]

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.base.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 version to be provided.

Return type:

str

upgrade_all_cli()[source]#

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

Return type:

tuple[str, ...]

upgrade_one_cli(package_id, version=None)[source]#

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

Allows a specific version to be provided.

Return type:

tuple[str, ...]

upgrade(package_id=None, version=None)[source]#

Perform an upgrade of either all or one package.

Executes the CLI provided by either meta_package_manager.base.PackageManager.upgrade_all_cli() or meta_package_manager.base.PackageManager.upgrade_one_cli().

If the manager doesn’t provides a full upgrade one-liner (i.e. if meta_package_manager.base.PackageManager.upgrade_all_cli() raises NotImplementedError), then the list of all outdated packages will be fetched (via meta_package_manager.base.PackageManager.outdated()) and each package will be updated one by one by calling meta_package_manager.base.PackageManager.upgrade_one_cli().

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

Return type:

str

remove(package_id)[source]#

Remove one package and one only.

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

Return type:

str

sync()[source]#

Refresh package metadata from remote repositories.

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

Return type:

None

cleanup()[source]#

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

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

Return type:

None

meta_package_manager.capabilities module#

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

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

meta_package_manager.capabilities.version_not_implemented(func)[source]#

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

Return type:

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

meta_package_manager.cli module#

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

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

See the corresponding implementation rationale in issue #10.

meta_package_manager.cli.add_manager_to_selection(ctx, param, selected)[source]#

Store singular manager flag selection in the context. :rtype: None

Important

Because the parameter’s name is transformed into a Python identifier on instantiation, we have to reverse the process to get our value.

Example: --apt-mint => apt_mint => apt-mint

meta_package_manager.cli.single_manager_selectors()[source]#

Dynamiccaly creates a dedicated flag selector alias for each manager.

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

Print the location of the Xbar/SwiftBar plugin.

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

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

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

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

meta_package_manager.inventory module#

Introspection utilities to produce comparison matrixes between managers.

meta_package_manager.inventory.operation_matrix()[source]#

Inspect manager and print a matrix of their current implementation.

Return type:

str

meta_package_manager.inventory.update_readme()[source]#

Update readme.md at the root of the project with the implementation table for each manager we support.

Return type:

None

meta_package_manager.labels module#

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

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

Generate labels.

Return type:

dict[str, str]

meta_package_manager.labels.MANAGER_LABEL_GROUPS: Dict[str, FrozenSet[str]] = {'dnf-based': frozenset({'dnf', 'yum'}), 'dpkg-based': frozenset({'apt', 'apt-mint', 'opkg'}), 'npm-based': frozenset({'npm', 'yarn'}), 'pacman-based': frozenset({'pacaur', 'pacman', 'paru', 'yay'}), 'pip-based': frozenset({'pip', 'pipx'})}#

Managers sharing some origin or implementation are grouped together under the same label.

meta_package_manager.labels.all_manager_label_ids = frozenset({'apm', 'apt', 'apt-mint', 'brew', 'cargo', 'cask', 'choco', 'composer', 'dnf', 'emerge', 'flatpak', 'gem', 'mas', 'mpm', 'npm', 'opkg', 'pacaur', 'pacman', 'paru', 'pip', 'pipx', 'pkg', 'scoop', 'snap', 'steamcmd', 'vscode', 'yarn', 'yay', 'yum', 'zypper'})#

Adds mpm as its own manager alongside all those implemented.

meta_package_manager.labels.MANAGER_LABELS = {'apm': '📦 manager: apm', 'apt': '📦 manager: dpkg-based', 'apt-mint': '📦 manager: dpkg-based', 'brew': '📦 manager: brew', 'cargo': '📦 manager: cargo', 'cask': '📦 manager: cask', 'choco': '📦 manager: choco', 'composer': '📦 manager: composer', 'dnf': '📦 manager: dnf-based', 'emerge': '📦 manager: emerge', 'flatpak': '📦 manager: flatpak', 'gem': '📦 manager: gem', 'mas': '📦 manager: mas', 'mpm': '📦 manager: mpm', 'npm': '📦 manager: npm-based', 'opkg': '📦 manager: dpkg-based', 'pacaur': '📦 manager: pacman-based', 'pacman': '📦 manager: pacman-based', 'paru': '📦 manager: pacman-based', 'pip': '📦 manager: pip-based', 'pipx': '📦 manager: pip-based', 'pkg': '📦 manager: pkg', 'scoop': '📦 manager: scoop', 'snap': '📦 manager: snap', 'steamcmd': '📦 manager: steamcmd', 'vscode': '📦 manager: vscode', 'yarn': '📦 manager: npm-based', 'yay': '📦 manager: pacman-based', 'yum': '📦 manager: dnf-based', 'zypper': '📦 manager: zypper'}#

Maps all manager IDs to their labels.

meta_package_manager.labels.PLATFORM_LABEL_GROUPS: Dict[str, FrozenSet[str]] = {'BSD': frozenset({'FreeBSD', 'NetBSD', 'OpenBSD', 'SunOS'}), 'Linux': frozenset({'Linux', 'Windows Subsystem for Linux v2'}), 'Unix': frozenset({'AIX', 'Cygwin', 'GNU/Hurd', 'Solaris', 'Windows Subsystem for Linux v1'}), 'Windows': frozenset({'Windows'}), 'macOS': frozenset({'macOS'})}#

Similar platforms are grouped together under the same label.

meta_package_manager.labels.PLATFORM_LABELS = {'AIX': '🖥 platform: Unix', 'Cygwin': '🖥 platform: Unix', 'FreeBSD': '🖥 platform: BSD', 'GNU/Hurd': '🖥 platform: Unix', 'Linux': '🖥 platform: Linux', 'NetBSD': '🖥 platform: BSD', 'OpenBSD': '🖥 platform: BSD', 'Solaris': '🖥 platform: Unix', 'SunOS': '🖥 platform: BSD', 'Windows': '🖥 platform: Windows', 'Windows Subsystem for Linux v1': '🖥 platform: Unix', 'Windows Subsystem for Linux v2': '🖥 platform: Linux', 'macOS': '🖥 platform: macOS'}#

Maps all platform names to their labels.

meta_package_manager.labels.LABELS: list[tuple[str, str, str]] = [('📦 manager: apm', '#bfdadc', 'apm'), ('📦 manager: brew', '#bfdadc', 'brew'), ('📦 manager: cargo', '#bfdadc', 'cargo'), ('📦 manager: cask', '#bfdadc', 'cask'), ('📦 manager: choco', '#bfdadc', 'choco'), ('📦 manager: composer', '#bfdadc', 'composer'), ('📦 manager: dnf-based', '#bfdadc', 'dnf, yum'), ('📦 manager: dpkg-based', '#bfdadc', 'apt, apt-mint, opkg'), ('📦 manager: emerge', '#bfdadc', 'emerge'), ('📦 manager: flatpak', '#bfdadc', 'flatpak'), ('📦 manager: gem', '#bfdadc', 'gem'), ('📦 manager: mas', '#bfdadc', 'mas'), ('📦 manager: mpm', '#bfdadc', 'mpm'), ('📦 manager: npm-based', '#bfdadc', 'npm, yarn'), ('📦 manager: pacman-based', '#bfdadc', 'pacaur, pacman, paru, yay'), ('📦 manager: pip-based', '#bfdadc', 'pip, pipx'), ('📦 manager: pkg', '#bfdadc', 'pkg'), ('📦 manager: scoop', '#bfdadc', 'scoop'), ('📦 manager: snap', '#bfdadc', 'snap'), ('📦 manager: steamcmd', '#bfdadc', 'steamcmd'), ('📦 manager: vscode', '#bfdadc', 'vscode'), ('📦 manager: zypper', '#bfdadc', 'zypper'), ('🔌 bar-plugin', '#fef2c0', 'Xbar/SwiftBar plugin code, documentation and features'), ('🖥 platform: BSD', '#bfd4f2', 'FreeBSD, NetBSD, OpenBSD, SunOS'), ('🖥 platform: Linux', '#bfd4f2', 'Linux, Windows Subsystem for Linux v2'), ('🖥 platform: macOS', '#bfd4f2', 'macOS'), ('🖥 platform: Unix', '#bfd4f2', 'AIX, Cygwin, GNU/Hurd, Solaris, Windows Subsystem for Linux v1'), ('🖥 platform: Windows', '#bfd4f2', 'Windows')]#

Global registry of all labels used in the project.

Structure:

("label_name", "color", "optional_description")

meta_package_manager.output module#

Helpers and utilities to render and print content.

Todo

Some of these are good candidates for upstream contribution to click.extra.

meta_package_manager.output.SORTABLE_FIELDS = {'manager_id', 'manager_name', 'package_id', 'package_name', 'version'}#

List of fields IDs allowed to be sorted.

meta_package_manager.output.colored_diff(a, b, style_common=None, style_a=None, style_b=None)[source]#

Highlight the most common left part between a and b strings and their trailing differences.

Always returns 2 strings.

meta_package_manager.output.print_json(data)[source]#

Pretty-print Python data to JSON and output results to <stdout>.

Serialize pathlib.Path and meta_package_manager.version.TokenizedString objects.

meta_package_manager.output.print_table(header_defs, rows, sort_key=None)[source]#

Print a table.

header_defs parameter is an ordered list of tuple whose first item is the column’s label and the second the column’s ID. Example:

[
    ("Column 1", "column1"),
    ("User's name", "name"),
    ("Package manager", "manager_id"),
    ...,
]

Rows can be sorted by providing the column’s ID to sort_key parameter. By default, None means the table will be sorted in the order of columns provided by header_defs.

Return type:

None

meta_package_manager.output.print_stats(manager_stats)[source]#

Prints statistics to <stderr>: total packages and a break down by package manager.

Prints something like:

10 packages total (brew: 2, pip: 2, gem: 2, vscode: 2, npm: 2, composer: 0).
Return type:

None

class meta_package_manager.output.BarPluginRenderer[source]#

Bases: MPMPlugin

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

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

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

property submenu_layout: bool#

Group packages into manager sub-menus.

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

Value is sourced from the VAR_SUBMENU_LAYOUT environment variable.

property dark_mode: bool#

Detect dark mode by inspecting environment variables.

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

  • OS_APPEARANCE for SwiftBar

  • XBARDarkMode for XBar

static render_cli(cmd_args)[source]#

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

I.e. a string with this schema:

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

str

print_cli_item(*args)[source]#

Print two CLI entries: :rtype: None

  • one that is silent

  • a second one that is the exact copy of the above but forces the execution by the way of a visible terminal

print_upgrade_all_item(manager, submenu='')[source]#

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

Return type:

None

plain_table_format = TableFormat(lineabove=None, linebelowheader=None, linebetweenrows=None, linebelow=None, headerrow=DataRow(begin='', sep=' ', end=''), datarow=DataRow(begin='', sep=' ', end=''), padding=0, with_header_hide=None)#

Simple rendering format with single-space separated columns used in the function below.

static render_table(table_data)[source]#

Renders a table data with pre-configured alignment centered around the third column.

Returns a list of strings, one item per line.

>>> table_data = [
...     ("xmlrpc", "0.3.1", "→", "0.4"),
...     ("blockblock", "5.33,VHSDGataYCcV8xqv5TSZA", "→", "5.39"),
...     ("sed", "2", "→", "2021.0328"),
... ]
>>> print(render_table(table_data))
xmlrpc                          0.3.1 → 0.4
blockblock 5.33,VHSDGataYCcV8xqv5TSZA → 5.39
sed                                 2 → 2021.0328

..todo:

Use upcoming ``tabulate.SEPARATING_LINE`` to produce the whole bar plugin
table in one go and have all version numbers from all managers aligned. See:
https://github.com/astanin/python-tabulate/commit/dab256d1f64da97720c1459478a3cc0a4ea7a91e
Return type:

Any | list

render(outdated_data)[source]#

Wraps the meta_package_manager.output.BarPluginRenderer._render() function above to capture all print statements.

Return type:

str

add_upgrade_cli(outdated_data)[source]#

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

print(outdated_data)[source]#

Print the final plugin rendering to <stdout>.

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

Return type:

None

meta_package_manager.platforms module#

mpm’s platform introspection utilities.

meta_package_manager.platforms.WINDOWS: Group = Group(id='all_windows', name='Windows', platform_ids=frozenset({'windows'}))#

Define comprehensive platform groups to minimize the operation matrix verbosity.

meta_package_manager.platforms.PLATFORM_GROUPS: Tuple[Group, ...] = (Group(id='bsd_without_macos', name='BSD', platform_ids=frozenset({'freebsd', 'netbsd', 'sunos', 'openbsd'})), Group(id='linux', name='Linux', platform_ids=frozenset({'wsl2', 'linux'})), Group(id='macos', name='macOS', platform_ids=frozenset({'macos'})), Group(id='unix', name='Unix', platform_ids=frozenset({'aix', 'cygwin', 'solaris', 'hurd', 'wsl1'})), Group(id='all_windows', name='Windows', platform_ids=frozenset({'windows'})))#

Sorted list of platform groups that will have their own dedicated column in the matrix.

meta_package_manager.platforms.encoding_args = {}#

Forcing encoding is required on Windows.

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.apm.APM'>, <class 'meta_package_manager.managers.apt.APT'>, <class 'meta_package_manager.managers.apt.APT_Mint'>, <class 'meta_package_manager.managers.homebrew.Brew'>, <class 'meta_package_manager.managers.cargo.Cargo'>, <class 'meta_package_manager.managers.homebrew.Cask'>, <class 'meta_package_manager.managers.chocolatey.Choco'>, <class 'meta_package_manager.managers.composer.Composer'>, <class 'meta_package_manager.managers.dnf.DNF'>, <class 'meta_package_manager.managers.emerge.Emerge'>, <class 'meta_package_manager.managers.flatpak.Flatpak'>, <class 'meta_package_manager.managers.gem.Gem'>, <class 'meta_package_manager.managers.mas.MAS'>, <class 'meta_package_manager.managers.npm.NPM'>, <class 'meta_package_manager.managers.opkg.OPKG'>, <class 'meta_package_manager.managers.pacman.Pacaur'>, <class 'meta_package_manager.managers.pacman.Pacman'>, <class 'meta_package_manager.managers.pacman.Paru'>, <class 'meta_package_manager.managers.pip.Pip'>, <class 'meta_package_manager.managers.pipx.Pipx'>, <class 'meta_package_manager.managers.pkg.PKG'>, <class 'meta_package_manager.managers.scoop.Scoop'>, <class 'meta_package_manager.managers.snap.Snap'>, <class 'meta_package_manager.managers.steamcmd.SteamCMD'>, <class 'meta_package_manager.managers.vscode.VSCode'>, <class 'meta_package_manager.managers.yarn.Yarn'>, <class 'meta_package_manager.managers.pacman.Yay'>, <class 'meta_package_manager.managers.dnf.YUM'>, <class 'meta_package_manager.managers.zypper.Zypper'>)#

The list of all classes implementing the specific package managers.

Is considered valid package manager, definitions classes which:

  1. are located in the :py:prop:`meta_package_manager.pool.ManagerPool.manager_subfolder`

    subfolder, and

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

  3. are not :py:prop:`meta_package_manager.base.PackageManager.virtual` (i.e. have a

    non-null :py:prop:`meta_package_manager.base.PackageManager.cli_names` property).

These properties are checked and enforced in unittests.

class meta_package_manager.pool.ManagerPool[source]#

Bases: object

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

ALLOWED_EXTRA_OPTION: Final = frozenset({'dry_run', 'ignore_auto_updates', 'stop_on_error', 'timeout'})#

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

property register: dict[str, PackageManager]#

Instantiate all supported package managers.

get(key)#
values()[source]#
items()[source]#
property all_manager_ids: tuple[str, ...]#

All recognized manager IDs.

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

property maintained_manager_ids: tuple[str, ...]#

All manager IDs which are not deprecated.

property default_manager_ids: tuple[str, ...]#

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

Must keep the same order defined by :py:prop:`meta_package_manager.pool.ManagerPool.all_manager_ids`.

property unsupported_manager_ids: tuple[str, ...]#

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

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

select_managers(keep=None, drop=None, keep_deprecated=False, keep_unsupported=False, drop_inactive=True, implements_operation=None, **extra_options)[source]#

Utility method to extract a subset of the manager pool based on selection list (keep parameter) and exclusion list (drop parameter) criterion.

By default, only the managers supported by the current platform are selected. Unless keep_unsupported is set to True, in which case all managers implemented by mpm are selected, regardless of their supported platform.

Deprecated managers are also excluded by default, unless keep_deprecated is True.

drop_inactive filters out managers that where not found on the system.

implements_operation filters out managers which do not implements the provided operation.

Finally, extra_options parameters are fed to manager objects to set some additional options.

Returns a generator producing a manager instance one after the other.

Return type:

Iterator[PackageManager]

meta_package_manager.specifier module#

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

meta_package_manager.specifier.VERSION_SEP: Final = '@'#

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

This has been chosen as a separator because it is shared by popular package managers and purls.

..code-block:

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

Bases: object

Lightweight representation of a package specification.

Contains all parsed metadata to be used as constraints.

raw_spec: str#

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

package_id: str#

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

manager_id: str | None = None#
version: str | None = None#

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

classmethod parse_purl(spec_str)[source]#

Parse a purl string.

Yields Specifier objects or returns None.

Return type:

tuple[Specifier, ...] | None

classmethod from_string(spec_str)[source]#

Parse a string into a package specifier.

Supports various formats: - plain package_id - simple package ID with version: package_id@version - purl: pkg:npm/left-pad@3.7

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

Returns a generator of Specifier.

Return type:

Iterator[Specifier]

property parsed_version: TokenizedString#
property is_blank: bool#

Is considered blank a Specifier without any constraint on manager_id or version.

exception meta_package_manager.specifier.EmptyReduction[source]#

Bases: Exception

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

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

Bases: object

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

spec_pool: set[Specifier] = {}#
manager_priority: Sequence[str] = []#
populate_from_strings(spec_strings)[source]#

Populate the solver with package specifiers parsed from provided strings.

top_priority_manager(keep_managers=None)[source]#

Returns the top priority manager configured on the solver.

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

Return type:

str | None

reduce_specs(specs)[source]#

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

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

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

Return type:

Specifier

Filtering steps: 1. We remove all constraints tied to all by the top priority manager if

provided.

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

    manager.

  2. We discard constraints not tied to a version.

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

resolve_package_specs()[source]#

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

Return each package ID with its single, reduced spec, or None if it ha no constraints.

Return type:

Iterator[tuple[str, Specifier]]

resolve_specs_group_by_managers()[source]#

Resolves package specs, and returns them grouped by managers.

Return type:

dict[str | None, set[Specifier]]

meta_package_manager.version module#

Helpers and utilities to parse and compare version numbers.

class meta_package_manager.version.Token(value)[source]#

Bases: object

A token is a normalized word, persisting its lossless integer variant.

Support natural comparison with str and int types.

We mainly use them here to compare versions and package IDs.

Instantiates a Token from an alphanumeric string or a non-negative integer.

static str_to_int(value)[source]#

Convert a str or an int to a (string, integer) couple.

Returns together the original string and its integer representation if conversion is successful and lossless. Else, returns the original value and None.

Return type:

tuple[str, int | None]

string: str#
integer: int | None = None#
property isint: bool#

Does the Token got an equivalent pure integer representation?

class meta_package_manager.version.TokenizedString(value, separator='-')[source]#

Bases: object

Tokenize a string for user-friendly sorting.

Essentially a wrapper around a list of Token instances.

Parse and tokenize the provided raw value.

string: str#
tokens: tuple[Token, ...] = ()#
separator: str = ''#
pretty_print()[source]#
Return type:

str

classmethod tokenize(string)[source]#

Tokenize a string: ignore case and split at each non-alphanumeric characters.

Returns a list of Token instances.

Return type:

Iterator[Token]

meta_package_manager.version.parse_version = functools.partial(<class 'meta_package_manager.version.TokenizedString'>, separator='.')#

Utility method tweaking TokenizedString for dot-based serialization.