Skip to content

Add Python 3.13 (beta) support #12334

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

Merged
merged 2 commits into from
May 19, 2024
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
22 changes: 19 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
"windows-py310",
"windows-py311",
"windows-py312",
"windows-py313",

"ubuntu-py38",
"ubuntu-py38-pluggy",
Expand All @@ -63,12 +64,14 @@ jobs:
"ubuntu-py310",
"ubuntu-py311",
"ubuntu-py312",
"ubuntu-py313",
"ubuntu-pypy3",

"macos-py38",
"macos-py39",
"macos-py310",
"macos-py312",
"macos-py313",

"doctesting",
"plugins",
Expand Down Expand Up @@ -97,9 +100,13 @@ jobs:
os: windows-latest
tox_env: "py311"
- name: "windows-py312"
python: "3.12-dev"
python: "3.12"
os: windows-latest
tox_env: "py312"
- name: "windows-py313"
python: "3.13-dev"
os: windows-latest
tox_env: "py313"

- name: "ubuntu-py38"
python: "3.8"
Expand Down Expand Up @@ -128,10 +135,15 @@ jobs:
tox_env: "py311"
use_coverage: true
- name: "ubuntu-py312"
python: "3.12-dev"
python: "3.12"
os: ubuntu-latest
tox_env: "py312"
use_coverage: true
- name: "ubuntu-py313"
python: "3.13-dev"
os: ubuntu-latest
tox_env: "py313"
use_coverage: true
- name: "ubuntu-pypy3"
python: "pypy-3.8"
os: ubuntu-latest
Expand All @@ -151,9 +163,13 @@ jobs:
os: macos-latest
tox_env: "py310-xdist"
- name: "macos-py312"
python: "3.12-dev"
python: "3.12"
os: macos-latest
tox_env: "py312-xdist"
- name: "macos-py313"
python: "3.13-dev"
os: macos-latest
tox_env: "py313-xdist"

- name: "plugins"
python: "3.12"
Expand Down
1 change: 1 addition & 0 deletions changelog/12334.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support for Python 3.13 (beta1 at the time of writing).
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Testing",
"Topic :: Utilities",
Expand Down
7 changes: 3 additions & 4 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,15 +424,14 @@ def recursionindex(self) -> Optional[int]:
# which generates code objects that have hash/value equality
# XXX needs a test
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
# print "checking for recursion at", key
values = cache.setdefault(key, [])
# Since Python 3.13 f_locals is a proxy, freeze it.
loc = dict(entry.frame.f_locals)
if values:
f = entry.frame
loc = f.f_locals
for otherloc in values:
if otherloc == loc:
return i
values.append(entry.frame.f_locals)
values.append(loc)
return None


Expand Down
6 changes: 5 additions & 1 deletion src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None:
__tracebackhide__ = True
i = 0
entries = list(entries)
backlocals = sys._getframe(1).f_locals
# Since Python 3.13, f_locals is not a dict, but eval requires a dict.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backlocals = dict(sys._getframe(1).f_locals)
while entries:
name, check = entries.pop(0)
for ind, call in enumerate(self.calls[i:]):
Expand Down Expand Up @@ -760,6 +761,9 @@ def _makefile(
) -> Path:
items = list(files.items())

if ext is None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@nicoddemus nicoddemus May 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave a note here:

Suggested change
if ext is None:
# https://github.com/python/cpython/issues/119113
if ext is None:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary, since I think we better not rely on it and just keep the check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see what harm a small comment like that could cause either, but OK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also in that case, we probably should change the signature as well, so we have the typing match the runtime behavior.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type is ext: str which is correct. The callers have some *args, **kwargs forwarding which is somewhat annoying to type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well OK, this is no big deal anyway.

raise TypeError("ext must not be None")

