Weird `platform_version` example in PEP 508

I saw this requirement specification in PEP 508:

tests = [
    ...
    "name[quux, strange];python_version<'2.7' and platform_version=='2'",

which initially parses just fine:

from packaging.requirements import Requirement

req = Requirement("name[quux, strange];python_version<'2.7' and platform_version=='2'")

however, evaluating the markers results in an ugly error:

In [4]: req.marker.evaluate()
---------------------------------------------------------------------------
InvalidVersion                            Traceback (most recent call last)
Cell In[4], line 1
----> 1 req.marker.evaluate()

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/markers.py:245, in Marker.evaluate(self, environment)
    242     if current_environment["extra"] is None:
    243         current_environment["extra"] = ""
--> 245 return _evaluate_markers(self._markers, current_environment)

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/markers.py:151, in _evaluate_markers(markers, environment)
    148         rhs_value = environment[environment_key]
    150     lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
--> 151     groups[-1].append(_eval_op(lhs_value, op, rhs_value))
    152 else:
    153     assert marker in ["and", "or"]

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/markers.py:109, in _eval_op(lhs, op, rhs)
    107     pass
    108 else:
--> 109     return spec.contains(lhs, prereleases=True)
    111 oper: Optional[Operator] = _operators.get(op.serialize())
    112 if oper is None:

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/specifiers.py:565, in Specifier.contains(self, item, prereleases)
    561     prereleases = self.prereleases
    563 # Normalize item to a Version, this allows us to have a shortcut for
    564 # "2.0" in Specifier(">=2")
--> 565 normalized_item = _coerce_version(item)
    567 # Determine if we should be supporting prereleases in this specifier
    568 # or not, if we do not support prereleases than we can short circuit
    569 # logic if this version is a prereleases.
    570 if normalized_item.is_prerelease and not prereleases:

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/specifiers.py:36, in _coerce_version(version)
     34 def _coerce_version(version: UnparsedVersion) -> Version:
     35     if not isinstance(version, Version):
---> 36         version = Version(version)
     37     return version

File ~/.micromamba/envs/kedro38-dev/lib/python3.8/site-packages/packaging/version.py:197, in Version.__init__(self, version)
    195 match = self._regex.search(version)
    196 if not match:
--> 197     raise InvalidVersion(f"Invalid version: '{version}'")
    199 # Store the parsed out pieces of the version
    200 self._version = _Version(
    201     epoch=int(match.group("epoch")) if match.group("epoch") else 0,
    202     release=tuple(int(i) for i in match.group("release").split(".")),
   (...)
    208     local=_parse_local_version(match.group("local")),
    209 )

InvalidVersion: Invalid version: 'Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000'

Is this expected? (I come from Programmatically getting non-optional requirements of current directory - #2 by layday)

PEP 508 says

The <version_cmp> operators use the PEP 440 version comparison rules when those are defined (that is when both sides have a valid version specifier). If there is no defined PEP 440 behaviour and the operator exists in Python, then the operator falls back to the Python behaviour.

I’m not clear what “when both sides have a valid version specifier” is intended to mean. The example suggests that maybe the idea is that in cases when platform.version() returns a non-PEP440 string, Python string comparison is used. It looks like packaging doesn’t do this.

I’m not sure if this could be described as a bug, the statement “when both sides have a valid version specifier” seems pretty imprecise to be viewed as a specification of the behaviour. At a minimum, it needs clarification (and the packaging library might need modifying based on the clarification).

Personally, I don’t think it’s reasonable to assume platform.version() is a version number (it is on Windows, but not on Linux, and even on Windows there’s no reason to expect PEP 440 semantics from a platform version number). So I’d argue that version comparison operators don’t make sense when used with platform_version.

The only environment markers that make sense as versions are python_version, python_full_version and implementation_version, although even those aren’t required to follow PEP 440. From a practicality point of view, though, they should probably be assumed to work as PEP 440 versions.

1 Like