Skip to content

TST: run python-dev CI on 3.14-dev #61950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
3 changes: 1 addition & 2 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ jobs:
# To freeze this file, uncomment out the ``if: false`` condition, and migrate the jobs
# to the corresponding posix/windows-macos/sdist etc. workflows.
# Feel free to modify this comment as necessary.
if: false
defaults:
run:
shell: bash -eou pipefail {0}
Expand Down Expand Up @@ -345,7 +344,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 Down
7 changes: 5 additions & 2 deletions pandas/_testing/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
)
import uuid

from pandas.compat import PYPY
from pandas.compat import (
PYPY,
WARNING_CHECK_BROKEN,
)
from pandas.errors import ChainedAssignmentError

from pandas.io.common import get_handle
Expand Down Expand Up @@ -163,7 +166,7 @@ def with_csv_dialect(name: str, **kwargs) -> Generator[None]:
def raises_chained_assignment_error(extra_warnings=(), extra_match=()):
from pandas._testing import assert_produces_warning

if PYPY:
if PYPY or WARNING_CHECK_BROKEN:
if not extra_warnings:
from contextlib import nullcontext

Expand Down
4 changes: 4 additions & 0 deletions pandas/compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
ISMUSL,
PY311,
PY312,
PY314,
PYPY,
WARNING_CHECK_BROKEN,
WASM,
)
from pandas.compat.numpy import is_numpy_dev
Expand Down Expand Up @@ -157,7 +159,9 @@ def is_ci_environment() -> bool:
"ISMUSL",
"PY311",
"PY312",
"PY314",
"PYPY",
"WARNING_CHECK_BROKEN",
"WASM",
"is_numpy_dev",
"pa_version_under12p1",
Expand Down
3 changes: 3 additions & 0 deletions pandas/compat/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@

PY311 = sys.version_info >= (3, 11)
PY312 = sys.version_info >= (3, 12)
PY314 = sys.version_info >= (3, 14)
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 "")
REF_COUNT = 2 if PY311 else 3
# hopefully there is a workaround in Python 3.14.1
WARNING_CHECK_BROKEN = PY314

