Skip to content

Commit 6342533

Browse files
authored
Preliminary support for Python 3.13
Some notes; - Python 3.13.0 has a critical bug which will crash the interpreter (so, tests are being skipped on it, should be fixed in 3.13.1). - threading._start_joinable_thread and thread.start_joinable_thread must now also be patched to notify when a thread starts. - the frame.f_locals has some backward compatibility breakage where things can no longer be deleted from it (seems like it'll be fixed in 3.13.1) A subsequent commit will be done with some other improvements related to the integration.
1 parent 7d6e6e6 commit 6342533

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+8282
-7897
lines changed

.github/workflows/pydevd-release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
fail-fast: false
1717
matrix:
1818
os: [macos-latest, windows-latest]
19-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
19+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
2020
steps:
2121
- uses: actions/checkout@v3
2222
- name: Set up Python

.github/workflows/pydevd-tests-python.yml

+15-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
"ubuntu-py311-cython",
2525
"ubuntu-py312-cython-checkbin",
2626
"windows-py312-cython-checkbin",
27+
"ubuntu-py313-cython",
28+
"windows-py313-cython",
2729
]
2830

2931
include:
@@ -64,6 +66,14 @@ jobs:
6466
python: "3.12"
6567
os: windows-latest
6668
PYDEVD_USE_CYTHON: YES
69+
- name: "ubuntu-py313-cython"
70+
python: "3.13"
71+
os: ubuntu-20.04
72+
PYDEVD_USE_CYTHON: YES
73+
- name: "windows-py313-cython"
74+
python: "3.13"
75+
os: windows-latest
76+
PYDEVD_USE_CYTHON: YES
6777

