Skip to content

Commit

Permalink
MAINT: update vendored version util from packaging (#59558)
Browse files Browse the repository at this point in the history
* MAINT: update vendored version util from packaging

* fix docstring

* fix docstring

* skip docstring validation

* ignore

* fix validation ignore

* remove docstring

* rollback

* add comments
  • Loading branch information
fangchenli authored Aug 25, 2024
1 parent fe42b3b commit 90e8e04
Showing 1 changed file with 51 additions and 187 deletions.
238 changes: 51 additions & 187 deletions pandas/util/version/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
# Vendored from https://github.com/pypa/packaging/blob/main/packaging/_structures.py
# and https://github.com/pypa/packaging/blob/main/packaging/_structures.py
# changeset ae891fd74d6dd4c6063bb04f2faeadaac6fc6313
# 04/30/2021
# Vendored from https://github.com/pypa/packaging/blob/main/src/packaging/_structures.py
# and https://github.com/pypa/packaging/blob/main/src/packaging/version.py
# changeset 24e5350b2ff3c5c7a36676c2af5f2cb39fd1baf8

# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. Licence at LICENSES/PACKAGING_LICENSE
from __future__ import annotations

import collections
from collections.abc import (
Callable,
Iterator,
)
from collections.abc import Callable
import itertools
import re
from typing import (
Any,
NamedTuple,
SupportsInt,
Tuple,
Union,
)
import warnings

__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]


class InfinityType:
Expand All @@ -40,9 +35,6 @@ def __le__(self, other: object) -> bool:
def __eq__(self, other: object) -> bool:
return isinstance(other, type(self))

def __ne__(self, other: object) -> bool:
return not isinstance(other, type(self))

def __gt__(self, other: object) -> bool:
return True

Expand Down Expand Up @@ -72,9 +64,6 @@ def __le__(self, other: object) -> bool:
def __eq__(self, other: object) -> bool:
return isinstance(other, type(self))

def __ne__(self, other: object) -> bool:
return not isinstance(other, type(self))

def __gt__(self, other: object) -> bool:
return False

Expand All @@ -88,45 +77,39 @@ def __neg__(self: object) -> InfinityType:
NegativeInfinity = NegativeInfinityType()


InfiniteTypes = Union[InfinityType, NegativeInfinityType]
PrePostDevType = Union[InfiniteTypes, tuple[str, int]]
SubLocalType = Union[InfiniteTypes, int, str]
LocalType = Union[
LocalType = tuple[Union[int, str], ...]

CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, tuple[str, int]]
CmpLocalType = Union[
NegativeInfinityType,
tuple[
Union[
SubLocalType,
tuple[SubLocalType, str],
tuple[NegativeInfinityType, SubLocalType],
],
...,
],
tuple[Union[tuple[int, str], tuple[NegativeInfinityType, Union[int, str]]], ...],
]
CmpKey = tuple[
int, tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
]
LegacyCmpKey = tuple[int, tuple[str, ...]]
VersionComparisonMethod = Callable[
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
int,
tuple[int, ...],
CmpPrePostDevType,
CmpPrePostDevType,
CmpPrePostDevType,
CmpLocalType,
]
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]

_Version = collections.namedtuple(
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)

class _Version(NamedTuple):
epoch: int
release: tuple[int, ...]
dev: tuple[str, int] | None
pre: tuple[str, int] | None
post: tuple[str, int] | None
local: LocalType | None

def parse(version: str) -> LegacyVersion | Version:
"""
Parse the given version string and return either a :class:`Version` object
or a :class:`LegacyVersion` object depending on if the given version is
a valid PEP 440 version or a legacy version.
"""
try:
return Version(version)
except InvalidVersion:
return LegacyVersion(version)

def parse(version: str) -> Version:
return Version(version)


