Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ jobs:
- name: Set up Python Dev Version
uses: actions/setup-python@v5
with:
python-version: '3.13-dev'
python-version: '3.14-dev'

- name: Build Environment
run: |
Expand All @@ -406,3 +406,5 @@ jobs:

- name: Run Tests
uses: ./.github/actions/run-tests
# TEMP allow this to fail until we fixed all test failures (related to chained assignment warnings)
continue-on-error: true
2 changes: 2 additions & 0 deletions pandas/compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
PY310,
PY311,
PY312,
PY314,
PYPY,
)
import pandas.compat.compressors
Expand Down Expand Up @@ -205,5 +206,6 @@ def get_bz2_file() -> type[pandas.compat.compressors.BZ2File]:
"PY310",
"PY311",
"PY312",
"PY314",
"PYPY",
]
2 changes: 2 additions & 0 deletions pandas/compat/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
PY310 = sys.version_info >= (3, 10)
PY311 = sys.version_info >= (3, 11)
PY312 = sys.version_info >= (3, 12)
PY314 = sys.version_info >= (3, 14)
PYPY = platform.python_implementation() == "PyPy"
ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "")
REF_COUNT = 2 if PY311 else 3
Expand All @@ -26,5 +27,6 @@
"PY310",
"PY311",
"PY312",
"PY314",
"PYPY",
]
9 changes: 8 additions & 1 deletion pandas/tests/indexes/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import numpy as np
import pytest

from pandas.compat import PY314
from pandas.errors import InvalidIndexError

from pandas.core.dtypes.common import (
Expand Down Expand Up @@ -160,13 +161,19 @@ def test_contains_requires_hashable_raises(self, index):
with pytest.raises(TypeError, match=msg):
[] in index

if PY314:
container_or_iterable = "a container or iterable"
else:
container_or_iterable = "iterable"

msg = "|".join(
[
r"unhashable type: 'dict'",
r"must be real number, not dict",
r"an integer is required",
r"\{\}",
r"pandas\._libs\.interval\.IntervalTree' is not iterable",
r"pandas\._libs\.interval\.IntervalTree' is not "
f"{container_or_iterable}",
]
)
with pytest.raises(TypeError, match=msg):
Expand Down
24 changes: 20 additions & 4 deletions pandas/tests/io/parser/test_quoting.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

import pytest

from pandas.compat import PY311
from pandas.compat import (
PY311,
PY314,
)
from pandas.errors import ParserError

from pandas import DataFrame
Expand All @@ -21,15 +24,24 @@
skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")


if PY314:
# TODO: write a regex that works with all new possitibilities here
MSG1 = ""
MSG2 = r"[\s\S]*"
else:
MSG1 = "a(n)? 1-character string"
MSG2 = "string( or None)?"


@pytest.mark.parametrize(
"kwargs,msg",
[
({"quotechar": "foo"}, '"quotechar" must be a(n)? 1-character string'),
({"quotechar": "foo"}, f'"quotechar" must be {MSG1}'),
(
{"quotechar": None, "quoting": csv.QUOTE_MINIMAL},
"quotechar must be set if quoting enabled",
),
({"quotechar": 2}, '"quotechar" must be string( or None)?, not int'),
({"quotechar": 2}, f'"quotechar" must be {MSG2}, not int'),
],
)
@skip_pyarrow # ParserError: CSV parse error: Empty CSV file or block
Expand Down Expand Up @@ -88,8 +100,12 @@ def test_null_quote_char(all_parsers, quoting, quote_char):

if quoting != csv.QUOTE_NONE:
# Sanity checking.
if not PY314:
msg = "1-character string"
else:
msg = "unicode character or None"
msg = (
'"quotechar" must be a 1-character string'
f'"quotechar" must be a {msg}'
if PY311 and all_parsers.engine == "python" and quote_char == ""
else "quotechar must be set if quoting enabled"
)
Expand Down
12 changes: 11 additions & 1 deletion pandas/tests/reshape/merge/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import numpy as np
import pytest

from pandas.compat import PY314

from pandas.core.dtypes.common import (
is_object_dtype,
is_string_dtype,
Expand Down Expand Up @@ -2394,10 +2396,18 @@ def test_merge_suffix_raises(suffixes):
merge(a, b, left_index=True, right_index=True, suffixes=suffixes)


TWO_GOT_THREE = "2, got 3" if PY314 else "2"


@pytest.mark.parametrize(
"col1, col2, suffixes, msg",
[
("a", "a", ("a", "b", "c"), r"too many values to unpack \(expected 2\)"),
(
"a",
"a",
("a", "b", "c"),
(rf"too many values to unpack \(expected {TWO_GOT_THREE}\)"),
),
("a", "a", tuple("a"), r"not enough values to unpack \(expected 2, got 1\)"),
],
)
Expand Down
6 changes: 5 additions & 1 deletion pandas/tests/scalar/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime
from pandas._libs.tslibs.parsing import DateParseError
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
from pandas.compat import PY314

from pandas import (
NaT,
Expand Down Expand Up @@ -342,7 +343,10 @@ def test_invalid_arguments(self):
msg = '^Given date string "-2000" not likely a datetime$'
with pytest.raises(ValueError, match=msg):
Period("-2000", "Y")
msg = "day is out of range for month"
if PY314:
msg = "day 0 must be in range 1..31 for month 1 in year 1: 0"
else:
msg = "day is out of range for month"
with pytest.raises(DateParseError, match=msg):
Period("0", "Y")
msg = "Unknown datetime string format, unable to parse"
Expand Down
15 changes: 12 additions & 3 deletions pandas/tests/scalar/timestamp/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import pytz

from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
from pandas.compat import PY310
from pandas.compat import (
PY310,
PY314,
)
from pandas.errors import OutOfBoundsDatetime

from pandas import (
Expand Down Expand Up @@ -225,7 +228,10 @@ def test_constructor_positional(self):
with pytest.raises(ValueError, match=msg):
Timestamp(2000, 13, 1)

msg = "day is out of range for month"
if PY314:
msg = "must be in range 1..31 for month 1 in year 2000"
else:
msg = "day is out of range for month"
with pytest.raises(ValueError, match=msg):
Timestamp(2000, 1, 0)
with pytest.raises(ValueError, match=msg):
Expand All @@ -249,7 +255,10 @@ def test_constructor_keyword(self):
with pytest.raises(ValueError, match=msg):
Timestamp(year=2000, month=13, day=1)

msg = "day is out of range for month"
if PY314:
msg = "must be in range 1..31 for month 1 in year 2000"
else:
msg = "day is out of range for month"
with pytest.raises(ValueError, match=msg):
Timestamp(year=2000, month=1, day=0)
with pytest.raises(ValueError, match=msg):
Expand Down
29 changes: 22 additions & 7 deletions pandas/tests/tools/test_to_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
iNaT,
parsing,
)
from pandas.compat import PY314
from pandas.errors import (
OutOfBoundsDatetime,
OutOfBoundsTimedelta,
Expand Down Expand Up @@ -57,6 +58,17 @@
r"alongside this."
)

if PY314:
NOT_99 = ", not 99"
DAY_IS_OUT_OF_RANGE = (
r"day \d{1,2} must be in range 1\.\.\d{1,2} for month \d{1,2} in year \d{4}"
", at position 0"
)
else:
NOT_99 = ""
DAY_IS_OUT_OF_RANGE = "day is out of range for month, at position 0"


pytestmark = pytest.mark.filterwarnings(
"ignore:errors='ignore' is deprecated:FutureWarning"
)
Expand Down Expand Up @@ -1451,7 +1463,7 @@ def test_datetime_invalid_scalar(self, value, format):
r'^Given date string "a" not likely a datetime, at position 0$',
r'^unconverted data remains when parsing with format "%H:%M:%S": "9", '
f"at position 0. {PARSING_ERR_MSG}$",
r"^second must be in 0..59: 00:01:99, at position 0$",
rf"^second must be in 0..59{NOT_99}: 00:01:99, at position 0$",
]
)
with pytest.raises(ValueError, match=msg):
Expand Down Expand Up @@ -1509,7 +1521,7 @@ def test_datetime_invalid_index(self, values, format):
f"{PARSING_ERR_MSG}$",
r'^unconverted data remains when parsing with format "%H:%M:%S": "9", '
f"at position 0. {PARSING_ERR_MSG}$",
r"^second must be in 0..59: 00:01:99, at position 0$",
rf"^second must be in 0..59{NOT_99}: 00:01:99, at position 0$",
]
)
with pytest.raises(ValueError, match=msg):
Expand Down Expand Up @@ -3012,7 +3024,10 @@ def test_day_not_in_month_coerce(self, cache, arg, format):
assert isna(to_datetime(arg, errors="coerce", format=format, cache=cache))

