Package: src:pandas Version: 3.0.5+dfsg-1 User: [email protected] Usertags: python3.15 Tags: patch
Hi! While rebuilding the Python related packages against the Python 3.15rc1 version we found that pandas fails to build from source[1]. The upstream code already has fixes for these issues and since there are many packages that require pandas to build I already applied these fixes in the packages that are in the rebuild sandbox[2]. Please consider applying this patches in your next upload, or consider updating pandas when a new release including these patches is available. Happy hacking, [1]: https://debusine.debian.net/debian/r-python-python3.15/work-request/1012982/ [2]: https://debusine.debian.net/debian/r-python-python3.15/ -- "recursividad 95, 154, 156, 201, 224, 293" -- El Lenguaje de Programacion C, pag. 293 (Kernighan & Ritchie) Saludos /\/\ /\ >< `/
Description: Python 3.15 compatibility fixes Backport of upstream commit 7bb284ac43 (PR #66473) for Python 3.15 support: - Support optional %z and %:z (colon_z) in strptime - Explicitly close XML iterparse parser to avoid unclosed iterator ResourceWarning - Accept Python 3.15 TypeError / ValueError error message changes in tests - Define and export PY315 in pandas.compat Origin: upstream, https://github.com/pandas-dev/pandas/commit/7bb284ac438d2b981a7f42e3bdd0fe5ee26aada0 Bug: https://github.com/pandas-dev/pandas/issues/66473 Forwarded: not-needed Index: pandas/pandas/_libs/tslibs/strptime.pyx =================================================================== --- pandas.orig/pandas/_libs/tslibs/strptime.pyx +++ pandas/pandas/_libs/tslibs/strptime.pyx @@ -167,6 +167,7 @@ cdef dict _parse_code_table = {"y": 0, "Z": 17, "p": 18, # an additional key, only with I "z": 19, + "colon_z": 24, "G": 20, "V": 21, "u": 22} @@ -178,7 +179,7 @@ cdef _validate_fmt(str fmt): raise ValueError("Cannot use '%W' or '%U' without day and year") if "%A" not in fmt and "%a" not in fmt and "%w" not in fmt: raise ValueError("Cannot use '%W' or '%U' without day and year") - elif "%Z" in fmt and "%z" in fmt: + elif "%Z" in fmt and ("%z" in fmt or "%:z" in fmt): raise ValueError("Cannot parse both %Z and %z") elif "%j" in fmt and "%G" in fmt: raise ValueError("Day of the year directive '%j' is not " @@ -618,9 +619,19 @@ cdef tzinfo _parse_with_format( f"time data \"{val}\" doesn't match format \"{fmt}\"" ) if len(val) != found.end(): + rest = val[found.end():] + # Specific check for '%:z' directive + if ( + "colon_z" in found.re.groupindex + and found.group("colon_z") is not None + and rest[0] != ":" + ): + raise ValueError( + f"Missing colon in %:z before '{rest}', got '{val}'" + ) raise ValueError( "unconverted data remains when parsing with " - f"format \"{fmt}\": \"{val[found.end():]}\"" + f"format \"{fmt}\": \"{rest}\"" ) else: @@ -760,9 +771,15 @@ cdef tzinfo _parse_with_format( elif parse_code == 17: # e.g. val='2011-12-30T00:00:00.000000UTC'; fmt='%Y-%m-%dT%H:%M:%S.%f%Z' tz = zoneinfo.ZoneInfo(found_dict["Z"]) - elif parse_code == 19: + # elif group_key in ('z', 'colon_z'): + elif parse_code == 19 or parse_code == 24: # e.g. val='March 1, 2018 12:00:00+0400'; fmt='%B %d, %Y %H:%M:%S%z' - tz = parse_timezone_directive(found_dict["z"]) + if found_dict[group_key] is None: + raise ValueError( + f"time data \"{val}\" doesn't match format \"{fmt}\"" + ) + + tz = parse_timezone_directive(found_dict[group_key]) elif parse_code == 20: # e.g. val='2015-1-7'; fmt='%G-%V-%u' iso_year = int(found_dict["G"]) Index: pandas/pandas/compat/__init__.py =================================================================== --- pandas.orig/pandas/compat/__init__.py +++ pandas/pandas/compat/__init__.py @@ -21,6 +21,7 @@ from pandas.compat._constants import ( ISMUSL, PY312, PY314, + PY315, PYPY, WASM, ) @@ -161,6 +162,7 @@ __all__ = [ "ISMUSL", "PY312", "PY314", + "PY315", "PYARROW_INSTALLED", "PYARROW_MIN_VERSION", "PYPY", Index: pandas/pandas/compat/_constants.py =================================================================== --- pandas.orig/pandas/compat/_constants.py +++ pandas/pandas/compat/_constants.py @@ -15,6 +15,7 @@ IS64 = sys.maxsize > 2**32 PY312 = sys.version_info >= (3, 12) PY314 = sys.version_info >= (3, 14) +PY315 = sys.version_info >= (3, 15) PYPY = platform.python_implementation() == "PyPy" WASM = (sys.platform == "emscripten") or (platform.machine() in ["wasm32", "wasm64"]) ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "") @@ -30,6 +31,7 @@ __all__ = [ "ISMUSL", "PY312", "PY314", + "PY315", "PYPY", "WASM", ] Index: pandas/pandas/io/xml.py =================================================================== --- pandas.orig/pandas/io/xml.py +++ pandas/pandas/io/xml.py @@ -337,44 +337,53 @@ class _XMLFrameParser: set(self.iterparse[row_node]) ) - for event, elem in iterparse(self.path_or_buffer, events=("start", "end")): - curr_elem = elem.tag.split("}")[1] if "}" in elem.tag else elem.tag - - if event == "start": - if curr_elem == row_node: - row = {} - - if row is not None: - if self.names and iterparse_repeats: - for col, nm in zip( - self.iterparse[row_node], self.names, strict=True - ): - if curr_elem == col: - elem_val = elem.text if elem.text else None - if elem_val not in row.values() and nm not in row: - row[nm] = elem_val - - if col in elem.attrib: - if elem.attrib[col] not in row.values() and nm not in row: - row[nm] = elem.attrib[col] - else: - for col in self.iterparse[row_node]: - if curr_elem == col: - row[col] = elem.text if elem.text else None - if col in elem.attrib: - row[col] = elem.attrib[col] - - if event == "end": - if curr_elem == row_node and row is not None: - dicts.append(row) - row = None - - elem.clear() - if hasattr(elem, "getprevious"): - while ( - elem.getprevious() is not None and elem.getparent() is not None - ): - del elem.getparent()[0] + parser = iterparse(self.path_or_buffer, events=("start", "end")) + try: + for event, elem in parser: + curr_elem = elem.tag.split("}")[1] if "}" in elem.tag else elem.tag + + if event == "start": + if curr_elem == row_node: + row = {} + + if row is not None: + if self.names and iterparse_repeats: + for col, nm in zip( + self.iterparse[row_node], self.names, strict=True + ): + if curr_elem == col: + elem_val = elem.text if elem.text else None + if elem_val not in row.values() and nm not in row: + row[nm] = elem_val + + if col in elem.attrib: + if ( + elem.attrib[col] not in row.values() + and nm not in row + ): + row[nm] = elem.attrib[col] + else: + for col in self.iterparse[row_node]: + if curr_elem == col: + row[col] = elem.text if elem.text else None + if col in elem.attrib: + row[col] = elem.attrib[col] + + if event == "end": + if curr_elem == row_node and row is not None: + dicts.append(row) + row = None + + elem.clear() + if hasattr(elem, "getprevious"): + while ( + elem.getprevious() is not None + and elem.getparent() is not None + ): + del elem.getparent()[0] + finally: + if hasattr(parser, "close"): + parser.close() if dicts == []: raise ParserError("No result from selected items in iterparse.") Index: pandas/pandas/tests/extension/decimal/test_decimal.py =================================================================== --- pandas.orig/pandas/tests/extension/decimal/test_decimal.py +++ pandas/pandas/tests/extension/decimal/test_decimal.py @@ -151,7 +151,12 @@ class TestDecimalArray(base.ExtensionTes # GH#57723 # EAs that don't have special logic for None will raise, unlike pandas' # which interpret None as the NA value for the dtype. - msg = "conversion from NoneType to Decimal is not supported" + msg = "|".join( + [ + "Cannot convert None to Decimal", # PY315 - maybe linux specific + "conversion from NoneType to Decimal is not supported", + ] + ) with pytest.raises(TypeError, match=msg): super().test_fillna_with_none(data_missing) Index: pandas/pandas/tests/frame/indexing/test_indexing.py =================================================================== --- pandas.orig/pandas/tests/frame/indexing/test_indexing.py +++ pandas/pandas/tests/frame/indexing/test_indexing.py @@ -12,6 +12,7 @@ import pytest from pandas._libs import iNaT from pandas.errors import InvalidIndexError +from pandas.compat import PY315 from pandas.core.dtypes.common import is_integer import pandas as pd @@ -30,7 +31,10 @@ from pandas import ( import pandas._testing as tm # We pass through a TypeError raised by numpy -_slice_msg = "slice indices must be integers or None or have an __index__ method" +if PY315: + _slice_msg = "slice indices must be integers or have an __index__ method" +else: + _slice_msg = "slice indices must be integers or None or have an __index__ method" class TestDataFrameIndexing: Index: pandas/pandas/tests/indexes/period/test_partial_slicing.py =================================================================== --- pandas.orig/pandas/tests/indexes/period/test_partial_slicing.py +++ pandas/pandas/tests/indexes/period/test_partial_slicing.py @@ -1,6 +1,8 @@ import numpy as np import pytest +from pandas.compat import PY315 + from pandas import ( DataFrame, PeriodIndex, @@ -53,7 +55,10 @@ class TestPeriodIndex: # GH#6716 idx = make_range(start="2013/01/01", freq="D", periods=400) - msg = "slice indices must be integers or None or have an __index__ method" + if PY315: + msg = "slice indices must be integers or have an __index__ method" + else: + msg = "slice indices must be integers or None or have an __index__ method" # slices against index should raise IndexError values = [ "2014", @@ -82,7 +87,10 @@ class TestPeriodIndex: def test_range_slice_seconds(self, make_range): # GH#6716 idx = make_range(start="2013/01/01 09:00:00", freq="s", periods=4000) - msg = "slice indices must be integers or None or have an __index__ method" + if PY315: + msg = "slice indices must be integers or have an __index__ method" + else: + msg = "slice indices must be integers or None or have an __index__ method" # slices against index should raise IndexError values = [ Index: pandas/pandas/tests/indexes/test_old_base.py =================================================================== --- pandas.orig/pandas/tests/indexes/test_old_base.py +++ pandas/pandas/tests/indexes/test_old_base.py @@ -7,6 +7,7 @@ import numpy as np import pytest from pandas._libs.tslibs import Timestamp +from pandas.compat import PY315 from pandas.errors import Pandas4Warning from pandas.core.dtypes.common import ( @@ -452,6 +453,8 @@ class TestBase: if len(index) == 0: # 0 vs 0.5 in error message varies with numpy version msg = "index (0|0.5) is out of bounds for axis 0 with size 0" + elif PY315: + msg = "slice indices must be integers or have an __index__ method" else: msg = "slice indices must be integers or None or have an __index__ method" Index: pandas/pandas/tests/indexing/test_floats.py =================================================================== --- pandas.orig/pandas/tests/indexing/test_floats.py +++ pandas/pandas/tests/indexing/test_floats.py @@ -1,6 +1,8 @@ import numpy as np import pytest +from pandas.compat import PY315 + from pandas import ( DataFrame, Index, @@ -251,7 +253,12 @@ class TestFloatIndexers: # setitem if indexer_sli is tm.iloc: # otherwise we keep the same message as above - msg = "slice indices must be integers or None or have an __index__ method" + if PY315: + msg = "slice indices must be integers or have an __index__ method" + else: + msg = ( + "slice indices must be integers or None or have an __index__ method" + ) with pytest.raises(TypeError, match=msg): indexer_sli(s)[idx] = 0 Index: pandas/pandas/tests/scalar/timestamp/test_constructors.py =================================================================== --- pandas.orig/pandas/tests/scalar/timestamp/test_constructors.py +++ pandas/pandas/tests/scalar/timestamp/test_constructors.py @@ -18,7 +18,10 @@ import pytest import pandas.util._test_decorators as td from pandas._libs.tslibs.dtypes import NpyDatetimeUnit -from pandas.compat import PY314 +from pandas.compat import ( + PY314, + PY315, +) from pandas.errors import ( OutOfBoundsDatetime, Pandas4Warning, @@ -240,7 +243,13 @@ class TestTimestampConstructorPositional def test_constructor_keyword(self): # GH#10758 - msg = "function missing required argument 'day'|Required argument 'day'" + msg = "|".join( + [ + r"datetime\(\) missing required argument 'day'", # PY315 + "function missing required argument 'day'", + "Required argument 'day'", + ] + ) with pytest.raises(TypeError, match=msg): Timestamp(year=2000, month=1) @@ -299,7 +308,15 @@ class TestTimestampConstructorPositional # GH#31200 # The exact error message of datetime() depends on its version - msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)" + if PY315: + msg1 = ( + r"datetime\(\) missing required argument " + r"'(year|month|day)' \(pos [123]\)" + ) + else: + msg1 = ( + r"function missing required argument '(year|month|day)' \(pos [123]\)" + ) msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found" msg = "|".join([msg1, msg2]) Index: pandas/pandas/tests/tools/test_to_datetime.py =================================================================== --- pandas.orig/pandas/tests/tools/test_to_datetime.py +++ pandas/pandas/tests/tools/test_to_datetime.py @@ -23,6 +23,7 @@ from pandas._libs.tslibs import ( ) from pandas.compat import ( PY314, + PY315, WASM, ) from pandas.errors import ( @@ -517,6 +518,39 @@ class TestTimeConversionFormats: expected = DatetimeIndex(expected_dates) tm.assert_index_equal(result, expected) + @pytest.mark.xfail( + not PY315, reason="%:z directive not supported prior to 3.15", raises=ValueError + ) + def test_to_datetime_colon_z_offset(self): + dates = [ + "2010-01-01 12:00:00+04:00", + "2010-01-01 12:00:00+04:30", + "2010-01-01 12:00:00-05:00", + ] + expected_dates = [ + "2010-01-01 08:00:00+00:00", + "2010-01-01 07:30:00+00:00", + "2010-01-01 17:00:00+00:00", + ] + fmt = "%Y-%m-%d %H:%M:%S%:z" + + result = to_datetime(dates, format=fmt, utc=True) + expected = DatetimeIndex(expected_dates) + tm.assert_index_equal(result, expected) + + def test_to_datetime_missing_colon_z_offset(self): + # test adapted from python/cpython#136961 + dates = ["+04:0030"] + fmt = "%:z" + + if PY315: + msg = r"Missing colon in %:z before '30', got '\+04:0030'" + else: + msg = "':' is a bad directive in format '%:z'" + + with pytest.raises(ValueError, match=msg): + to_datetime(dates, format=fmt, utc=True) + @pytest.mark.parametrize( "offset", ["+0", "-1foo", "UTCbar", ":10", "+01:000:01", ""] ) @@ -1390,9 +1424,12 @@ class TestToDatetime: @pytest.mark.parametrize("errors", ["coerce", "raise"]) def test_invalid_format_raises(self, errors): # https://github.com/pandas-dev/pandas/issues/50255 - with pytest.raises( - ValueError, match="':' is a bad directive in format 'H%:M%:S%" - ): + if PY315: + msg = r"':M' is a bad directive in format 'H%:M%:S%" + else: + msg = "':' is a bad directive in format 'H%:M%:S%" + + with pytest.raises(ValueError, match=msg): to_datetime(["00:00:00"], format="H%:M%:S%", errors=errors) @pytest.mark.parametrize("value", ["a", "00:01:99"])
signature.asc
Description: PGP signature
-- debian-science-maintainers mailing list [email protected] https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/debian-science-maintainers