6878
steps:
6979
- uses: actions/checkout@v1
@@ -95,7 +105,7 @@ jobs:
95105
pip install untangle --no-warn-script-location
96106
pip install importlib-metadata --no-warn-script-location
97107
- name: Install Python 3.x deps
98-
if: contains(matrix.name, 'py3') && !contains(matrix.name, 'pypy') && !contains(matrix.name, 'py312') && !contains(matrix.name, 'py311')
108+
if: contains(matrix.name, 'py3') && !contains(matrix.name, 'pypy') && !contains(matrix.name, 'py312') && !contains(matrix.name, 'py311') && !contains(matrix.name, 'py313')
99109
run: |
100110
pip install PySide2 --no-warn-script-location
101111
pip install "numpy<2" --force --no-warn-script-location
@@ -118,13 +128,14 @@ jobs:
118128
- name: Check that wheels can be built
119129
if: contains(matrix.name, 'checkbin') && contains(matrix.name, 'ubuntu')
120130
run: |
121-
python -m pip install cibuildwheel==2.21.2
131+
python -m pip install setuptools --no-warn-script-location
132+
python -m pip install cibuildwheel==2.21.3
122133
# Remove these .so files (will be rebuilt)
123134
rm pydevd_attach_to_process/*.so
124135
python -m cibuildwheel --output-dir wheelhouse
125136
env:
126-
CIBW_BUILD: cp310-*manylinux*x86_64 cp311-*manylinux*x86_64 cp312-*manylinux*x86_64
127-
CIBW_BUILD_VERBOSITY: 1
137+
CIBW_BUILD: cp310-*manylinux*x86_64 cp311-*manylinux*x86_64 cp312-*manylinux*x86_64 cp313-*manylinux*x86_64
138+
CIBW_BUILD_VERBOSITY: 3
128139

129140
- name: Rebuild .so
130141
if: contains(matrix.name, 'checkbin') && contains(matrix.name, 'ubuntu')

_pydev_bundle/pydev_is_thread_alive.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
# circumstances).
55
# It is required to debug threads started by start_new_thread in Python 3.4
66
_temp = threading.Thread()
7-
if hasattr(_temp, "_is_stopped"): # Python 3.x has this
7+
if hasattr(_temp, "_handle") and hasattr(_temp, "_started"):
8+
# Python 3.13 and later has this
9+
def is_thread_alive(t):
10+
return not t._handle.is_done() and t._started.is_set()
11+
if hasattr(_temp, "_is_stopped"): # Python 3.12 and earlier has this
812

913
def is_thread_alive(t):
1014
return not t._is_stopped

_pydev_bundle/pydev_monkey.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
from _pydev_bundle._pydev_saved_modules import threading
66
from _pydevd_bundle.pydevd_constants import (
7+
IS_PY313_OR_GREATER,
78
get_global_debugger,
89
IS_WINDOWS,
910
IS_JYTHON,
@@ -1173,11 +1174,13 @@ def _get_threading_modules_to_patch():
11731174

11741175

11751176
def patch_thread_module(thread_module):
1177+
attr = "_start_joinable_thread" if IS_PY313_OR_GREATER else "_start_new_thread"
1178+
is_start_joinable = thread_module is threading and IS_PY313_OR_GREATER
11761179
if getattr(thread_module, "_original_start_new_thread", None) is None:
11771180
if thread_module is threading:
1178-
if not hasattr(thread_module, "_start_new_thread"):
1181+
if not hasattr(thread_module, attr):
11791182
return # Jython doesn't have it.
1180-
_original_start_new_thread = thread_module._original_start_new_thread = thread_module._start_new_thread
1183+
_original_start_new_thread = thread_module._original_start_new_thread = getattr(thread_module, attr)
11811184
else:
11821185
_original_start_new_thread = thread_module._original_start_new_thread = thread_module.start_new_thread
11831186
else:
@@ -1191,6 +1194,16 @@ def pydev_start_new_thread(self, function, args=(), kwargs={}):
11911194
"""
11921195
return _original_start_new_thread(_UseNewThreadStartup(function, args, kwargs), ())
11931196

1197+
class ClassWithPydevStartJoinableThread:
1198+
def pydev_start_joinable_thread(self, function, *args, **kwargs):
1199+
"""
1200+
We need to replace the original thread_module._start_joinable_thread with this function so that threads started
1201+
through it and not through the threading module are properly traced.
1202+
"""
1203+
handle = kwargs.pop("handle", None)
1204+
daemon = kwargs.pop("daemon", True)
1205+
return _original_start_new_thread(_UseNewThreadStartup(function, args, kwargs), handle=handle, daemon=daemon)
1206+
11941207
# This is a hack for the situation where the thread_module.start_new_thread is declared inside a class, such as the one below
11951208
# class F(object):
11961209
# start_new_thread = thread_module.start_new_thread
@@ -1199,13 +1212,17 @@ def pydev_start_new_thread(self, function, args=(), kwargs={}):
11991212
# self.start_new_thread(self.function, args, kwargs)
12001213
# So, if it's an already bound method, calling self.start_new_thread won't really receive a different 'self' -- it
12011214
# does work in the default case because in builtins self isn't passed either.
1202-
pydev_start_new_thread = ClassWithPydevStartNewThread().pydev_start_new_thread
1215+
pydev_start_new_thread = (
1216+
ClassWithPydevStartJoinableThread().pydev_start_joinable_thread
1217+
if is_start_joinable
1218+
else ClassWithPydevStartNewThread().pydev_start_new_thread
1219+
)
12031220

12041221
try:
12051222
# We need to replace the original thread_module.start_new_thread with this function so that threads started through
12061223
# it and not through the threading module are properly traced.
12071224
if thread_module is threading:
1208-
thread_module._start_new_thread = pydev_start_new_thread
1225+
setattr(thread_module, attr, pydev_start_new_thread)
12091226
else:
12101227
thread_module.start_new_thread = pydev_start_new_thread
12111228
thread_module.start_new = pydev_start_new_thread

_pydevd_bundle/pydevd_additional_thread_info_regular.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111
from _pydev_bundle import pydev_log
1212
from _pydev_bundle._pydev_saved_modules import threading
13+
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
1314
import weakref
1415

1516
version = 11
@@ -135,7 +136,7 @@ def _get_related_thread(self):
135136
if thread is None:
136137
return False
137138

138-
if thread._is_stopped:
139+
if not is_thread_alive(thread):
139140
return None
140141

141142
if thread._ident is None: # Can this happen?

_pydevd_bundle/pydevd_collect_bytecode_info.py

+2
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,8 @@ def _create_msg_part(self, instruction, tok=None, line=None):
846846
argrepr = instruction.argrepr
847847
if isinstance(argrepr, str) and argrepr.startswith("NULL + "):
848848
argrepr = argrepr[7:]
849+
if isinstance(argrepr, str) and argrepr.endswith("+ NULL"):
850+
argrepr = argrepr[:-7]
849851
return _MsgPart(line, tok if tok is not None else dec(instruction, argrepr))
850852

851853
def _next_instruction_to_str(self, line_to_contents):

_pydevd_bundle/pydevd_comm.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,8 @@ def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
11401140
xml += "<line>%d</line>" % (frame.f_lineno,)
11411141
else:
11421142
for _, line in linestarts:
1143-
xml += "<line>%d</line>" % (line,)
1143+
if line is not None:
1144+
xml += "<line>%d</line>" % (line,)
11441145
del frame
11451146
xml += "</xml>"
11461147
cmd = dbg.cmd_factory.make_get_next_statement_targets_message(seq, xml)

_pydevd_bundle/pydevd_constants.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,10 @@ def _current_frames():
174174
IS_PY311_OR_GREATER = sys.version_info >= (3, 11)
175175
IS_PY312_OR_GREATER = sys.version_info >= (3, 12)
176176
IS_PY313_OR_GREATER = sys.version_info >= (3, 13)
177+
IS_PY314_OR_GREATER = sys.version_info >= (3, 14)
177178

178179
# Not currently supported in Python 3.12.
179-
SUPPORT_ATTACH_TO_PID = not IS_PY313_OR_GREATER
180+
SUPPORT_ATTACH_TO_PID = not IS_PY314_OR_GREATER
180181

181182

182183
def version_str(v):

0 commit comments

Comments
 (0)