meta_package_manager.specifier module¶

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

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

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

This has been chosen as a separator because it is shared by popular package managers (like npm) and pURLs.

..code-block:

package_id@version
meta_package_manager.specifier.PURL_MAP: dict[str, set[str] | None] = {'alpine': None, 'alpm': {'aura', 'pacaur', 'pacman', 'pamac', 'paru', 'shelly', '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', 'aptitude'}, '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': {'pear'}, '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_MAP mapping.

Todo

Reuse the mapping that is proposed upstream to the package-url Python project.

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

Bases: object

Lightweight representation of a package specification.

Contains all parsed metadata to be used as constraints.

raw_spec: str¶

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

package_id: str¶

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

manager_id: str | None = None¶
version: str | None = None¶

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

classmethod parse_purl(spec_str)[source]¶

Resolve a pURL into its corresponding manager candidates.

Yields Specifier objects or returns None.

Todo

Reassemble the package ID from purl.namespace where the type calls for it, instead of reading purl.name alone. Two claimed types carry a namespace today and both resolve wrongly: pkg:composer/monolog/monolog yields monolog where Composer needs monolog/monolog, and pkg:npm/@angular/animation yields animation, which is a real and unrelated package, so the install succeeds on the wrong thing rather than failing. Claiming golang for Go waits on the same fix, the module path being exactly the part that gets dropped.

The join cannot be blanket, which is what makes this more than a one-liner: a pkg:deb/debian/curl namespace names the distribution rather than half the package name, so joining it would ask apt for debian/curl. The rule has to be per-type, and tests/test_specifier.py covers only namespace-free pURLs today, which is why nothing caught it.

Return type:

tuple[Specifier, ...] | None

classmethod from_string(spec_str)[source]¶

Parse a string into a package specifier.

Supports various formats: - plain package_id - simple package ID with version: package_id@version - package with multiple version separators: @eslint/json@0.9.0 - pURL: pkg:npm/left-pad@3.7

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

Returns a tuple of Specifier.

Return type:

tuple[Specifier, ...]

property parsed_version: TokenizedString[source]¶
exception meta_package_manager.specifier.EmptyReduction[source]¶

Bases: Exception

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

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

Bases: object

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

spec_pool: set[Specifier]¶
manager_priority: Sequence[str] = ()¶
populate_from_strings(spec_strings)[source]¶

Populate the solver with package specifiers parsed from provided strings.

top_priority_manager(keep_managers=None)[source]¶

Returns the top priority manager configured on the solver.

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

Return type:

str | None

reduce_specs(specs)[source]¶

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

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

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

Filtering steps:

  1. We remove all constraints tied to all by the top priority manager if provided.

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

  3. We discard constraints not tied to a version.

  4. We only keep constraints tied to the highest version.

If we ends up with more than one set of constraints after all this filtering, an error is raised to invite the developer to troubleshoot the situation and refine this process.

Return type:

Specifier

resolve_package_specs()[source]¶

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

Each package ID yields one reduced spec per distinct target manager. A package therefore produces several specs when the user explicitly names several managers for it (pkg:uv/rich pkg:brew/rich → one spec each). In contrast, a single alias pURL that expands to several managers (pkg:rpm/ping → dnf/yum/zypper) is a set of alternatives, reduced to the top-priority one.

Return type:

Iterator[tuple[str, Specifier]]

resolve_specs_group_by_managers()[source]¶

Resolves package specs, and returns them grouped by managers.

Return type:

dict[str | None, set[Specifier]]