Version Ranges

A VersionRange is the set of Version values accepted by a SpecifierSet, viewed as intervals on the PEP 440 ordering. It supports intersection, union, and complement, so tooling that combines many requirements, such as a resolver, can work on the intervals directly.

Usage

>>> from packaging.ranges import VersionRange
>>> from packaging.specifiers import SpecifierSet
>>> from packaging.version import Version
>>> # Build a range from a specifier set
>>> r = SpecifierSet(">=1.0,<2.0").to_range()
>>> r
<VersionRange '[1.0, 2.0.dev0)'>
>>> Version("1.5") in r
True
>>> Version("2.0") in r
False
>>> # Combine ranges with set algebra
>>> a = SpecifierSet(">=1.0").to_range()
>>> b = SpecifierSet("<2.0").to_range()
>>> a & b == r
True
>>> # The complement is every other version
>>> Version("0.5") in ~r
True
>>> # Filter an iterable of versions
>>> list(r.filter(["0.9", "1.5", "2.0"]))
['1.5']
>>> # An unsatisfiable set produces the empty range
>>> SpecifierSet(">=2.0,<1.0").to_range().is_empty
True

Comparing ranges

Equality on a VersionRange is structural: two ranges are equal only when they behave the same under VersionRange.contains() and VersionRange.filter(). Equality covers the pre-release policy and any === admission, not only the versions matched. Intersection can change one of those without changing the version set, so the textbook subset test a & b == a can report a false negative. Use the algebra and VersionRange.is_empty for set relations:

>>> from packaging.specifiers import SpecifierSet
>>> a = SpecifierSet(">=1.0").to_range()
>>> b = SpecifierSet(">=1.0a1").to_range()
>>> # a is a subset of b (every version >=1.0 is also >=1.0a1), but b
>>> # admits pre-releases, so ``a & b`` and ``a`` differ only in policy:
>>> a & b == a
False
>>> (a & ~b).is_empty       # subset: a has no version outside b
True
>>> (a & b).is_empty        # disjoint: a and b share no version
False

Different specifiers for the same set of versions canonicalize to one form, so they compare equal. >1.0a1 excludes 1.0a1’s post-releases per PEP 440, so its smallest member is 1.0a2.dev0, exactly the set of >=1.0a2.dev0:

>>> SpecifierSet(">1.0a1").to_range() == SpecifierSet(">=1.0a2.dev0").to_range()
True

The pre-release policy is still part of equality. <1.0.post0.dev0 and <=1.0 cover the same versions, but the first admits pre-releases by default (its bound is a .dev release) while the second does not, so they are not substitutable and compare unequal:

>>> SpecifierSet("<1.0.post0.dev0").to_range() == SpecifierSet("<=1.0").to_range()
False

Reference

Public VersionRange API.

A set-algebra view of the versions accepted by a SpecifierSet. Ranges support intersection, union, and complement; membership and filtering match the originating specifier set.

class packaging.ranges.VersionRange

A set of Version values accepted by a SpecifierSet.

Construct via to_range(), or with the full(), empty(), and singleton() class methods. Compose with intersection(), union(), and complement() (or the & / | / ~ operators). Test membership with in or contains(), filter an iterable with filter().

The configured pre-release policy of the originating specifier set carries onto the range and controls whether pre-releases are admitted under in, contains(), and filter(). intersection() and union() require both operands to share the same policy.

>>> r = SpecifierSet(">=1.0,<2.0").to_range()
>>> "1.5" in r
True
>>> "2.0" in r
False
>>> SpecifierSet(">=2.0,<1.0").to_range().is_empty
True

PEP 440’s === operator matches a candidate string verbatim (case-insensitive) rather than a set of versions. Ranges built from === specifiers still support membership and set operations; matching follows the literal-equality rule.

static __new__(cls, *args, **kwargs)
Parameters:
Return type:

VersionRange

classmethod empty(*, prereleases=None)

Return the empty range. No version satisfies it.

>>> VersionRange.empty().is_empty
True
>>> "1.0" in VersionRange.empty()
False
Parameters:

prereleases (bool | None)

Return type:

VersionRange

classmethod full(*, admit_arbitrary=True, prereleases=None)

Return the full range. Every PEP 440 version satisfies it.

admit_arbitrary=False restricts the range to PEP 440 versions only (matching the same versions as SpecifierSet(">=0.dev0").to_range()); its complement is empty(). The flag propagates through set algebra and is part of equality. Default True so that r & full() preserves r’s own flag structurally.

>>> "1.0" in VersionRange.full()
True
>>> "garbage" in VersionRange.full()
True
>>> "garbage" in VersionRange.full(admit_arbitrary=False)
False
Parameters:
  • admit_arbitrary (bool)

  • prereleases (bool | None)

Return type:

VersionRange

classmethod singleton(version, *, prereleases=None)

Return the strict singleton range {version}.

Built as the closed interval [version, version] with strict equality. Specifier("==V") matches V+local too, so the strict singleton is narrower:

>>> "1.0+local" in VersionRange.singleton("1.0")
False
>>> "1.0+local" in SpecifierSet("==1.0").to_range()
True
Raises:

