From 4c987bdcb6e13d2a68a4b7519c88fc480b877f7b Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Mon, 19 Aug 2024 22:41:02 -0700 Subject: [PATCH 1/9] MAINT: update vendored version util from packaging --- pandas/util/version/__init__.py | 419 ++++++++++++++++++-------------- 1 file changed, 231 insertions(+), 188 deletions(-) diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py index 9838e371f0d00..82656bb000377 100644 --- a/pandas/util/version/__init__.py +++ b/pandas/util/version/__init__.py @@ -1,27 +1,23 @@ -# 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: @@ -40,9 +36,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 @@ -72,9 +65,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 @@ -88,59 +78,57 @@ 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. + +def parse(version: str) -> Version: + """Parse the given version string. + + >>> parse("1.0.dev1") + + + :param version: The version string to parse. + :raises InvalidVersion: When the version string is not a valid version. """ - try: - return Version(version) - except InvalidVersion: - return LegacyVersion(version) + return Version(version) class InvalidVersion(ValueError): - """ - An invalid version was found, users should refer to PEP 440. + """Raised when a version string is not a valid version. - Examples - -------- - >>> pd.util.version.Version("1.") + >>> Version("invalid") Traceback (most recent call last): - InvalidVersion: Invalid version: '1.' + ... + packaging.version.InvalidVersion: Invalid version: 'invalid' """ class _BaseVersion: - _key: CmpKey | LegacyCmpKey + _key: tuple[Any, ...] def __hash__(self) -> int: return hash(self._key) @@ -185,132 +173,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"" - - @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[0-9]+)!)? # epoch (?P[0-9]+(?:\.[0-9]+)*) # release segment (?P
                                          # pre-release
             [-_\.]?
-            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            (?Palpha|a|beta|b|preview|pre|c|rc)
             [-_\.]?
             (?P[0-9]+)?
         )?
