pypa/pip · error · TypeError

Unexpected marker

Error message

Unexpected marker {marker!r}

What it means

Defensive TypeError in _evaluate_markers (markers.py:325, marked '# pragma: nocover') for a marker element that is neither a list, a tuple, the 'and' string, nor the 'or' string. The parser grammar (_parse_marker) cannot produce such an element, so this branch is effectively unreachable through the public Marker/Requirement APIs.

Solutions

  1. Do not mutate _markers directly; always construct via Marker(string) or the __and__/__or__ combinators.
  2. If encountered without direct attribute tampering, file a packaging bug report with the marker string.
Defensive patterns

Strategy: try-catch

Try / catch

from packaging.markers import Marker

try:
    Marker(expr).evaluate()
except TypeError as e:
    # defensive/unexpected internal state; report upstream
    ...

Prevention

When it happens

Trigger: Not reachable via normal parsing. Only by directly assigning a malformed structure to a Marker's private _markers attribute (e.g. in tests/mocking) or via an internal packaging bug.

Common situations: Monkeypatching or hand-building marker._markers in tests with an invalid node type.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/c7e22347e26d7627. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/markers.py:325

                lhs_value = lhs.value
                environment_key = rhs.value
                rhs_value = _lookup_environment(environment, environment_key)

            if not isinstance(lhs_value, str):
                raise UndefinedComparison(
                    f"Set-valued marker {environment_key!r} can only be used "
                    f'with the membership form (e.g. "<name>" in '
                    f"{environment_key}); it cannot appear on the left-hand "
                    f"side of {op.serialize()!r}."
                )
            lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
            groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
        elif marker == "or":
            groups.append([])
        elif marker == "and":
            pass
        else:  # pragma: nocover
            raise TypeError(f"Unexpected marker {marker!r}")

    return any(all(item) for item in groups)


def _format_full_version(info: sys._version_info) -> str:
    version = f"{info.major}.{info.minor}.{info.micro}"
    kind = info.releaselevel
    if kind != "final":
        version += kind[0] + str(info.serial)
    return version


@functools.cache
def _cached_default_environment() -> Environment:
    """Build the default marker environment for the current Python process.

    The values are derived from process-constant data (the running interpreter
    and the host platform), so this is cached and built only once. The result is

View on GitHub (pinned to f399c37189)