# The docstring is from an older version of the packaging library to avoid
# errors in the docstring validation.
class InvalidVersion(ValueError):
"""
An invalid version was found, users should refer to PEP 440.
Expand All @@ -140,7 +123,7 @@ class InvalidVersion(ValueError):


class _BaseVersion:
_key: CmpKey | LegacyCmpKey
_key: tuple[Any, ...]

def __hash__(self) -> int:
return hash(self._key)
Expand Down Expand Up @@ -185,132 +168,16 @@ def __ne__(self, other: object) -> bool:
return self._key != other._key


class LegacyVersion(_BaseVersion):
def __init__(self, version: str) -> None:
self._version = str(version)
self._key = _legacy_cmpkey(self._version)

warnings.warn(
"Creating a LegacyVersion has been deprecated and will be "
"removed in the next major release.",
DeprecationWarning,
)

def __str__(self) -> str:
return self._version

def __repr__(self) -> str:
return f"<LegacyVersion('{self}')>"

@property
def public(self) -> str:
return self._version

@property
def base_version(self) -> str:
return self._version

@property
def epoch(self) -> int:
return -1

@property
def release(self) -> None:
return None

@property
def pre(self) -> None:
return None

@property
def post(self) -> None:
return None

@property
def dev(self) -> None:
return None

@property
def local(self) -> None:
return None

@property
def is_prerelease(self) -> bool:
return False

@property
def is_postrelease(self) -> bool:
return False

@property
def is_devrelease(self) -> bool:
return False


_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)

_legacy_version_replacement_map = {
"pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
}


def _parse_version_parts(s: str) -> Iterator[str]:
for part in _legacy_version_component_re.split(s):
mapped_part = _legacy_version_replacement_map.get(part, part)

if not mapped_part or mapped_part == ".":
continue

if mapped_part[:1] in "0123456789":
# pad for numeric comparison
yield mapped_part.zfill(8)
else:
yield "*" + mapped_part

# ensure that alpha/beta/candidate are before final
yield "*final"


def _legacy_cmpkey(version: str) -> LegacyCmpKey:
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
# greater than or equal to 0. This will effectively put the LegacyVersion,
# which uses the defacto standard originally implemented by setuptools,
# as before all PEP 440 versions.
epoch = -1

# This scheme is taken from pkg_resources.parse_version setuptools prior to
# it's adoption of the packaging library.
parts: list[str] = []
for part in _parse_version_parts(version.lower()):
if part.startswith("*"):
# remove "-" before a prerelease tag
if part < "*final":
while parts and parts[-1] == "*final-":
parts.pop()

# remove trailing zeros from each series of numeric parts
while parts and parts[-1] == "00000000":
parts.pop()

parts.append(part)

return epoch, tuple(parts)


# Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse
VERSION_PATTERN = r"""
_VERSION_PATTERN = r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
Expand All @@ -334,9 +201,12 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey:
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""

VERSION_PATTERN = _VERSION_PATTERN


class Version(_BaseVersion):
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
_key: CmpKey

def __init__(self, version: str) -> None:
# Validate the version and parse it into pieces
Expand Down Expand Up @@ -377,11 +247,11 @@ def __str__(self) -> str:
parts.append(f"{self.epoch}!")

# Release segment
parts.append(".".join([str(x) for x in self.release]))
parts.append(".".join(str(x) for x in self.release))

# Pre-release
if self.pre is not None:
parts.append("".join([str(x) for x in self.pre]))
parts.append("".join(str(x) for x in self.pre))

# Post-release
if self.post is not None:
Expand All @@ -399,18 +269,15 @@ def __str__(self) -> str:

@property
def epoch(self) -> int:
_epoch: int = self._version.epoch
return _epoch
return self._version.epoch

@property
def release(self) -> tuple[int, ...]:
_release: tuple[int, ...] = self._version.release
return _release
return self._version.release

@property
def pre(self) -> tuple[str, int] | None:
_pre: tuple[str, int] | None = self._version.pre
return _pre
return self._version.pre

@property
def post(self) -> int | None:
Expand All @@ -423,7 +290,7 @@ def dev(self) -> int | None:
@property
def local(self) -> str | None:
if self._version.local:
return ".".join([str(x) for x in self._version.local])
return ".".join(str(x) for x in self._version.local)
else:
return None

Expand All @@ -440,7 +307,7 @@ def base_version(self) -> str:
parts.append(f"{self.epoch}!")

# Release segment
parts.append(".".join([str(x) for x in self.release]))
parts.append(".".join(str(x) for x in self.release))

return "".join(parts)

Expand Down Expand Up @@ -470,7 +337,7 @@ def micro(self) -> int:


def _parse_letter_version(
letter: str, number: str | bytes | SupportsInt
letter: str | None, number: str | bytes | SupportsInt | None
) -> tuple[str, int] | None:
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
Expand Down Expand Up @@ -507,10 +374,7 @@ def _parse_letter_version(
_local_version_separators = re.compile(r"[\._-]")


def _parse_local_version(local: str) -> LocalType | None:
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
def _parse_local_version(local: str | None) -> LocalType | None:
if local is not None:
return tuple(
part.lower() if not part.isdigit() else int(part)
Expand All @@ -525,7 +389,7 @@ def _cmpkey(
pre: tuple[str, int] | None,
post: tuple[str, int] | None,
dev: tuple[str, int] | None,
local: tuple[SubLocalType] | None,
local: LocalType | None,
) -> CmpKey:
# When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now
Expand All @@ -541,7 +405,7 @@ def _cmpkey(
# if there is not a pre or a post segment. If we have one of those then
# the normal sorting rules will handle this case correctly.
if pre is None and post is None and dev is not None:
_pre: PrePostDevType = NegativeInfinity
_pre: CmpPrePostDevType = NegativeInfinity
# Versions without a pre-release (except as noted above) should sort after
# those with one.
elif pre is None:
Expand All @@ -551,21 +415,21 @@ def _cmpkey(

# Versions without a post segment should sort before those with one.
if post is None:
_post: PrePostDevType = NegativeInfinity
_post: CmpPrePostDevType = NegativeInfinity

else:
_post = post

# Versions without a development segment should sort after those with one.
if dev is None:
_dev: PrePostDevType = Infinity
_dev: CmpPrePostDevType = Infinity

else:
_dev = dev

if local is None:
# Versions without a local segment should sort before those with one.
_local: LocalType = NegativeInfinity
_local: CmpLocalType = NegativeInfinity
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
Expand Down

0 comments on commit 90e8e04

Please sign in to comment.