if ext and not ext.startswith("."):
raise ValueError(
f"pytester.makefile expects a file extension, try .{ext} instead of {ext}"
Expand Down
8 changes: 6 additions & 2 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# mypy: allow-untyped-defs
from __future__ import annotations

import fnmatch
import importlib
import io
import operator
Expand Down Expand Up @@ -237,7 +238,7 @@ def f(n):
n += 1
f(n)

excinfo = pytest.raises(RuntimeError, f, 8)
excinfo = pytest.raises(RecursionError, f, 8)
traceback = excinfo.traceback
recindex = traceback.recursionindex()
assert recindex == 3
Expand Down Expand Up @@ -373,7 +374,10 @@ def test_excinfo_no_sourcecode():
except ValueError:
excinfo = _pytest._code.ExceptionInfo.from_current()
s = str(excinfo.traceback[-1])
assert s == " File '<string>':1 in <module>\n ???\n"
# TODO: Since Python 3.13b1 under pytest-xdist, the * is `import
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so.

# sys;exec(eval(sys.stdin.readline()))` (execnet bootstrap code)
# instead of `???` like before. Is this OK?
fnmatch.fnmatch(s, " File '<string>':1 in <module>\n *\n")


def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
Expand Down
6 changes: 5 additions & 1 deletion testing/code/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,11 @@
pass

B.__name__ = B.__qualname__ = "B2"
assert getfslineno(B)[1] == -1
# Since Python 3.13 this started working.
if sys.version_info >= (3, 13):
assert getfslineno(B)[1] != -1

Check warning on line 375 in testing/code/test_source.py

View check run for this annotation

Codecov / codecov/patch

testing/code/test_source.py#L375

Added line #L375 was not covered by tests
else:
assert getfslineno(B)[1] == -1


def test_code_of_object_instance_with_call() -> None:
Expand Down
2 changes: 1 addition & 1 deletion testing/test_cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def test_custom_cache_dir_with_env_var(
assert pytester.path.joinpath("custom_cache_dir").is_dir()


@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir")))
@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "mydir/tox-env")))
def test_cache_reportheader(
env: Sequence[str], pytester: Pytester, monkeypatch: MonkeyPatch
) -> None:
Expand Down
8 changes: 2 additions & 6 deletions testing/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,7 @@ def test_doctest_unexpected_exception(self, pytester: Pytester):
"Traceback (most recent call last):",
' File "*/doctest.py", line *, in __run',
" *",
*(
(" *^^^^*",)
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
else ()
),
*((" *^^^^*", " *", " *") if sys.version_info >= (3, 13) else ()),
' File "<doctest test_doctest_unexpected_exception.txt[1]>", line 1, in <module>',
"ZeroDivisionError: division by zero",
"*/test_doctest_unexpected_exception.txt:2: UnexpectedException",
Expand Down Expand Up @@ -385,7 +381,7 @@ def some_property(self):
"*= FAILURES =*",
"*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*",
"004 ",
"005 >>> Sample().some_property",
"005 *>>> Sample().some_property",
"Expected:",
" 'another thing'",
"Got:",
Expand Down
17 changes: 1 addition & 16 deletions testing/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os
from pathlib import Path
import re
import sys
from typing import Optional

from _pytest.config import ExitCode
Expand Down Expand Up @@ -45,32 +44,18 @@ def pytest_internalerror(excrepr, excinfo):
assert result.ret == ExitCode.INTERNAL_ERROR
assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):"

end_lines = (
result.stdout.lines[-4:]
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
else result.stdout.lines[-3:]
)
end_lines = result.stdout.lines[-3:]

if exc == SystemExit:
assert end_lines == [
f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart',
'INTERNALERROR> raise SystemExit("boom")',
*(
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
else ()
),
"INTERNALERROR> SystemExit: boom",
]
else:
assert end_lines == [
f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart',
'INTERNALERROR> raise ValueError("boom")',
*(
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
else ()
),
"INTERNALERROR> ValueError: boom",
]
if returncode is False:
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ envlist =
py310
py311
py312
py313
pypy3
py38-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib}
doctesting
Expand Down
Loading