@@ -334,11 +206,57 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey:
     (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
 """
 
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
 
 class Version(_BaseVersion):
+    """This class abstracts handling of a project's versions.
+
+    A :class:`Version` instance is comparison aware and can be compared and
+    sorted using the standard Python interfaces.
+
+    >>> v1 = Version("1.0a5")
+    >>> v2 = Version("1.0")
+    >>> v1
+    
+    >>> v2
+    
+    >>> v1 < v2
+    True
+    >>> v1 == v2
+    False
+    >>> v1 > v2
+    False
+    >>> v1 >= v2
+    False
+    >>> v1 <= v2
+    True
+    """
+
     _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+    _key: CmpKey
 
     def __init__(self, version: str) -> None:
+        """Initialize a Version object.
+
+        :param version:
+            The string representation of a version which will be parsed and normalized
+            before use.
+        :raises InvalidVersion:
+            If the ``version`` does not conform to PEP 440 in any way then this
+            exception will be raised.
+        """
+
         # Validate the version and parse it into pieces
         match = self._regex.search(version)
         if not match:
@@ -367,9 +285,19 @@ def __init__(self, version: str) -> None:
         )
 
     def __repr__(self) -> str:
+        """A representation of the Version that shows all internal state.
+
+        >>> Version("1.0.0")
+        
+        """
         return f""
 
     def __str__(self) -> str:
+        """A string representation of the version that can be round-tripped.
+
+        >>> str(Version("1.0a5"))
+        '1.0a5'
+        """
         parts = []
 
         # Epoch
@@ -377,11 +305,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:
@@ -399,40 +327,109 @@ def __str__(self) -> str:
 
     @property
     def epoch(self) -> int:
-        _epoch: int = self._version.epoch
-        return _epoch
+        """The epoch of the version.
+
+        >>> Version("2.0.0").epoch
+        0
+        >>> Version("1!2.0.0").epoch
+        1
+        """
+        return self._version.epoch
 
     @property
     def release(self) -> tuple[int, ...]:
-        _release: tuple[int, ...] = self._version.release
-        return _release
+        """The components of the "release" segment of the version.
+
+        >>> Version("1.2.3").release
+        (1, 2, 3)
+        >>> Version("2.0.0").release
+        (2, 0, 0)
+        >>> Version("1!2.0.0.post0").release
+        (2, 0, 0)
+
+        Includes trailing zeroes but not the epoch or any pre-release / development /
+        post-release suffixes.
+        """
+        return self._version.release
 
     @property
     def pre(self) -> tuple[str, int] | None:
-        _pre: tuple[str, int] | None = self._version.pre
-        return _pre
+        """The pre-release segment of the version.
+
+        >>> print(Version("1.2.3").pre)
+        None
+        >>> Version("1.2.3a1").pre
+        ('a', 1)
+        >>> Version("1.2.3b1").pre
+        ('b', 1)
+        >>> Version("1.2.3rc1").pre
+        ('rc', 1)
+        """
+        return self._version.pre
 
     @property
     def post(self) -> int | None:
+        """The post-release number of the version.
+
+        >>> print(Version("1.2.3").post)
+        None
+        >>> Version("1.2.3.post1").post
+        1
+        """
         return self._version.post[1] if self._version.post else None
 
     @property
     def dev(self) -> int | None:
+        """The development number of the version.
+
+        >>> print(Version("1.2.3").dev)
+        None
+        >>> Version("1.2.3.dev1").dev
+        1
+        """
         return self._version.dev[1] if self._version.dev else None
 
     @property
     def local(self) -> str | None:
+        """The local version segment of the version.
+
+        >>> print(Version("1.2.3").local)
+        None
+        >>> Version("1.2.3+abc").local
+        'abc'
+        """
         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
 
     @property
     def public(self) -> str:
+        """The public portion of the version.
+
+        >>> Version("1.2.3").public
+        '1.2.3'
+        >>> Version("1.2.3+abc").public
+        '1.2.3'
+        >>> Version("1!1.2.3dev1+abc").public
+        '1!1.2.3.dev1'
+        """
         return str(self).split("+", 1)[0]
 
     @property
     def base_version(self) -> str:
+        """The "base version" of the version.
+
+        >>> Version("1.2.3").base_version
+        '1.2.3'
+        >>> Version("1.2.3+abc").base_version
+        '1.2.3'
+        >>> Version("1!1.2.3dev1+abc").base_version
+        '1!1.2.3'
+
+        The "base version" is the public version of the project without any pre or post
+        release markers.
+        """
         parts = []
 
         # Epoch
@@ -440,37 +437,83 @@ 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)
 
     @property
     def is_prerelease(self) -> bool:
+        """Whether this version is a pre-release.
+
+        >>> Version("1.2.3").is_prerelease
+        False
+        >>> Version("1.2.3a1").is_prerelease
+        True
+        >>> Version("1.2.3b1").is_prerelease
+        True
+        >>> Version("1.2.3rc1").is_prerelease
+        True
+        >>> Version("1.2.3dev1").is_prerelease
+        True
+        """
         return self.dev is not None or self.pre is not None
 
     @property
     def is_postrelease(self) -> bool:
+        """Whether this version is a post-release.
+
+        >>> Version("1.2.3").is_postrelease
+        False
+        >>> Version("1.2.3.post1").is_postrelease
+        True
+        """
         return self.post is not None
 
     @property
     def is_devrelease(self) -> bool:
+        """Whether this version is a development release.
+
+        >>> Version("1.2.3").is_devrelease
+        False
+        >>> Version("1.2.3.dev1").is_devrelease
+        True
+        """
         return self.dev is not None
 
     @property
     def major(self) -> int:
+        """The first item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").major
+        1
+        """
         return self.release[0] if len(self.release) >= 1 else 0
 
     @property
     def minor(self) -> int:
+        """The second item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").minor
+        2
+        >>> Version("1").minor
+        0
+        """
         return self.release[1] if len(self.release) >= 2 else 0
 
     @property
     def micro(self) -> int:
+        """The third item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").micro
+        3
+        >>> Version("1").micro
+        0
+        """
         return self.release[2] if len(self.release) >= 3 else 0
 
 
 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
@@ -507,7 +550,7 @@ def _parse_letter_version(
 _local_version_separators = re.compile(r"[\._-]")
 
 
-def _parse_local_version(local: str) -> LocalType | None:
+def _parse_local_version(local: str | None) -> LocalType | None:
     """
     Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
     """
@@ -525,7 +568,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
@@ -541,7 +584,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:
@@ -551,21 +594,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.

From abbd9c20c6a182ef8bb4bbe4bab770cee4003ab9 Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Mon, 19 Aug 2024 23:21:33 -0700
Subject: [PATCH 2/9] fix docstring

---
 pandas/util/version/__init__.py | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py
index 82656bb000377..2c3249ddc1948 100644
--- a/pandas/util/version/__init__.py
+++ b/pandas/util/version/__init__.py
@@ -106,7 +106,8 @@ class _Version(NamedTuple):
 
 
 def parse(version: str) -> Version:
-    """Parse the given version string.
+    """
+    Parse the given version string.
 
     >>> parse("1.0.dev1")
     
@@ -118,7 +119,8 @@ def parse(version: str) -> Version:
 
 
 class InvalidVersion(ValueError):
-    """Raised when a version string is not a valid version.
+    """
+    Raised when a version string is not a valid version.
 
     >>> Version("invalid")
     Traceback (most recent call last):
@@ -220,7 +222,8 @@ def __ne__(self, other: object) -> bool:
 
 
 class Version(_BaseVersion):
-    """This class abstracts handling of a project's versions.
+    """
+    This class abstracts handling of a project's versions.
 
     A :class:`Version` instance is comparison aware and can be compared and
     sorted using the standard Python interfaces.
@@ -247,7 +250,8 @@ class Version(_BaseVersion):
     _key: CmpKey
 
     def __init__(self, version: str) -> None:
-        """Initialize a Version object.
+        """
+        Initialize a Version object.
 
         :param version:
             The string representation of a version which will be parsed and normalized
@@ -285,7 +289,8 @@ def __init__(self, version: str) -> None:
         )
 
     def __repr__(self) -> str:
-        """A representation of the Version that shows all internal state.
+        """
+        A representation of the Version that shows all internal state.
 
         >>> Version("1.0.0")
         
@@ -293,7 +298,8 @@ def __repr__(self) -> str:
         return f""
 
     def __str__(self) -> str:
-        """A string representation of the version that can be round-tripped.
+        """
+        A string representation of the version that can be round-tripped.
 
         >>> str(Version("1.0a5"))
         '1.0a5'

From 2b87e441074162c05e97106d9e95225d31af5000 Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Mon, 19 Aug 2024 23:23:54 -0700
Subject: [PATCH 3/9] fix docstring

---
 pandas/util/version/__init__.py | 42 ++++++++++++++++++++++-----------
 1 file changed, 28 insertions(+), 14 deletions(-)

diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py
index 2c3249ddc1948..28e5fb7400662 100644
--- a/pandas/util/version/__init__.py
+++ b/pandas/util/version/__init__.py
@@ -333,7 +333,8 @@ def __str__(self) -> str:
 
     @property
     def epoch(self) -> int:
-        """The epoch of the version.
+        """
+        The epoch of the version.
 
         >>> Version("2.0.0").epoch
         0
@@ -344,7 +345,8 @@ def epoch(self) -> int:
 
     @property
     def release(self) -> tuple[int, ...]:
-        """The components of the "release" segment of the version.
+        """
+        The components of the "release" segment of the version.
 
         >>> Version("1.2.3").release
         (1, 2, 3)
@@ -360,7 +362,8 @@ def release(self) -> tuple[int, ...]:
 
     @property
     def pre(self) -> tuple[str, int] | None:
-        """The pre-release segment of the version.
+        """
+        The pre-release segment of the version.
 
         >>> print(Version("1.2.3").pre)
         None
@@ -375,7 +378,8 @@ def pre(self) -> tuple[str, int] | None:
 
     @property
     def post(self) -> int | None:
-        """The post-release number of the version.
+        """
+        The post-release number of the version.
 
         >>> print(Version("1.2.3").post)
         None
@@ -386,7 +390,8 @@ def post(self) -> int | None:
 
     @property
     def dev(self) -> int | None:
-        """The development number of the version.
+        """
+        The development number of the version.
 
         >>> print(Version("1.2.3").dev)
         None
@@ -397,7 +402,8 @@ def dev(self) -> int | None:
 
     @property
     def local(self) -> str | None:
-        """The local version segment of the version.
+        """
+        The local version segment of the version.
 
         >>> print(Version("1.2.3").local)
         None
@@ -411,7 +417,8 @@ def local(self) -> str | None:
 
     @property
     def public(self) -> str:
-        """The public portion of the version.
+        """
+        The public portion of the version.
 
         >>> Version("1.2.3").public
         '1.2.3'
@@ -424,7 +431,8 @@ def public(self) -> str:
 
     @property
     def base_version(self) -> str:
-        """The "base version" of the version.
+        """
+        The "base version" of the version.
 
         >>> Version("1.2.3").base_version
         '1.2.3'
@@ -449,7 +457,8 @@ def base_version(self) -> str:
 
     @property
     def is_prerelease(self) -> bool:
-        """Whether this version is a pre-release.
+        """
+        Whether this version is a pre-release.
 
         >>> Version("1.2.3").is_prerelease
         False
@@ -466,7 +475,8 @@ def is_prerelease(self) -> bool:
 
     @property
     def is_postrelease(self) -> bool:
-        """Whether this version is a post-release.
+        """
+        Whether this version is a post-release.
 
         >>> Version("1.2.3").is_postrelease
         False
@@ -477,7 +487,8 @@ def is_postrelease(self) -> bool:
 
     @property
     def is_devrelease(self) -> bool:
-        """Whether this version is a development release.
+        """
+        Whether this version is a development release.
 
         >>> Version("1.2.3").is_devrelease
         False
@@ -488,7 +499,8 @@ def is_devrelease(self) -> bool:
 
     @property
     def major(self) -> int:
-        """The first item of :attr:`release` or ``0`` if unavailable.
+        """
+        The first item of :attr:`release` or ``0`` if unavailable.
 
         >>> Version("1.2.3").major
         1
@@ -497,7 +509,8 @@ def major(self) -> int:
 
     @property
     def minor(self) -> int:
-        """The second item of :attr:`release` or ``0`` if unavailable.
+        """
+        The second item of :attr:`release` or ``0`` if unavailable.
 
         >>> Version("1.2.3").minor
         2
@@ -508,7 +521,8 @@ def minor(self) -> int:
 
     @property
     def micro(self) -> int:
-        """The third item of :attr:`release` or ``0`` if unavailable.
+        """
+        The third item of :attr:`release` or ``0`` if unavailable.
 
         >>> Version("1.2.3").micro
         3

From 73c6c928e4de406092f74e1139bbd5a4cabadbda Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Tue, 20 Aug 2024 22:24:21 -0700
Subject: [PATCH 4/9] skip docstring validation

---
 scripts/validate_docstrings.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
index 55acfaac4d843..3d836fb3f1b1d 100755
--- a/scripts/validate_docstrings.py
+++ b/scripts/validate_docstrings.py
@@ -40,6 +40,9 @@
 
 # Styler methods are Jinja2 objects who's docstrings we don't own.
 IGNORE_VALIDATION = {
+    "util.version.parse",
+    "util.version.InvalidVersion",
+    "util.version.Version",
     "Styler.env",
     "Styler.template_html",
     "Styler.template_html_style",

From 35190f14176106351d910a0b1f14639964bc8e5c Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Thu, 22 Aug 2024 22:45:47 -0700
Subject: [PATCH 5/9] ignore

---
 scripts/validate_docstrings.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
index 3d836fb3f1b1d..f6256024a6a43 100755
--- a/scripts/validate_docstrings.py
+++ b/scripts/validate_docstrings.py
@@ -40,9 +40,9 @@
 
 # Styler methods are Jinja2 objects who's docstrings we don't own.
 IGNORE_VALIDATION = {
-    "util.version.parse",
-    "util.version.InvalidVersion",
-    "util.version.Version",
+    "version.parse",
+    "version.InvalidVersion",
+    "version.Version",
     "Styler.env",
     "Styler.template_html",
     "Styler.template_html_style",

From 4d29afeba85420490c78b910be6c16fdf717a6a1 Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Thu, 22 Aug 2024 23:13:21 -0700
Subject: [PATCH 6/9] fix validation ignore

---
 ci/code_checks.sh              | 3 ++-
 scripts/validate_docstrings.py | 3 ---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index e9f4ee1f391a2..e0823a23b24dd 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -548,7 +548,8 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         -i "pandas.tseries.offsets.YearEnd.month GL08" \
         -i "pandas.tseries.offsets.YearEnd.n GL08" \
         -i "pandas.tseries.offsets.YearEnd.normalize GL08" \
-        -i "pandas.util.hash_pandas_object PR07,SA01" # There should be no backslash in the final line, please keep this comment in the last ignored function
+        -i "pandas.util.hash_pandas_object PR07,SA01" \
+        -i "pandas.util.version.InvalidVersion EX01" # There should be no backslash in the final line, please keep this comment in the last ignored function
 
     RET=$(($RET + $?)) ; echo $MSG "DONE"
 
diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
index f6256024a6a43..55acfaac4d843 100755
--- a/scripts/validate_docstrings.py
+++ b/scripts/validate_docstrings.py
@@ -40,9 +40,6 @@
 
 # Styler methods are Jinja2 objects who's docstrings we don't own.
 IGNORE_VALIDATION = {
-    "version.parse",
-    "version.InvalidVersion",
-    "version.Version",
     "Styler.env",
     "Styler.template_html",
     "Styler.template_html_style",

From acaf018cbc4ac62885a801f6aa8c9454cf8798c0 Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Fri, 23 Aug 2024 12:19:03 -0700
Subject: [PATCH 7/9] remove docstring

---
 ci/code_checks.sh               |   3 +-
 pandas/util/version/__init__.py | 212 +-------------------------------
 2 files changed, 2 insertions(+), 213 deletions(-)

diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index e0823a23b24dd..e9f4ee1f391a2 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -548,8 +548,7 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         -i "pandas.tseries.offsets.YearEnd.month GL08" \
         -i "pandas.tseries.offsets.YearEnd.n GL08" \
         -i "pandas.tseries.offsets.YearEnd.normalize GL08" \
-        -i "pandas.util.hash_pandas_object PR07,SA01" \
-        -i "pandas.util.version.InvalidVersion EX01" # There should be no backslash in the final line, please keep this comment in the last ignored function
+        -i "pandas.util.hash_pandas_object PR07,SA01" # There should be no backslash in the final line, please keep this comment in the last ignored function
 
     RET=$(($RET + $?)) ; echo $MSG "DONE"
 
diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py
index 28e5fb7400662..3e726417ab870 100644
--- a/pandas/util/version/__init__.py
+++ b/pandas/util/version/__init__.py
@@ -13,7 +13,6 @@
     Any,
     NamedTuple,
     SupportsInt,
-    Tuple,
     Union,
 )
 
@@ -106,27 +105,10 @@ class _Version(NamedTuple):
 
 
 def parse(version: str) -> Version:
-    """
-    Parse the given version string.
-
-    >>> parse("1.0.dev1")
-    
-
-    :param version: The version string to parse.
-    :raises InvalidVersion: When the version string is not a valid version.
-    """
     return Version(version)
 
 
-class InvalidVersion(ValueError):
-    """
-    Raised when a version string is not a valid version.
-
-    >>> Version("invalid")
-    Traceback (most recent call last):
-        ...
-    packaging.version.InvalidVersion: Invalid version: 'invalid'
-    """
+class InvalidVersion(ValueError): ...
 
 
 class _BaseVersion:
@@ -209,58 +191,13 @@ def __ne__(self, other: object) -> bool:
 """
 
 VERSION_PATTERN = _VERSION_PATTERN
-"""
-A string containing the regular expression used to match a valid version.
-
-The pattern is not anchored at either end, and is intended for embedding in larger
-expressions (for example, matching a version number as part of a file name). The
-regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
-flags set.
-
-:meta hide-value:
-"""
 
 
 class Version(_BaseVersion):
-    """
-    This class abstracts handling of a project's versions.
-
-    A :class:`Version` instance is comparison aware and can be compared and
-    sorted using the standard Python interfaces.
-
-    >>> v1 = Version("1.0a5")
-    >>> v2 = Version("1.0")
-    >>> v1
-    
-    >>> v2
-    
-    >>> v1 < v2
-    True
-    >>> v1 == v2
-    False
-    >>> v1 > v2
-    False
-    >>> v1 >= v2
-    False
-    >>> v1 <= v2
-    True
-    """
-
     _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
     _key: CmpKey
 
     def __init__(self, version: str) -> None:
-        """
-        Initialize a Version object.
-
-        :param version:
-            The string representation of a version which will be parsed and normalized
-            before use.
-        :raises InvalidVersion:
-            If the ``version`` does not conform to PEP 440 in any way then this
-            exception will be raised.
-        """
-
         # Validate the version and parse it into pieces
         match = self._regex.search(version)
         if not match:
@@ -289,21 +226,9 @@ def __init__(self, version: str) -> None:
         )
 
     def __repr__(self) -> str:
-        """
-        A representation of the Version that shows all internal state.
-
-        >>> Version("1.0.0")
-        
-        """
         return f""
 
     def __str__(self) -> str:
-        """
-        A string representation of the version that can be round-tripped.
-
-        >>> str(Version("1.0a5"))
-        '1.0a5'
-        """
         parts = []
 
         # Epoch
@@ -333,83 +258,26 @@ def __str__(self) -> str:
 
     @property
     def epoch(self) -> int:
-        """
-        The epoch of the version.
-
-        >>> Version("2.0.0").epoch
-        0
-        >>> Version("1!2.0.0").epoch
-        1
-        """
         return self._version.epoch
 
     @property
     def release(self) -> tuple[int, ...]:
-        """
-        The components of the "release" segment of the version.
-
-        >>> Version("1.2.3").release
-        (1, 2, 3)
-        >>> Version("2.0.0").release
-        (2, 0, 0)
-        >>> Version("1!2.0.0.post0").release
-        (2, 0, 0)
-
-        Includes trailing zeroes but not the epoch or any pre-release / development /
-        post-release suffixes.
-        """
         return self._version.release
 
     @property
     def pre(self) -> tuple[str, int] | None:
-        """
-        The pre-release segment of the version.
-
-        >>> print(Version("1.2.3").pre)
-        None
-        >>> Version("1.2.3a1").pre
-        ('a', 1)
-        >>> Version("1.2.3b1").pre
-        ('b', 1)
-        >>> Version("1.2.3rc1").pre
-        ('rc', 1)
-        """
         return self._version.pre
 
     @property
     def post(self) -> int | None:
-        """
-        The post-release number of the version.
-
-        >>> print(Version("1.2.3").post)
-        None
-        >>> Version("1.2.3.post1").post
-        1
-        """
         return self._version.post[1] if self._version.post else None
 
     @property
     def dev(self) -> int | None:
-        """
-        The development number of the version.
-
-        >>> print(Version("1.2.3").dev)
-        None
-        >>> Version("1.2.3.dev1").dev
-        1
-        """
         return self._version.dev[1] if self._version.dev else None
 
     @property
     def local(self) -> str | None:
-        """
-        The local version segment of the version.
-
-        >>> print(Version("1.2.3").local)
-        None
-        >>> Version("1.2.3+abc").local
-        'abc'
-        """
         if self._version.local:
             return ".".join(str(x) for x in self._version.local)
         else:
@@ -417,33 +285,10 @@ def local(self) -> str | None:
 
     @property
     def public(self) -> str:
-        """
-        The public portion of the version.
-
-        >>> Version("1.2.3").public
-        '1.2.3'
-        >>> Version("1.2.3+abc").public
-        '1.2.3'
-        >>> Version("1!1.2.3dev1+abc").public
-        '1!1.2.3.dev1'
-        """
         return str(self).split("+", 1)[0]
 
     @property
     def base_version(self) -> str:
-        """
-        The "base version" of the version.
-
-        >>> Version("1.2.3").base_version
-        '1.2.3'
-        >>> Version("1.2.3+abc").base_version
-        '1.2.3'
-        >>> Version("1!1.2.3dev1+abc").base_version
-        '1!1.2.3'
-
-        The "base version" is the public version of the project without any pre or post
-        release markers.
-        """
         parts = []
 
         # Epoch
@@ -457,78 +302,26 @@ def base_version(self) -> str:
 
     @property
     def is_prerelease(self) -> bool:
-        """
-        Whether this version is a pre-release.
-
-        >>> Version("1.2.3").is_prerelease
-        False
-        >>> Version("1.2.3a1").is_prerelease
-        True
-        >>> Version("1.2.3b1").is_prerelease
-        True
-        >>> Version("1.2.3rc1").is_prerelease
-        True
-        >>> Version("1.2.3dev1").is_prerelease
-        True
-        """
         return self.dev is not None or self.pre is not None
 
     @property
     def is_postrelease(self) -> bool:
-        """
-        Whether this version is a post-release.
-
-        >>> Version("1.2.3").is_postrelease
-        False
-        >>> Version("1.2.3.post1").is_postrelease
-        True
-        """
         return self.post is not None
 
     @property
     def is_devrelease(self) -> bool:
