diff --git a/.bumpversion.toml b/.bumpversion.toml index 9a53bc766..29dcf9a2d 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,6 +1,6 @@ [tool.bumpversion] allow_dirty = true - current_version = "0.183.5" + current_version = "0.184.0" [[tool.bumpversion.files]] filename = "pyproject.toml" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9170852bc..b79fbdb80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/dycw/actions - rev: 0.15.5 + rev: 0.15.8 hooks: - args: - --ci--pull-request--pyright diff --git a/pyproject.toml b/pyproject.toml index ecf6c3693..e2c8c141f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ jupyter-test = ["pandas>=2.3.3", "polars>=1.37.1"] libcst = ["libcst>=1.8.6"] lightweight-charts = ["lightweight-charts>=2.1"] - lightweight-charts-test = ["polars>=1.37.1", "pyarrow>=22.0.0"] + lightweight-charts-test = ["polars>=1.37.1", "pyarrow>=23.0.0"] math-test = ["numpy>=2.4.1"] memory-profiler = ["memory-profiler>=0.61.0"] more-itertools = ["more-itertools>=10.8.0"] @@ -116,7 +116,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.183.5" + version = "0.184.0" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" diff --git a/src/tests/test_hypothesis.py b/src/tests/test_hypothesis.py index 162289f51..63532385d 100644 --- a/src/tests/test_hypothesis.py +++ b/src/tests/test_hypothesis.py @@ -118,7 +118,8 @@ uint32s, uint64s, urls, - versions, + version2s, + version3s, year_months, zone_infos, zoned_date_time_periods, @@ -127,7 +128,7 @@ from utilities.iterables import one from utilities.libcst import parse_import from utilities.platform import maybe_lower_case -from utilities.version import Version +from utilities.version import Version2, Version3 from utilities.whenever import ( DATE_TWO_DIGIT_YEAR_MAX, DATE_TWO_DIGIT_YEAR_MIN, @@ -1223,11 +1224,22 @@ def test_main( assert url.database is not None -class TestVersions: +class TestVersion2s: @given(data=data(), suffix=booleans()) def test_main(self, *, data: DataObject, suffix: bool) -> None: - version = data.draw(versions(suffix=suffix)) - assert isinstance(version, Version) + version = data.draw(version2s(suffix=suffix)) + assert isinstance(version, Version2) + if suffix: + assert version.suffix is not None + else: + assert version.suffix is None + + +class TestVersion3s: + @given(data=data(), suffix=booleans()) + def test_main(self, *, data: DataObject, suffix: bool) -> None: + version = data.draw(version3s(suffix=suffix)) + assert isinstance(version, Version3) if suffix: assert version.suffix is not None else: diff --git a/src/tests/test_objects/objects.py b/src/tests/test_objects/objects.py index f0d553d4b..bea3bba66 100644 --- a/src/tests/test_objects/objects.py +++ b/src/tests/test_objects/objects.py @@ -51,7 +51,8 @@ time_deltas, time_periods, times, - versions, + version2s, + version3s, year_months, zoned_date_time_periods, zoned_date_times, @@ -101,7 +102,8 @@ def objects( | times() | hypothesis.strategies.times() | uuids() - | versions() + | version2s() + | version3s() | year_months() | zoned_date_time_periods() | zoned_date_times() diff --git a/src/tests/test_parse.py b/src/tests/test_parse.py index 32e519b32..a997c32a1 100644 --- a/src/tests/test_parse.py +++ b/src/tests/test_parse.py @@ -55,7 +55,8 @@ text_ascii, time_deltas, times, - versions, + version2s, + version3s, year_months, zoned_date_times, ) @@ -70,7 +71,7 @@ ) from utilities.text import parse_bool from utilities.types import Number -from utilities.version import Version +from utilities.version import Version2, Version3 class TestSerializeAndParseObject: @@ -312,10 +313,16 @@ def parser(text: str, /) -> DataClassFutureIntEvenOrOddUnion: raise ImpossibleCaseError(case=[f"{int_=}"]) assert result == expected - @given(version=versions()) - def test_version(self, *, version: Version) -> None: + @given(version=version2s()) + def test_version2(self, *, version: Version2) -> None: serialized = serialize_object(version) - result = parse_object(Version, serialized) + result = parse_object(Version2, serialized) + assert result == version + + @given(version=version3s()) + def test_version3(self, *, version: Version3) -> None: + serialized = serialize_object(version) + result = parse_object(Version3, serialized) assert result == version @given(year_month=year_months()) @@ -631,12 +638,19 @@ def test_error_union_not_implemented(self) -> None: ): _ = parse_object(DataClassFutureIntEvenOrOddUnion, "invalid") - def test_error_version(self) -> None: + def test_error_version2(self) -> None: + with raises( + _ParseObjectParseError, + match=r"Unable to parse ; got 'invalid'", + ): + _ = parse_object(Version2, "invalid") + + def test_error_version3(self) -> None: with raises( _ParseObjectParseError, - match=r"Unable to parse ; got 'invalid'", + match=r"Unable to parse ; got 'invalid'", ): - _ = parse_object(Version, "invalid") + _ = parse_object(Version3, "invalid") def test_error_year_month(self) -> None: with raises( diff --git a/src/tests/test_polars.py b/src/tests/test_polars.py index 3eecf37cf..541cd9934 100644 --- a/src/tests/test_polars.py +++ b/src/tests/test_polars.py @@ -2297,7 +2297,7 @@ class TestNormalPDF: @given( xs=lists(float64s(), max_size=10), loc=float64s(), - scale=float64s(min_value=0.0, exclude_min=True), + scale=float64s(min_value=1e-8), ) def test_main(self, *, xs: list[float], loc: float, scale: float) -> None: x = Series(name="x", values=xs, dtype=Float64) diff --git a/src/tests/test_version.py b/src/tests/test_version.py index fffa59751..07d280419 100644 --- a/src/tests/test_version.py +++ b/src/tests/test_version.py @@ -7,50 +7,43 @@ from hypothesis.strategies import booleans, integers, none from pytest import raises -from utilities.hypothesis import sentinels, text_ascii, versions +from utilities.hypothesis import sentinels, text_ascii, version3s from utilities.version import ( - ParseVersionError, - Version, - _VersionEmptySuffixError, - _VersionNegativeMajorVersionError, - _VersionNegativeMinorVersionError, - _VersionNegativePatchVersionError, - _VersionZeroError, - parse_version, - to_version, + Version3, + _Version3EmptySuffixError, + _Version3NegativeMajorVersionError, + _Version3NegativeMinorVersionError, + _Version3NegativePatchVersionError, + _Version3ParseError, + _Version3ZeroError, + to_version3, ) if TYPE_CHECKING: from utilities.constants import Sentinel -class TestParseVersion: - @given(version=versions()) - def test_main(self, *, version: Version) -> None: - parsed = parse_version(str(version)) - assert parsed == version - - def test_error(self) -> None: - with raises(ParseVersionError, match=r"Invalid version string: 'invalid'"): - _ = parse_version("invalid") - - class TestVersion: - @given(version=versions()) - def test_hashable(self, *, version: Version) -> None: + @given(version=version3s()) + def test_hashable(self, *, version: Version3) -> None: _ = hash(version) - @given(version1=versions(), version2=versions()) - def test_orderable(self, *, version1: Version, version2: Version) -> None: + @given(version1=version3s(), version2=version3s()) + def test_orderable(self, *, version1: Version3, version2: Version3) -> None: assert (version1 <= version2) or (version1 >= version2) - @given(version=versions(suffix=booleans())) - def test_repr(self, *, version: Version) -> None: + @given(version=version3s()) + def test_parse(self, *, version: Version3) -> None: + parsed = Version3.parse(str(version)) + assert parsed == version + + @given(version=version3s(suffix=booleans())) + def test_repr(self, *, version: Version3) -> None: result = repr(version) assert search(r"^\d+\.\d+\.\d+", result) - @given(version=versions()) - def test_bump_major(self, *, version: Version) -> None: + @given(version=version3s()) + def test_bump_major(self, *, version: Version3) -> None: bumped = version.bump_major() assert version < bumped assert bumped.major == version.major + 1 @@ -58,8 +51,8 @@ def test_bump_major(self, *, version: Version) -> None: assert bumped.patch == 0 assert bumped.suffix is None - @given(version=versions()) - def test_bump_minor(self, *, version: Version) -> None: + @given(version=version3s()) + def test_bump_minor(self, *, version: Version3) -> None: bumped = version.bump_minor() assert version < bumped assert bumped.major == version.major @@ -67,8 +60,8 @@ def test_bump_minor(self, *, version: Version) -> None: assert bumped.patch == 0 assert bumped.suffix is None - @given(version=versions()) - def test_bump_patch(self, *, version: Version) -> None: + @given(version=version3s()) + def test_bump_patch(self, *, version: Version3) -> None: bumped = version.bump_patch() assert version < bumped assert bumped.major == version.major @@ -76,69 +69,75 @@ def test_bump_patch(self, *, version: Version) -> None: assert bumped.patch == version.patch + 1 assert bumped.suffix is None - @given(version=versions(), suffix=text_ascii(min_size=1) | none()) - def test_with_suffix(self, *, version: Version, suffix: str | None) -> None: + @given(version=version3s(), suffix=text_ascii(min_size=1) | none()) + def test_with_suffix(self, *, version: Version3, suffix: str | None) -> None: new = version.with_suffix(suffix=suffix) assert new.major == version.major assert new.minor == version.minor assert new.patch == version.patch assert new.suffix == suffix - @given(version=versions()) - def test_error_order(self, *, version: Version) -> None: + @given(version=version3s()) + def test_error_order(self, *, version: Version3) -> None: with raises(TypeError): _ = version <= None def test_error_zero(self) -> None: with raises( - _VersionZeroError, match=r"Version must be greater than zero; got 0\.0\.0" + _Version3ZeroError, match=r"Version must be greater than zero; got 0\.0\.0" ): - _ = Version(0, 0, 0) + _ = Version3(0, 0, 0) @given(major=integers(max_value=-1)) def test_error_negative_major_version(self, *, major: int) -> None: with raises( - _VersionNegativeMajorVersionError, + _Version3NegativeMajorVersionError, match=r"Major version must be non-negative; got .*", ): - _ = Version(major=major) + _ = Version3(major=major) @given(minor=integers(max_value=-1)) def test_error_negative_minor_version(self, *, minor: int) -> None: with raises( - _VersionNegativeMinorVersionError, + _Version3NegativeMinorVersionError, match=r"Minor version must be non-negative; got .*", ): - _ = Version(minor=minor) + _ = Version3(minor=minor) @given(patch=integers(max_value=-1)) def test_error_negative_patch_version(self, *, patch: int) -> None: with raises( - _VersionNegativePatchVersionError, + _Version3NegativePatchVersionError, match=r"Patch version must be non-negative; got .*", ): - _ = Version(patch=patch) + _ = Version3(patch=patch) def test_error_empty_suffix(self) -> None: with raises( - _VersionEmptySuffixError, match=r"Suffix must be non-empty; got .*" + _Version3EmptySuffixError, match=r"Suffix must be non-empty; got .*" + ): + _ = Version3(suffix="") + + def test_error_parse(self) -> None: + with raises( + _Version3ParseError, match=r"Unable to parse version; got 'invalid'" ): - _ = Version(suffix="") + _ = Version3.parse("invalid") -class TestGetVersion: - @given(version=versions()) - def test_version(self, *, version: Version) -> None: - assert to_version(version) == version +class TestToVersion3: + @given(version=version3s()) + def test_version(self, *, version: Version3) -> None: + assert to_version3(version) == version - @given(version=versions()) - def test_str(self, *, version: Version) -> None: - assert to_version(str(version)) == version + @given(version=version3s()) + def test_str(self, *, version: Version3) -> None: + assert to_version3(str(version)) == version @given(version=none() | sentinels()) def test_none_or_sentinel(self, *, version: None | Sentinel) -> None: - assert to_version(version) is version + assert to_version3(version) is version - @given(version=versions()) - def test_callable(self, *, version: Version) -> None: - assert to_version(lambda: version) == version + @given(version=version3s()) + def test_callable(self, *, version: Version3) -> None: + assert to_version3(lambda: version) == version diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index b74045cbb..8278ef256 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.183.5" +__version__ = "0.184.0" diff --git a/src/utilities/constants.py b/src/utilities/constants.py index 0bb8ac8f5..6c149aeb9 100644 --- a/src/utilities/constants.py +++ b/src/utilities/constants.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from dataclasses import dataclass from getpass import getuser from logging import getLogger @@ -7,7 +8,7 @@ from pathlib import Path from platform import system from random import SystemRandom -from re import IGNORECASE, search +from re import IGNORECASE from socket import gethostname from tempfile import gettempdir from typing import TYPE_CHECKING, Any, assert_never, cast, override @@ -226,6 +227,7 @@ def __call__(cls, *args: Any, **kwargs: Any) -> Any: return cls.instance +_SENTINEL_PATTERN = re.compile("^(|sentinel|)$", flags=IGNORECASE) _SENTINEL_REPR = "" @@ -242,8 +244,8 @@ def __str__(self) -> str: @classmethod def parse(cls, text: str, /) -> Sentinel: - """Parse text into the Sentinel value.""" - if search("^(|sentinel|)$", text, flags=IGNORECASE): + """Parse a string into the Sentinel value.""" + if _SENTINEL_PATTERN.search(text): return sentinel raise SentinelParseError(text=text) diff --git a/src/utilities/hypothesis.py b/src/utilities/hypothesis.py index b53373111..2069505cf 100644 --- a/src/utilities/hypothesis.py +++ b/src/utilities/hypothesis.py @@ -97,7 +97,7 @@ from utilities.pathlib import module_path, temp_cwd from utilities.permissions import Permissions from utilities.tempfile import TemporaryDirectory -from utilities.version import Version +from utilities.version import Version2, Version3 from utilities.whenever import ( DATE_DELTA_PARSABLE_MAX, DATE_DELTA_PARSABLE_MIN, @@ -1493,12 +1493,25 @@ def urls( @composite -def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version: - """Strategy for generating versions.""" +def version2s( + draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False +) -> Version2: + """Strategy for generating Version2 objects.""" + major, minor = draw(pairs(integers(min_value=0))) + _ = assume((major >= 1) or (minor >= 1)) + suffix_use = draw(text_ascii(min_size=1)) if draw2(draw, suffix) else None + return Version2(major=major, minor=minor, suffix=suffix_use) + + +@composite +def version3s( + draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False +) -> Version3: + """Strategy for generating Version3 objects.""" major, minor, patch = draw(triples(integers(min_value=0))) _ = assume((major >= 1) or (minor >= 1) or (patch >= 1)) suffix_use = draw(text_ascii(min_size=1)) if draw2(draw, suffix) else None - return Version(major=major, minor=minor, patch=patch, suffix=suffix_use) + return Version3(major=major, minor=minor, patch=patch, suffix=suffix_use) ## @@ -1680,7 +1693,8 @@ def zoned_date_times( "uint32s", "uint64s", "urls", - "versions", + "version2s", + "version3s", "year_months", "zone_infos", "zoned_date_time_periods", diff --git a/src/utilities/orjson.py b/src/utilities/orjson.py index 2e1251ab6..2b8610b45 100644 --- a/src/utilities/orjson.py +++ b/src/utilities/orjson.py @@ -52,7 +52,7 @@ from utilities.logging import get_logging_level_number from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping from utilities.typing import is_str_mapping -from utilities.version import Version, parse_version +from utilities.version import Version2, Version3 from utilities.whenever import ( DatePeriod, TimePeriod, @@ -98,7 +98,8 @@ class _Prefixes(StrEnum): tuple_ = "tu" unserializable = "un" uuid = "uu" - version = "v" + version2 = "v2" + version3 = "v3" year_month = "ym" zoned_date_time = "zd" zoned_date_time_period = "zp" @@ -212,8 +213,10 @@ def _pre_process( return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]" case UUID() as uuid: return f"[{_Prefixes.uuid.value}]{uuid}" - case Version() as version: - return f"[{_Prefixes.version.value}]{version}" + case Version2() as version: + return f"[{_Prefixes.version2.value}]{version}" + case Version3() as version: + return f"[{_Prefixes.version3.value}]{version}" case YearMonth() as year_month: return f"[{_Prefixes.year_month.value}]{year_month}" case ZonedDateTime() as date_time: @@ -408,7 +411,8 @@ class DeerializeError(Exception): _TIME_DELTA_PATTERN, _TIME_PERIOD_PATTERN, _UUID_PATTERN, - _VERSION_PATTERN, + _VERSION2_PATTERN, + _VERSION3_PATTERN, _YEAR_MONTH_PATTERN, _ZONED_DATE_TIME_PATTERN, _ZONED_DATE_TIME_PERIOD_PATTERN, @@ -432,7 +436,8 @@ class DeerializeError(Exception): _Prefixes.time_delta, _Prefixes.time_period, _Prefixes.uuid, - _Prefixes.version, + _Prefixes.version2, + _Prefixes.version3, _Prefixes.year_month, _Prefixes.zoned_date_time, _Prefixes.zoned_date_time_period, @@ -513,8 +518,10 @@ def _object_hook( return TimePeriod(start, end) if match := _UUID_PATTERN.search(text): return UUID(match.group(1)) - if match := _VERSION_PATTERN.search(text): - return parse_version(match.group(1)) + if match := _VERSION2_PATTERN.search(text): + return Version2.parse(match.group(1)) + if match := _VERSION3_PATTERN.search(text): + return Version3.parse(match.group(1)) if match := _YEAR_MONTH_PATTERN.search(text): return YearMonth.parse_iso(match.group(1)) if match := _ZONED_DATE_TIME_PATTERN.search(text): diff --git a/src/utilities/parse.py b/src/utilities/parse.py index 120342ca1..f92248893 100644 --- a/src/utilities/parse.py +++ b/src/utilities/parse.py @@ -55,7 +55,12 @@ is_tuple_type, is_union_type, ) -from utilities.version import ParseVersionError, Version, parse_version +from utilities.version import ( + Version2, + Version3, + _Version2ParseError, + _Version3ParseError, +) if TYPE_CHECKING: from collections.abc import Iterable, Mapping, Sequence @@ -217,10 +222,15 @@ def _parse_object_type( return Sentinel.parse(text) except SentinelParseError: raise _ParseObjectParseError(type_=cls, text=text) from None - if issubclass(cls, Version): + if issubclass(cls, Version2): + try: + return Version2.parse(text) + except _Version2ParseError: + raise _ParseObjectParseError(type_=cls, text=text) from None + if issubclass(cls, Version3): try: - return parse_version(text) - except ParseVersionError: + return Version3.parse(text) + except _Version3ParseError: raise _ParseObjectParseError(type_=cls, text=text) from None raise _ParseObjectParseError(type_=cls, text=text) @@ -463,7 +473,8 @@ def serialize_object( | IPv6Address | Path | Sentinel - | Version, + | Version2 + | Version3, ): return str(obj) if isinstance( diff --git a/src/utilities/traceback.py b/src/utilities/traceback.py index c3b7af1e8..60a4d710e 100644 --- a/src/utilities/traceback.py +++ b/src/utilities/traceback.py @@ -28,7 +28,7 @@ from utilities.pathlib import module_path, to_path from utilities.reprlib import yield_mapping_repr from utilities.text import to_bool -from utilities.version import to_version +from utilities.version import to_version3 from utilities.whenever import ( format_compact, get_now, @@ -48,7 +48,7 @@ MaybeCallableZonedDateTimeLike, PathLike, ) - from utilities.version import MaybeCallableVersionLike + from utilities.version import MaybeCallableVersion3Like ## @@ -60,7 +60,7 @@ def format_exception_stack( *, header: bool = False, start: MaybeCallableZonedDateTimeLike = get_now, - version: MaybeCallableVersionLike | None = None, + version: MaybeCallableVersion3Like | None = None, capture_locals: bool = False, max_width: int = RICH_MAX_WIDTH, indent_size: int = RICH_INDENT_SIZE, @@ -91,7 +91,7 @@ def format_exception_stack( def _yield_header_lines( *, start: MaybeCallableZonedDateTimeLike = get_now, - version: MaybeCallableVersionLike | None = None, + version: MaybeCallableVersion3Like | None = None, ) -> Iterator[str]: """Yield the header lines.""" now = get_now_local() @@ -102,7 +102,7 @@ def _yield_header_lines( yield f"User | {getuser()}" yield f"Host | {gethostname()}" yield f"Process ID | {getpid()}" - version_use = "" if version is None else to_version(version) + version_use = "" if version is None else to_version3(version) yield f"Version | {version_use}" yield "" @@ -197,7 +197,7 @@ def _trim_path(path: PathLike, pattern: str, /) -> Path | None: def make_except_hook( *, start: MaybeCallableZonedDateTimeLike = get_now, - version: MaybeCallableVersionLike | None = None, + version: MaybeCallableVersion3Like | None = None, path: MaybeCallablePathLike | None = None, path_max_age: Delta | None = None, max_width: int = RICH_MAX_WIDTH, @@ -236,7 +236,7 @@ def _make_except_hook_inner( /, *, start: MaybeCallableZonedDateTimeLike = get_now, - version: MaybeCallableVersionLike | None = None, + version: MaybeCallableVersion3Like | None = None, path: MaybeCallablePathLike | None = None, path_max_age: Delta | None = None, max_width: int = RICH_MAX_WIDTH, diff --git a/src/utilities/version.py b/src/utilities/version.py index 5afc319b7..a34c8061e 100644 --- a/src/utilities/version.py +++ b/src/utilities/version.py @@ -9,16 +9,139 @@ from utilities.constants import Sentinel from utilities.types import MaybeCallable, MaybeStr -type VersionLike = MaybeStr[Version] -type MaybeCallableVersionLike = MaybeCallable[VersionLike] +type Version2Like = MaybeStr[Version2] +type Version3Like = MaybeStr[Version3] +type MaybeCallableVersion3Like = MaybeCallable[Version3Like] ## +_PARSE_VERSION2_PATTERN = re.compile(r"^(\d+)\.(\d+)(?:-(\w+))?") + + +@dataclass(repr=False, frozen=True, slots=True) +@total_ordering +class Version2: + """A version identifier.""" + + major: int = 0 + minor: int = 0 + suffix: str | None = field(default=None, kw_only=True) + + def __post_init__(self) -> None: + if (self.major == 0) and (self.minor == 0): + raise _Version2ZeroError(major=self.major, minor=self.minor) + if self.major < 0: + raise _Version2NegativeMajorVersionError(major=self.major) + if self.minor < 0: + raise _Version2NegativeMinorVersionError(minor=self.minor) + if (self.suffix is not None) and (len(self.suffix) == 0): + raise _Version2EmptySuffixError(suffix=self.suffix) + + def __le__(self, other: Any, /) -> bool: + if not isinstance(other, type(self)): + return NotImplemented + self_as_tuple = ( + self.major, + self.minor, + "" if self.suffix is None else self.suffix, + ) + other_as_tuple = ( + other.major, + other.minor, + "" if other.suffix is None else other.suffix, + ) + return self_as_tuple <= other_as_tuple + + @override + def __repr__(self) -> str: + version = f"{self.major}.{self.minor}" + if self.suffix is not None: + version = f"{version}-{self.suffix}" + return version + + @classmethod + def parse(cls, text: str, /) -> Self: + """Parse a string into a Version2 object.""" + try: + ((major, minor, suffix),) = _PARSE_VERSION2_PATTERN.findall(text) + except ValueError: + raise _Version2ParseError(text=text) from None + return cls(int(major), int(minor), suffix=None if suffix == "" else suffix) + + def bump_major(self) -> Self: + """Bump the major component.""" + return type(self)(self.major + 1, 0) + + def bump_minor(self) -> Self: + """Bump the minor component.""" + return type(self)(self.major, self.minor + 1) + + def with_suffix(self, *, suffix: str | None = None) -> Self: + """Replace the suffix.""" + return replace(self, suffix=suffix) + + +@dataclass(kw_only=True, slots=True) +class Version2Error(Exception): ... + + +@dataclass(kw_only=True, slots=True) +class _Version2ZeroError(Version2Error): + major: int + minor: int + + @override + def __str__(self) -> str: + return f"Version must be greater than zero; got {self.major}.{self.minor}" + + +@dataclass(kw_only=True, slots=True) +class _Version2NegativeMajorVersionError(Version2Error): + major: int + + @override + def __str__(self) -> str: + return f"Major version must be non-negative; got {self.major}" + + +@dataclass(kw_only=True, slots=True) +class _Version2NegativeMinorVersionError(Version2Error): + minor: int + + @override + def __str__(self) -> str: + return f"Minor version must be non-negative; got {self.minor}" + + +@dataclass(kw_only=True, slots=True) +class _Version2EmptySuffixError(Version2Error): + suffix: str + + @override + def __str__(self) -> str: + return f"Suffix must be non-empty; got {self.suffix!r}" + + +@dataclass(kw_only=True, slots=True) +class _Version2ParseError(Version2Error): + text: str + + @override + def __str__(self) -> str: + return f"Unable to parse version; got {self.text!r}" + + +## + + +_PARSE_VERSION3_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?") + + @dataclass(repr=False, frozen=True, slots=True) @total_ordering -class Version: +class Version3: """A version identifier.""" major: int = 0 @@ -28,17 +151,17 @@ class Version: def __post_init__(self) -> None: if (self.major == 0) and (self.minor == 0) and (self.patch == 0): - raise _VersionZeroError( + raise _Version3ZeroError( major=self.major, minor=self.minor, patch=self.patch ) if self.major < 0: - raise _VersionNegativeMajorVersionError(major=self.major) + raise _Version3NegativeMajorVersionError(major=self.major) if self.minor < 0: - raise _VersionNegativeMinorVersionError(minor=self.minor) + raise _Version3NegativeMinorVersionError(minor=self.minor) if self.patch < 0: - raise _VersionNegativePatchVersionError(patch=self.patch) + raise _Version3NegativePatchVersionError(patch=self.patch) if (self.suffix is not None) and (len(self.suffix) == 0): - raise _VersionEmptySuffixError(suffix=self.suffix) + raise _Version3EmptySuffixError(suffix=self.suffix) def __le__(self, other: Any, /) -> bool: if not isinstance(other, type(self)): @@ -64,25 +187,40 @@ def __repr__(self) -> str: version = f"{version}-{self.suffix}" return version + @classmethod + def parse(cls, text: str, /) -> Self: + """Parse a string into a Version3 object.""" + try: + ((major, minor, patch, suffix),) = _PARSE_VERSION3_PATTERN.findall(text) + except ValueError: + raise _Version3ParseError(text=text) from None + return cls( + int(major), int(minor), int(patch), suffix=None if suffix == "" else suffix + ) + def bump_major(self) -> Self: + """Bump the major component.""" return type(self)(self.major + 1, 0, 0) def bump_minor(self) -> Self: + """Bump the minor component.""" return type(self)(self.major, self.minor + 1, 0) def bump_patch(self) -> Self: + """Bump the patch component.""" return type(self)(self.major, self.minor, self.patch + 1) def with_suffix(self, *, suffix: str | None = None) -> Self: + """Replace the suffix.""" return replace(self, suffix=suffix) @dataclass(kw_only=True, slots=True) -class VersionError(Exception): ... +class Version3Error(Exception): ... @dataclass(kw_only=True, slots=True) -class _VersionZeroError(VersionError): +class _Version3ZeroError(Version3Error): major: int minor: int patch: int @@ -93,7 +231,7 @@ def __str__(self) -> str: @dataclass(kw_only=True, slots=True) -class _VersionNegativeMajorVersionError(VersionError): +class _Version3NegativeMajorVersionError(Version3Error): major: int @override @@ -102,7 +240,7 @@ def __str__(self) -> str: @dataclass(kw_only=True, slots=True) -class _VersionNegativeMinorVersionError(VersionError): +class _Version3NegativeMinorVersionError(Version3Error): minor: int @override @@ -111,7 +249,7 @@ def __str__(self) -> str: @dataclass(kw_only=True, slots=True) -class _VersionNegativePatchVersionError(VersionError): +class _Version3NegativePatchVersionError(Version3Error): patch: int @override @@ -120,7 +258,7 @@ def __str__(self) -> str: @dataclass(kw_only=True, slots=True) -class _VersionEmptySuffixError(VersionError): +class _Version3EmptySuffixError(Version3Error): suffix: str @override @@ -128,63 +266,47 @@ def __str__(self) -> str: return f"Suffix must be non-empty; got {self.suffix!r}" -## - - -def parse_version(version: str, /) -> Version: - """Parse a string into a version object.""" - try: - ((major, minor, patch, suffix),) = _PARSE_VERSION_PATTERN.findall(version) - except ValueError: - raise ParseVersionError(version=version) from None - return Version( - int(major), int(minor), int(patch), suffix=None if suffix == "" else suffix - ) - - -_PARSE_VERSION_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?") - - @dataclass(kw_only=True, slots=True) -class ParseVersionError(Exception): - version: str +class _Version3ParseError(Version3Error): + text: str @override def __str__(self) -> str: - return f"Invalid version string: {self.version!r}" + return f"Unable to parse version; got {self.text!r}" ## @overload -def to_version(version: MaybeCallableVersionLike, /) -> Version: ... +def to_version3(version: MaybeCallableVersion3Like, /) -> Version3: ... @overload -def to_version(version: None, /) -> None: ... +def to_version3(version: None, /) -> None: ... @overload -def to_version(version: Sentinel, /) -> Sentinel: ... -def to_version( - version: MaybeCallableVersionLike | None | Sentinel, / -) -> Version | None | Sentinel: +def to_version3(version: Sentinel, /) -> Sentinel: ... +def to_version3( + version: MaybeCallableVersion3Like | None | Sentinel, / +) -> Version3 | None | Sentinel: """Convert to a version.""" match version: - case Version() | None | Sentinel(): + case Version3() | None | Sentinel(): return version case str(): - return parse_version(version) + return Version3.parse(version) case Callable() as func: - return to_version(func()) + return to_version3(func()) case never: assert_never(never) ## __all__ = [ - "MaybeCallableVersionLike", - "ParseVersionError", - "Version", - "VersionError", - "VersionLike", - "parse_version", - "to_version", + "MaybeCallableVersion3Like", + "Version2", + "Version2Error", + "Version2Like", + "Version3", + "Version3Error", + "Version3Like", + "to_version3", ] diff --git a/uv.lock b/uv.lock index aff2becfd..f48ae994e 100644 --- a/uv.lock +++ b/uv.lock @@ -625,7 +625,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.183.5" +version = "0.184.0" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, @@ -976,7 +976,7 @@ libcst = [{ name = "libcst", specifier = ">=1.8.6" }] lightweight-charts = [{ name = "lightweight-charts", specifier = ">=2.1" }] lightweight-charts-test = [ { name = "polars", specifier = ">=1.37.1" }, - { name = "pyarrow", specifier = ">=22.0.0" }, + { name = "pyarrow", specifier = ">=23.0.0" }, ] math-test = [{ name = "numpy", specifier = ">=2.4.1" }] memory-profiler = [{ name = "memory-profiler", specifier = ">=0.61.0" }] @@ -2543,45 +2543,45 @@ wheels = [ [[package]] name = "pyarrow" -version = "22.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, - { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, - { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, - { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", size = 34204629, upload-time = "2025-10-24T10:06:20.274Z" }, - { url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", size = 35985783, upload-time = "2025-10-24T10:06:27.301Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", size = 45020999, upload-time = "2025-10-24T10:06:35.387Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", size = 47724601, upload-time = "2025-10-24T10:06:43.551Z" }, - { url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", size = 48001050, upload-time = "2025-10-24T10:06:52.284Z" }, - { url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", size = 50307877, upload-time = "2025-10-24T10:07:02.405Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", size = 27977099, upload-time = "2025-10-24T10:08:07.259Z" }, - { url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", size = 34336685, upload-time = "2025-10-24T10:07:11.47Z" }, - { url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", size = 36032158, upload-time = "2025-10-24T10:07:18.626Z" }, - { url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", size = 44892060, upload-time = "2025-10-24T10:07:26.002Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", size = 47504395, upload-time = "2025-10-24T10:07:34.09Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, - { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, - { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b0/0fa4d28a8edb42b0a7144edd20befd04173ac79819547216f8a9f36f9e50/pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d", size = 34224062, upload-time = "2025-10-24T10:08:14.101Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/7a719076b3c1be0acef56a07220c586f25cd24de0e3f3102b438d18ae5df/pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9", size = 35990057, upload-time = "2025-10-24T10:08:21.842Z" }, - { url = "https://files.pythonhosted.org/packages/89/3c/359ed54c93b47fb6fe30ed16cdf50e3f0e8b9ccfb11b86218c3619ae50a8/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7", size = 45068002, upload-time = "2025-10-24T10:08:29.034Z" }, - { url = "https://files.pythonhosted.org/packages/55/fc/4945896cc8638536ee787a3bd6ce7cec8ec9acf452d78ec39ab328efa0a1/pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde", size = 47737765, upload-time = "2025-10-24T10:08:38.559Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5e/7cb7edeb2abfaa1f79b5d5eb89432356155c8426f75d3753cbcb9592c0fd/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc", size = 48048139, upload-time = "2025-10-24T10:08:46.784Z" }, - { url = "https://files.pythonhosted.org/packages/88/c6/546baa7c48185f5e9d6e59277c4b19f30f48c94d9dd938c2a80d4d6b067c/pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0", size = 50314244, upload-time = "2025-10-24T10:08:55.771Z" }, - { url = "https://files.pythonhosted.org/packages/3c/79/755ff2d145aafec8d347bf18f95e4e81c00127f06d080135dfc86aea417c/pyarrow-22.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730", size = 28757501, upload-time = "2025-10-24T10:09:59.891Z" }, - { url = "https://files.pythonhosted.org/packages/0e/d2/237d75ac28ced3147912954e3c1a174df43a95f4f88e467809118a8165e0/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2", size = 34355506, upload-time = "2025-10-24T10:09:02.953Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/733dfffe6d3069740f98e57ff81007809067d68626c5faef293434d11bd6/pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70", size = 36047312, upload-time = "2025-10-24T10:09:10.334Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2b/29d6e3782dc1f299727462c1543af357a0f2c1d3c160ce199950d9ca51eb/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754", size = 45081609, upload-time = "2025-10-24T10:09:18.61Z" }, - { url = "https://files.pythonhosted.org/packages/8d/42/aa9355ecc05997915af1b7b947a7f66c02dcaa927f3203b87871c114ba10/pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91", size = 47703663, upload-time = "2025-10-24T10:09:27.369Z" }, - { url = "https://files.pythonhosted.org/packages/ee/62/45abedde480168e83a1de005b7b7043fd553321c1e8c5a9a114425f64842/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c", size = 48066543, upload-time = "2025-10-24T10:09:34.908Z" }, - { url = "https://files.pythonhosted.org/packages/84/e9/7878940a5b072e4f3bf998770acafeae13b267f9893af5f6d4ab3904b67e/pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80", size = 50288838, upload-time = "2025-10-24T10:09:44.394Z" }, - { url = "https://files.pythonhosted.org/packages/7b/03/f335d6c52b4a4761bcc83499789a1e2e16d9d201a58c327a9b5cc9a41bd9/pyarrow-22.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae", size = 29185594, upload-time = "2025-10-24T10:09:53.111Z" }, +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, + { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, + { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, + { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, + { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, + { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, ] [[package]]