pypa/pip · error · TypeError

expected VersionRange, got {type(other).__name__}

Error message

expected VersionRange, got {type(other).__name__}

What it means

TypeError raised by VersionRange._check_policy_compat (invoked by union, intersection, difference, issubset, isdisjoint, etc.) when the 'other' operand is not a VersionRange. VersionRange set-algebra is only defined between two VersionRange instances; mixing in a SpecifierSet, str, or set is a type error.

Source

Thrown at src/pip/_vendor/packaging/ranges.py:1087

        away from full bounds it survives only on empty-bounds ranges, where
        it keeps ``~~full() == full()`` and union idempotent.
        """
        return self._admit_arbitrary and self._bounds == FULL_RANGE

    def _is_plain(self) -> bool:
        """True when membership is decided by ``_bounds`` alone, enabling the
        bounds-only fast paths in :meth:`is_subset` and :meth:`is_disjoint`.
        """
        return (
            not self._has_literals()
            and not self._admit_arbitrary
            and self._prereleases_configured is not False
        )

    def _check_policy_compat(self, other: VersionRange) -> None:
        """Refuse combining ranges with different pre-release policies."""
        if not isinstance(other, VersionRange):
            raise TypeError(f"expected VersionRange, got {type(other).__name__}")
        if self._prereleases_configured != other._prereleases_configured:
            raise ValueError(
                "Cannot combine VersionRange operands with different "
                f"pre-release policies: {self._prereleases_configured!r} "
                f"and {other._prereleases_configured!r}"
            )

    def _merged_region(self, other: VersionRange) -> tuple[Interval, ...]:
        """Union of ``self`` and ``other``'s opt-in regions.

        Used by :meth:`union` and :meth:`intersection`; :meth:`_build` clips the
        merge to the result bounds. A configured operand carries an empty region,
        so it contributes nothing to the merge.
        """
        # Reuse an operand's canonical tuple when only one side has a region;
        # an empty side contributes nothing to the union.
        if not other._pre_region:
            return self._pre_region

View on GitHub (pinned to f399c37189)

Solutions

  1. Convert the non-range operand to a VersionRange first: SpecifierSet(s).to_range() for a specifier, VersionRange.singleton(v) for a single version.
  2. If you hold a SpecifierSet, call .to_range() once and reuse the resulting VersionRange in all set operations.
  3. Type-narrow inputs in shared helpers so only VersionRange reaches the set-algebra methods.
  4. If the operand is a plain string meant as an exact match, use VersionRange.singleton(string).

Example fix

# before
from packaging.specifiers import SpecifierSet
r = SpecifierSet('>=1.0').to_range()
combined = r.union(SpecifierSet('<2.0'))  # TypeError

# after
combined = r.union(SpecifierSet('<2.0').to_range())
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

from packaging.ranges import VersionRange
from packaging.specifiers import SpecifierSet

def as_version_range(x) -> VersionRange:
    if isinstance(x, VersionRange):
        return x
    if isinstance(x, SpecifierSet):
        return x.to_range()
    if isinstance(x, str):
        return SpecifierSet(x).to_range()
    raise TypeError(f"expected VersionRange/SpecifierSet/str, got {type(x).__name__}")

# then: a.union(as_version_range(other))

Try / catch

try:
    combined = a.union(b)
except TypeError as e:
    # convert b to a VersionRange via as_version_range() and retry
    raise

Prevention

When it happens

Trigger: Calling e.g. range_a.union(specifierset) or range_a.intersection('>=1.0') — passing anything that is not an instance of VersionRange. The guard 'isinstance(other, VersionRange)' fails and raises with the actual type name.

Common situations: Refactoring code that previously combined SpecifierSets directly; passing a specifier string assuming implicit conversion; a generic helper that accepts both types and forgets to convert.

Related errors


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