|
| 1 | +Ranges |
| 2 | +====== |
| 3 | + |
| 4 | +.. versionadded:: 26.3 |
| 5 | + |
| 6 | +A :class:`~packaging.ranges.VersionRange` represents the set of |
| 7 | +:class:`~packaging.version.Version` values matched by a |
| 8 | +:class:`~packaging.specifiers.Specifier` or |
| 9 | +:class:`~packaging.specifiers.SpecifierSet`. Unlike a |
| 10 | +:class:`~packaging.specifiers.SpecifierSet`, ranges are closed under |
| 11 | +intersection, union, and complement, so questions like "do these two |
| 12 | +constraints overlap?" or "is this constraint a subset of that one?" |
| 13 | +reduce to direct set operations. |
| 14 | + |
| 15 | +Constructing a range |
| 16 | +-------------------- |
| 17 | + |
| 18 | +Build a range from a :class:`Specifier` or :class:`SpecifierSet` |
| 19 | +using :meth:`~Specifier.to_range`: |
| 20 | + |
| 21 | +.. doctest:: |
| 22 | + |
| 23 | + >>> from packaging.ranges import VersionRange |
| 24 | + >>> from packaging.specifiers import Specifier, SpecifierSet |
| 25 | + >>> r = SpecifierSet(">=1.0,<2.0").to_range() |
| 26 | + >>> "1.5" in r |
| 27 | + True |
| 28 | + >>> "2.0" in r |
| 29 | + False |
| 30 | + |
| 31 | +The classmethods :meth:`VersionRange.from_specifier` and |
| 32 | +:meth:`VersionRange.from_specifier_set` produce the same results and |
| 33 | +are useful when only a :class:`VersionRange` reference is in scope. |
| 34 | + |
| 35 | +Three factories return common identity ranges: |
| 36 | + |
| 37 | +.. doctest:: |
| 38 | + |
| 39 | + >>> VersionRange.empty().is_empty |
| 40 | + True |
| 41 | + >>> "1.5" in VersionRange.full() |
| 42 | + True |
| 43 | + >>> "1.0" in VersionRange.singleton("1.0") |
| 44 | + True |
| 45 | + |
| 46 | +Calling ``VersionRange()`` directly raises :exc:`TypeError`; use one |
| 47 | +of the factories above. |
| 48 | + |
| 49 | +Set algebra |
| 50 | +----------- |
| 51 | + |
| 52 | +:class:`VersionRange` supports intersection, union, and complement |
| 53 | +via the :meth:`~VersionRange.intersection`, |
| 54 | +:meth:`~VersionRange.union`, and :meth:`~VersionRange.complement` |
| 55 | +methods, or the ``&``, ``|``, and ``~`` operator aliases. Every |
| 56 | +operation returns a new range; operands are not mutated. |
| 57 | + |
| 58 | +.. doctest:: |
| 59 | + |
| 60 | + >>> ge1 = SpecifierSet(">=1.0").to_range() |
| 61 | + >>> lt2 = SpecifierSet("<2.0").to_range() |
| 62 | + >>> "1.5" in (ge1 & lt2) |
| 63 | + True |
| 64 | + >>> "2.5" in (ge1 | lt2) |
| 65 | + True |
| 66 | + >>> # Double-complement is the original range. |
| 67 | + >>> ~~ge1 == ge1 |
| 68 | + True |
| 69 | + >>> # A range and its complement are always disjoint. |
| 70 | + >>> bool(ge1 & ~ge1) |
| 71 | + False |
| 72 | + |
| 73 | +Set operations answer overlap and subset questions directly: |
| 74 | + |
| 75 | +.. doctest:: |
| 76 | + |
| 77 | + >>> a = SpecifierSet(">=1.0,<2.0").to_range() |
| 78 | + >>> b = SpecifierSet(">=1.5,<3.0").to_range() |
| 79 | + >>> # Do these constraints overlap? |
| 80 | + >>> bool(a & b) |
| 81 | + True |
| 82 | + >>> # Is *a* entirely contained in *b*? |
| 83 | + >>> (a & b) == a |
| 84 | + False |
| 85 | + >>> narrow = SpecifierSet(">=1.0,<1.5").to_range() |
| 86 | + >>> wide = SpecifierSet(">=1.0,<2.0").to_range() |
| 87 | + >>> (narrow & wide) == narrow |
| 88 | + True |
| 89 | + |
| 90 | +Membership and filtering |
| 91 | +------------------------ |
| 92 | + |
| 93 | +``in`` and :meth:`~VersionRange.filter` mirror :class:`SpecifierSet`'s |
| 94 | +:meth:`~SpecifierSet.__contains__` and :meth:`~SpecifierSet.filter`, |
| 95 | +including the PEP 440 pre-release behaviour: with |
| 96 | +``prereleases=None`` (the default), pre-releases are buffered and |
| 97 | +emitted only when the iterable contains no in-range final release. |
| 98 | + |
| 99 | +.. doctest:: |
| 100 | + |
| 101 | + >>> from packaging.version import Version |
| 102 | + >>> r = SpecifierSet(">=1.0,<2.0").to_range() |
| 103 | + >>> "1.5" in r |
| 104 | + True |
| 105 | + >>> Version("1.5") in r |
| 106 | + True |
| 107 | + >>> list(r.filter(["0.9", "1.5", "2.0"])) |
| 108 | + ['1.5'] |
| 109 | + |
| 110 | +Converting back to a SpecifierSet |
| 111 | +--------------------------------- |
| 112 | + |
| 113 | +:meth:`~VersionRange.to_specifier_set` returns a single |
| 114 | +:class:`SpecifierSet` whose :meth:`~SpecifierSet.to_range` yields the |
| 115 | +same range, or ``None`` if no such single set exists. Redundant |
| 116 | +specifiers are dropped, which makes the round-trip a useful |
| 117 | +normalisation step: |
| 118 | + |
| 119 | +.. doctest:: |
| 120 | + |
| 121 | + >>> r = SpecifierSet(">=1.0,<2.0,!=1.5").to_range() |
| 122 | + >>> str(r.to_specifier_set()) |
| 123 | + '!=1.5,<2.0,>=1.0' |
| 124 | + >>> # ``>2`` is subsumed by ``>=3``; ``!=1.0`` is outside ``>=3``. |
| 125 | + >>> str(SpecifierSet("!=1.0,>2,>=3").to_range().to_specifier_set()) |
| 126 | + '>=3' |
| 127 | + |
| 128 | +PEP 440 specifier sets are not closed under union, so the disjoint |
| 129 | +union of two intervals returns ``None``; |
| 130 | +:meth:`~VersionRange.to_specifier_sets` returns one |
| 131 | +:class:`SpecifierSet` per interval: |
| 132 | + |
| 133 | +.. doctest:: |
| 134 | + |
| 135 | + >>> r = ( |
| 136 | + ... SpecifierSet(">=1.0,<2.0").to_range() |
| 137 | + ... | SpecifierSet(">=3.0,<4.0").to_range() |
| 138 | + ... ) |
| 139 | + >>> r.to_specifier_set() is None |
| 140 | + True |
| 141 | + >>> [str(s) for s in r.to_specifier_sets()] |
| 142 | + ['<2.0,>=1.0', '<4.0,>=3.0'] |
| 143 | + |
| 144 | +The empty range round-trips through ``SpecifierSet("<0")`` (``<0`` |
| 145 | +excludes the smallest possible PEP 440 version, ``0.dev0``): |
| 146 | + |
| 147 | +.. doctest:: |
| 148 | + |
| 149 | + >>> VersionRange.empty().to_specifier_set() == SpecifierSet("<0") |
| 150 | + True |
| 151 | + |
| 152 | +Reference |
| 153 | +--------- |
| 154 | + |
| 155 | +.. autoclass:: packaging.ranges.VersionRange |
| 156 | + :members: |
| 157 | + :special-members: __contains__, __bool__, __eq__, __hash__, __repr__ |
0 commit comments