def test_day_not_in_month_raise(self, cache):
msg = "day is out of range for month: 2015-02-29, at position 0"
if PY314:
msg = "day 29 must be in range 1..28 for month 2 in year 2015: 2015-02-29"
else:
msg = "day is out of range for month: 2015-02-29"
with pytest.raises(ValueError, match=msg):
to_datetime("2015-02-29", errors="raise", cache=cache)

Expand All @@ -3022,12 +3037,12 @@ def test_day_not_in_month_raise(self, cache):
(
"2015-02-29",
"%Y-%m-%d",
f"^day is out of range for month, at position 0. {PARSING_ERR_MSG}$",
f"^{DAY_IS_OUT_OF_RANGE}. {PARSING_ERR_MSG}$",
),
(
"2015-29-02",
"%Y-%d-%m",
f"^day is out of range for month, at position 0. {PARSING_ERR_MSG}$",
f"^{DAY_IS_OUT_OF_RANGE}. {PARSING_ERR_MSG}$",
),
(
"2015-02-32",
Expand All @@ -3044,12 +3059,12 @@ def test_day_not_in_month_raise(self, cache):
(
"2015-04-31",
"%Y-%m-%d",
f"^day is out of range for month, at position 0. {PARSING_ERR_MSG}$",
f"^{DAY_IS_OUT_OF_RANGE}. {PARSING_ERR_MSG}$",
),
(
"2015-31-04",
"%Y-%d-%m",
f"^day is out of range for month, at position 0. {PARSING_ERR_MSG}$",
f"^{DAY_IS_OUT_OF_RANGE}. {PARSING_ERR_MSG}$",
),
],
)
Expand Down
Loading