__all__ = [
"IS64",
Expand Down
11 changes: 7 additions & 4 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
from pandas._libs.hashtable import duplicated
from pandas._libs.lib import is_range_indexer
from pandas.compat import PYPY
from pandas.compat._constants import REF_COUNT
from pandas.compat._constants import (
REF_COUNT,
WARNING_CHECK_BROKEN,
)
from pandas.compat._optional import import_optional_dependency
from pandas.compat.numpy import function as nv
from pandas.errors import (
Expand Down Expand Up @@ -4296,8 +4299,8 @@ def __setitem__(self, key, value) -> None:
z 3 50
# Values for 'a' and 'b' are completely ignored!
"""
if not PYPY:
if sys.getrefcount(self) <= 3:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT + 1:
warnings.warn(
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
)
Expand Down Expand Up @@ -9204,7 +9207,7 @@ def update(
1 2 500.0
2 3 6.0
"""
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down
21 changes: 12 additions & 9 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@
npt,
)
from pandas.compat import PYPY
from pandas.compat._constants import REF_COUNT
from pandas.compat._constants import (
REF_COUNT,
WARNING_CHECK_BROKEN,
)
from pandas.compat._optional import import_optional_dependency
from pandas.compat.numpy import function as nv
from pandas.errors import (
Expand Down Expand Up @@ -7069,7 +7072,7 @@ def fillna(
"""
inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -7300,7 +7303,7 @@ def ffill(
"""
inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -7440,7 +7443,7 @@ def bfill(
"""
inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -7525,7 +7528,7 @@ def replace(

inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -7888,7 +7891,7 @@ def interpolate(
inplace = validate_bool_kwarg(inplace, "inplace")

if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -8472,7 +8475,7 @@ def clip(
inplace = validate_bool_kwarg(inplace, "inplace")

if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -10081,7 +10084,7 @@ def where(
"""
inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down Expand Up @@ -10145,7 +10148,7 @@ def mask(
) -> Self | None:
inplace = validate_bool_kwarg(inplace, "inplace")
if inplace:
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down
8 changes: 6 additions & 2 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
from pandas._libs.indexing import NDFrameIndexerBase
from pandas._libs.lib import item_from_zerodim
from pandas.compat import PYPY
from pandas.compat._constants import (
REF_COUNT,
WARNING_CHECK_BROKEN,
)
from pandas.errors import (
AbstractMethodError,
ChainedAssignmentError,
Expand Down Expand Up @@ -913,8 +917,8 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None:

@final
def __setitem__(self, key, value) -> None:
if not PYPY:
if sys.getrefcount(self.obj) <= 2:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self.obj) <= REF_COUNT:
warnings.warn(
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
)
Expand Down
11 changes: 7 additions & 4 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
)
from pandas._libs.lib import is_range_indexer
from pandas.compat import PYPY
from pandas.compat._constants import REF_COUNT
from pandas.compat._constants import (
REF_COUNT,
WARNING_CHECK_BROKEN,
)
from pandas.compat._optional import import_optional_dependency
from pandas.compat.numpy import function as nv
from pandas.errors import (
Expand Down Expand Up @@ -1059,8 +1062,8 @@ def _get_value(self, label, takeable: bool = False):
return self.iloc[loc]

def __setitem__(self, key, value) -> None:
if not PYPY:
if sys.getrefcount(self) <= 3:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT + 1:
warnings.warn(
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
)
Expand Down Expand Up @@ -3338,7 +3341,7 @@ def update(self, other: Series | Sequence | Mapping) -> None:
2 3
dtype: int64
"""
if not PYPY:
if not PYPY and not WARNING_CHECK_BROKEN:
if sys.getrefcount(self) <= REF_COUNT:
warnings.warn(
_chained_assignment_method_msg,
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/copy_view/test_chained_assignment_deprecation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import pytest

from pandas.compat import WARNING_CHECK_BROKEN
from pandas.errors import ChainedAssignmentError

from pandas import DataFrame
Expand All @@ -17,6 +18,8 @@ def test_series_setitem(indexer):

# using custom check instead of tm.assert_produces_warning because that doesn't
# fail if multiple warnings are raised
if WARNING_CHECK_BROKEN:
return
with pytest.warns() as record: # noqa: TID251
df["a"][indexer] = 0
assert len(record) == 1
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/frame/test_query_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def test_query_duplicate_column_name(self, engine, parser):
}
).rename(columns={"B": "A"})

res = df.query('C == 1', engine=engine, parser=parser)
res = df.query("C == 1", engine=engine, parser=parser)

expect = DataFrame(
[[1, 1, 1]],
Expand Down Expand Up @@ -1411,7 +1411,7 @@ def test_expr_with_column_name_with_backtick_and_hash(self):
def test_expr_with_column_name_with_backtick(self):
# GH 59285
df = DataFrame({"a`b": (1, 2, 3), "ab": (4, 5, 6)})
result = df.query("`a``b` < 2") # noqa
result = df.query("`a``b` < 2")
# Note: Formatting checks may wrongly consider the above ``inline code``.
expected = df[df["a`b"] < 2]
tm.assert_frame_equal(result, expected)
Expand Down
9 changes: 8 additions & 1 deletion pandas/tests/indexes/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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
5 changes: 2 additions & 3 deletions pandas/tests/io/formats/test_to_latex.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import codecs
from datetime import datetime
from textwrap import dedent

Expand Down Expand Up @@ -42,15 +41,15 @@ def test_to_latex_to_file_utf8_with_encoding(self):
df = DataFrame([["au\xdfgangen"]])
with tm.ensure_clean("test.tex") as path:
df.to_latex(path, encoding="utf-8")
with codecs.open(path, "r", encoding="utf-8") as f:
with open(path, encoding="utf-8") as f:
assert df.to_latex() == f.read()

def test_to_latex_to_file_utf8_without_encoding(self):
# test with utf-8 without encoding option
df = DataFrame([["au\xdfgangen"]])
with tm.ensure_clean("test.tex") as path:
df.to_latex(path)
with codecs.open(path, "r", encoding="utf-8") as f:
with open(path, encoding="utf-8") as f:
assert df.to_latex() == f.read()

def test_to_latex_tabular_with_index(self):
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
Loading
Loading