-        """
-        Whether this version is a development release.
-
-        >>> Version("1.2.3").is_devrelease
-        False
-        >>> Version("1.2.3.dev1").is_devrelease
-        True
-        """
         return self.dev is not None
 
     @property
     def major(self) -> int:
-        """
-        The first item of :attr:`release` or ``0`` if unavailable.
-
-        >>> Version("1.2.3").major
-        1
-        """
         return self.release[0] if len(self.release) >= 1 else 0
 
     @property
     def minor(self) -> int:
-        """
-        The second item of :attr:`release` or ``0`` if unavailable.
-
-        >>> Version("1.2.3").minor
-        2
-        >>> Version("1").minor
-        0
-        """
         return self.release[1] if len(self.release) >= 2 else 0
 
     @property
     def micro(self) -> int:
-        """
-        The third item of :attr:`release` or ``0`` if unavailable.
-
-        >>> Version("1.2.3").micro
-        3
-        >>> Version("1").micro
-        0
-        """
         return self.release[2] if len(self.release) >= 3 else 0
 
 
@@ -571,9 +364,6 @@ def _parse_letter_version(
 
 
 def _parse_local_version(local: str | None) -> LocalType | None:
-    """
-    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
-    """
     if local is not None:
         return tuple(
             part.lower() if not part.isdigit() else int(part)

From 561040f8f1e9a16fa346c5c1d2962c905989634e Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Fri, 23 Aug 2024 15:44:24 -0700
Subject: [PATCH 8/9] rollback

---
 pandas/util/version/__init__.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py
index 3e726417ab870..17dec7ef0c1a4 100644
--- a/pandas/util/version/__init__.py
+++ b/pandas/util/version/__init__.py
@@ -108,7 +108,16 @@ def parse(version: str) -> Version:
     return Version(version)
 
 
-class InvalidVersion(ValueError): ...
+class InvalidVersion(ValueError):
+    """
+    An invalid version was found, users should refer to PEP 440.
+
+    Examples
+    --------
+    >>> pd.util.version.Version("1.")
+    Traceback (most recent call last):
+    InvalidVersion: Invalid version: '1.'
+    """
 
 
 class _BaseVersion:

From 57ef67f758b12b010a95277e2b45e1d0bafb65c2 Mon Sep 17 00:00:00 2001
From: Fangchen Li 
Date: Fri, 23 Aug 2024 17:21:03 -0700
Subject: [PATCH 9/9] add comments

---
 pandas/util/version/__init__.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pandas/util/version/__init__.py b/pandas/util/version/__init__.py
index 17dec7ef0c1a4..b5d975a0db1d8 100644
--- a/pandas/util/version/__init__.py
+++ b/pandas/util/version/__init__.py
@@ -108,6 +108,8 @@ 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.