diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 2d3dd4ed215..a2990470fc9 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -251,8 +251,9 @@ take a list of dictionaries, with each of them reporting the following fields: - ``name`` (string), unique identifier for this package manager. Usually, the executable name. - ``commands`` (list of dictionaries), the commands to run to install the mapped package(s) and check whether they are already installed. -- ``version_operators``: a mapping of PEP 440 operator names to the relevant - syntax for this package manager. +- ``specifier_syntax``: instructions on how to map a subset of PEP 440 specifiers to + the target package manager. Three levels of support are offered: name-only, exact-version-only, + and version-range compatibility (with per-operator translations). Each mapping MUST have a canonical URL for online retrieval. These mappings MAY also be packaged for offline distribution in each platform. The authors @@ -552,29 +553,74 @@ Each entry in this list is defined as a dictionary with these fields: - Short identifier for this package manager (usually the command name) - True * - ``commands`` - - ``dict[Literal['install', 'query'], dict[Literal['command', 'requires_elevation'], list[str] | bool]]`` - - Commands used to install or query the given package(s). - Only two keys are allowed: ``install`` and ``query``. Their value - is a dictionary with a required key ``command`` that takes a list of - strings (as expected by ``subprocess.run``), and an optional - ``requires_elevation`` boolean (``False`` by default) to indicate whether - the command must run with elevated permissions. - Exactly one of the ``command`` items MUST include a ``{}`` placeholder, which will be - replaced by the mapped package identifier(s). The ``install`` command MUST support - the placeholder being replaced by multiple identifiers, ``query`` will only receive a single - identifier per command. + - ``dict[Literal['install', 'query'], dict[Literal['command', 'requires_elevation', 'multiple_specifiers'], list[str] | bool | Literal['always', 'name-only', 'never']]]`` + - Commands used to install or query the given package(s). Only two keys + are allowed: ``install`` and ``query``. Their value is a dictionary + with: + + - a required key ``command`` that takes a list of strings + (as expected by ``subprocess.run``). + + - an optional ``requires_elevation`` boolean (``False`` by default) + to indicate whether the command must run with elevated permissions + (e.g. administrator on Windows, superuser on Linux and macOS). + + - an enum ``multiple_specifiers`` that determines whether the command + accepts multiple package specifiers at the same time, accepting one of: + + - ``always``, default in ``install``. + + - ``name-only``, the command only accepts multiple specifiers if they do + not contain version constraints. + + - ``never``, default in ``query``. + + Exactly one of the ``command`` items MUST include a ``{}`` placeholder, + which will be replaced by the mapped package identifier(s). The + ``install`` command SHOULD support the placeholder being replaced by + multiple identifiers, ``query`` MUST only receive a single identifier + per command. + - True + * - ``specifier_syntax`` + - ``dict[Literal['name_only', 'exact_version', 'version_ranges'], None | list[str] | dict[Literal['and', 'equal', 'greater_than', 'greater_than_equal', 'less_than', 'less_than_equal', 'not_equal', 'syntax'], None | str | list[str]]`` + - Mapping of allowed PEP440 version specifiers to the syntax used in this + package manager. Three top-level keys are expected and required: + + - ``name_only`` MUST take a list of strings as the syntax used for specifiers + that do not contain any version information; it MUST include the placeholder + ``{name}``. + + - ``exact_version`` MUST be ``None`` or a list of strings that describe + the syntax used for specifiers that only express exact version + constraints; in the latter case, the placeholders ``{name}`` + and ``{version}`` MUST be present in at least one of the strings + (although not necessary the same string for both). + + - ``version_ranges`` MUST be ``None`` or a dictionary with the + following required keys: + + - the key ``syntax`` takes a list of strings where at least one MUST + include the ``{ranges}`` placeholder (to be replaced by the + maybe-joined version constraints, as determined by the value of + ``and``). They MAY also include the ``{name}`` placeholder. + + - the keys ``equal``, ``greater_than``, ``greater_than_equal``, + ``less_than``, ``less_than_equal``, and ``not_equal`` take a string + if the operator is supported, ``None`` otherwise. In the former case, + the value MUST include the ``{version}`` placeholder, and MAY include + ``{name}``. + + - the key ``{and}`` takes a string used to join multiple version + constraints in a single token, or ``None`` if only a single + constraint can be used per token. In the latter case, the different + constraints will be "exploded" into several tokens using the + ``syntax`` template. + + When ``exact_version`` or ``version_ranges`` are set to ``None``, it + indicates that the respective types of specifiers are not supported + by the package manager. + - True - * - ``version_operators`` - - ``dict[Literal['and', 'arbitrary', 'compatible', 'equal', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equal', 'separator'], string]`` - - Mapping of PEP440 version comparison operators to the syntax used in this - package manager. If omitted, PEP 440 operators are used. If set to an empty - dictionary, it means that the package manager (or ecosystem) doesn't support - the notion of requesting particular package versions. The keys are ``and``, - ``arbitrary``, ``compatible``, ``equal``, ``greater_than_equal``, - ``greater_than``, ``less_than_equal``, ``less_than``, ``not_equal``, and - ``separator``. Empty strings can be used as a value if that particular operator - is not supported. - - False Examples @@ -680,9 +726,10 @@ for brevity, could look like: "command": [ "conda", "install", - "--yes", "{}" - ] + ], + "multiple_specifiers": "always", + "requires_elevation": false, }, "query": { "command": [ @@ -690,26 +737,37 @@ for brevity, could look like: "list", "-f", "{}" - ] + ], + "multiple_specifiers": "never", + "requires_elevation": false, } }, - "version_operators": { - "and": ",", - "arbitrary": "==", - "compatible": "~=", - "equal": "=", - "greater_than": ">", - "greater_than_equal": ">=", - "less_than": "<", - "less_than_equal": "<=", - "not_equal": "!=", - "separator": "" + "specifier_syntax": { + "exact_version": [ + "{name}=={version}" + ], + "name_only": [ + "{name}" + ], + "version_ranges": { + "and": ",", + "equal": "={version}", + "greater_than": ">{version}", + "greater_than_equal": ">={version}", + "less_than": "<{version}", + "less_than_equal": "<={version}", + "not_equal": "!={version}", + "syntax": [ + "{name}{ranges}" + ] + } } } ] } -The following repository provides additional examples of how these schemas would look like in real cases: +The following repository provides examples of how these schemas *could* look like in real cases. +They are not meant to be prescriptive, but just illustrative of how to apply these schemas: - `Central registry `__. @@ -915,11 +973,11 @@ The ``pyproject-external`` Python API also allows users to do these operations p # {"command": ["pixi", "add", "{}"]} ['pixi', 'add', 'c-compiler', 'cxx-compiler', 'python'] >>> external.query_commands(ecosystem, package_manager=package_manager) - # {"command": ["pixi", "list", "^{}$"]} + # {"command": ["pixi", "list", "{}"]} [ - ['pixi', 'list', '^c-compiler$'], - ['pixi', 'list', '^cxx-compiler$'], - ['pixi', 'list', '^python$'], + ['pixi', 'list', 'c-compiler'], + ['pixi', 'list', 'cxx-compiler'], + ['pixi', 'list', 'python'], ] Grayskull