packaging.version.InvalidVersion – if version is a string that does not parse as a PEP 440 version.

Parameters:
Return type:

VersionRange

intersection(other)

Range containing exactly the versions in both self and other.

Both operands must share the same configured pre-release policy; otherwise ValueError is raised.

>>> a = SpecifierSet(">=1.0").to_range()
>>> b = SpecifierSet("<2.0").to_range()
>>> a.intersection(b) == SpecifierSet(">=1.0,<2.0").to_range()
True
Parameters:

other (VersionRange)

Return type:

VersionRange

union(other)

Range containing every version in self or other.

Both operands must share the same configured pre-release policy; otherwise ValueError is raised.

>>> a = VersionRange.singleton("1.0")
>>> b = VersionRange.singleton("2.0")
>>> "1.0" in a.union(b) and "2.0" in a.union(b)
True
>>> "1.5" in a.union(b)
False
Parameters:

other (VersionRange)

Return type:

VersionRange

complement()

Range containing every version not in self.

Preserves the configured pre-release policy. Within the PEP 440 universe (no === literals and no arbitrary admission) double negation holds; for === ranges complement is one-way.

>>> r = SpecifierSet(">=1.0").to_range()
>>> "0.5" in r.complement()
True
>>> "1.5" in r.complement()
False
>>> r.complement().complement() == r
True
Return type:

VersionRange

__and__(other)

Operator alias for intersection().

Parameters:

other (object)

Return type:

VersionRange

__or__(other)

Operator alias for union().

Parameters:

other (object)

Return type:

VersionRange

__invert__()

Operator alias for complement().

Return type:

VersionRange

filter(iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None, key: None = None) Iterator[UnparsedVersionVar]
filter(iterable: Iterable[T], prereleases: bool | None = None, key: Callable[[T], UnparsedVersion] = None) Iterator[T]

Yield items from iterable whose version falls inside the range.

With prereleases None the PEP 440 default applies: pre-releases are buffered and only emitted if no final release in iterable is in range.

The signature mirrors filter().

>>> r = SpecifierSet(">=1.0,<2.0").to_range()
>>> list(r.filter(["0.9", "1.5", "2.0"]))
['1.5']
Parameters:
  • iterable (Iterable[Any])

  • prereleases (bool | None)

  • key (Callable[[Any], Version | str] | None)

Return type:

Iterator[Any]

property is_empty: bool

True if no version or string satisfies this range.

Agrees with is_unsatisfiable(), including the pre-release policy: a range whose only members are pre-releases is empty when that policy excludes them.

>>> SpecifierSet(">=2,<1").to_range().is_empty
True
>>> SpecifierSet(">=1,<2").to_range().is_empty
False
>>> SpecifierSet("==1.0a1", prereleases=False).to_range().is_empty
True
contains(item, prereleases=None, installed=None)

Return whether item is contained in this range.

Parameters:
  • item (Version | str) – a version string or Version.

  • prereleases (bool | None) – whether to match pre-releases. None (default) uses the range’s own policy.

  • installed (bool | None) – when True, accept a pre-release item even if the range would not otherwise allow it.

Return type:

bool

Unparsable strings do not match, except where the full SpecifierSet would also match: the full range admits any string, and a === range admits items equal to the literal case-insensitively.

>>> r = SpecifierSet(">=1.0,<2.0").to_range()
>>> r.contains("1.5")
True
>>> r.contains("2.0")
False
Raises:

TypeError – if item is not a str or Version.

Parameters:
Return type:

bool

__contains__(item)

Return whether item is contained in this range.

Forwards to contains() with default arguments.

>>> "1.5" in SpecifierSet(">=1.0,<2.0").to_range()
True
Parameters:

item (Version | str)

Return type:

bool

__eq__(other)

Structural equality.

Two ranges compare equal when every input to contains() and filter() agrees: the bounds, the === admit/reject literals, the arbitrary-string flag, and both the configured and resolved pre-release policies. Equal ranges therefore filter identically.

Different specifiers for the same set fold to one canonical bound form, so they compare equal. >1.0a1 excludes 1.0a1’s post-releases, so its smallest member is 1.0a2.dev0, the same set as >=1.0a2.dev0:

>>> SpecifierSet(">1.0a1").to_range() == SpecifierSet(">=1.0a2.dev0").to_range()
True

The pre-release policy is still part of equality, so two ranges with the same versions but different policies stay unequal:

>>> le, lt = SpecifierSet("<=1.0"), SpecifierSet("<1.0.post0.dev0")
>>> le.to_range() == lt.to_range()
False
>>> r = SpecifierSet(">=1.0,<2.0").to_range()
>>> r == SpecifierSet(">=1.0,<2.0").to_range()
True
Parameters:

other (object)

Return type:

bool

__hash__()

Return hash(self).

Return type:

int

__repr__()

Human-readable representation for debugging.

>>> SpecifierSet(">=1.0,<2.0").to_range()
<VersionRange '[1.0, 2.0.dev0)'>
>>> SpecifierSet("").to_range()
<VersionRange '(-inf, +inf)' arbitrary>
>>> SpecifierSet(">=2.0,<1.0").to_range()
<VersionRange '(empty)'>
Return type:

str