Skip to content

Commit fb5ae14

Browse files
committed
feat: Support for Python 3.12, 3.13 and 3.14
1 parent fccf708 commit fb5ae14

File tree

8 files changed

+65
-10
lines changed

8 files changed

+65
-10
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
matrix:
1919
# Test all supported versions on Ubuntu:
2020
os: [ubuntu-latest]
21-
python: ["3.9", "3.10", "3.11", "pypy-3.10"]
21+
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.10"]
2222
experimental: [false]
2323
# include:
2424
# - os: macos-latest

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def run(self):
7878
raise NotImplementedError("platform '%s' is not supported!" % sys.platform)
7979
extra_compile_args.append('-I src/')
8080
extra_compile_args.append('-I src/libbacktrace')
81-
if sys.version_info[:2] == (3,11):
81+
if sys.version_info[:2] >= (3,11):
8282
extra_source_files += ['src/populate_frames.c']
8383
ext_modules = [Extension('_vmprof',
8484
sources=[
@@ -123,7 +123,7 @@ def run(self):
123123
'pytz',
124124
'colorama',
125125
] + extra_install_requires,
126-
python_requires='<3.12',
126+
python_requires='<3.15',
127127
tests_require=['pytest','cffi','hypothesis'],
128128
entry_points = {
129129
'console_scripts': [

src/_vmprof.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@
1414

1515
#ifndef RPYTHON_VMPROF
1616
#if PY_VERSION_HEX >= 0x030b00f0 /* >= 3.11 */
17+
#define Py_BUILD_CORE
18+
#if PY_VERSION_HEX >= 0x030E0000 /* >= 3.14 */
19+
#include "internal/pycore_interpframe.h"
20+
#else
1721
#include "internal/pycore_frame.h"
22+
#endif
23+
#undef PY_BUILD_CORE
1824
#include "populate_frames.h"
1925
#endif
2026
#endif
@@ -140,7 +146,11 @@ void emit_all_code_objects(PyObject * seen_code_ids)
140146
Py_ssize_t i, size;
141147
void * param[2];
142148

149+
#if PY_VERSION_HEX >= 0x030D0000 /* >= 3.13 */
150+
gc_module = PyImport_ImportModule("gc");
151+
#else
143152
gc_module = PyImport_ImportModuleNoBlock("gc");
153+
#endif
144154
if (gc_module == NULL)
145155
goto error;
146156

src/populate_frames.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88
// 0x030B0000 is 3.11.
99
#define PY_311 0x030B0000
10+
// 0x030D0000 is 3.13.
11+
#define PY_313 0x030D0000
12+
// 0x030E0000 is 3.14.
13+
#define PY_314 0x030E0000
14+
1015
#if PY_VERSION_HEX >= PY_311
1116

1217
/**
@@ -22,15 +27,26 @@
2227
*/
2328

2429
#define Py_BUILD_CORE
30+
#if PY_VERSION_HEX >= PY_314
31+
// Python 3.14 moved frame internals to pycore_interpframe.h
32+
#include "internal/pycore_interpframe.h"
33+
#else
2534
#include "internal/pycore_frame.h"
35+
#endif
2636
#undef Py_BUILD_CORE
2737

2838
// Modified from
2939
// https://github.com/python/cpython/blob/v3.11.4/Python/pystate.c#L1278-L1285
3040
_PyInterpreterFrame *unsafe_PyThreadState_GetInterpreterFrame(
3141
PyThreadState *tstate) {
3242
assert(tstate != NULL);
43+
#if PY_VERSION_HEX >= PY_313
44+
// In Python 3.13+, cframe was removed and current_frame is directly on tstate
45+
_PyInterpreterFrame *f = tstate->current_frame;
46+
#else
47+
// Python 3.11 and 3.12 use cframe->current_frame
3348
_PyInterpreterFrame *f = tstate->cframe->current_frame;
49+
#endif
3450
while (f && _PyFrame_IsIncomplete(f)) {
3551
f = f->previous;
3652
}
@@ -47,7 +63,13 @@ PyCodeObject *unsafe_PyInterpreterFrame_GetCode(
4763
_PyInterpreterFrame *frame) {
4864
assert(frame != NULL);
4965
assert(!_PyFrame_IsIncomplete(frame));
66+
#if PY_VERSION_HEX >= PY_313
67+
// In Python 3.13+, use the _PyFrame_GetCode inline function
68+
// f_code was renamed to f_executable
69+
PyCodeObject *code = _PyFrame_GetCode(frame);
70+
#else
5071
PyCodeObject *code = frame->f_code;
72+
#endif
5173
assert(code != NULL);
5274
return code;
5375
}
@@ -71,6 +93,10 @@ _PyInterpreterFrame *unsafe_PyInterpreterFrame_GetBack(
7193
// this function is not available in libpython
7294
int _PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame) {
7395
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
96+
#if PY_VERSION_HEX >= PY_313
97+
return PyCode_Addr2Line(_PyFrame_GetCode(frame), addr);
98+
#else
7499
return PyCode_Addr2Line(frame->f_code, addr);
100+
#endif
75101
}
76-
#endif // PY_VERSION_HEX >= PY_311
102+
#endif // PY_VERSION_HEX >= PY_311

src/populate_frames.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@
77

88
#include <frameobject.h>
99

10-
#define Py_BUILD_CORE
10+
// 0x030E0000 is 3.14.
11+
#define PY_314 0x030E0000
12+
13+
#define PY_BUILD_CORE
14+
#if PY_VERSION_HEX >= PY_314
15+
// Python 3.14 moved frame internals to pycore_interpframe.h
16+
#include "internal/pycore_interpframe.h"
17+
#else
1118
#include "internal/pycore_frame.h"
12-
#undef Py_BUILD_CORE
19+
#endif
20+
#undef PY_BUILD_CORE
1321

1422
_PyInterpreterFrame *unsafe_PyThreadState_GetInterpreterFrame(PyThreadState *tstate);
1523

@@ -19,4 +27,4 @@ _PyInterpreterFrame *unsafe_PyInterpreterFrame_GetBack(_PyInterpreterFrame *fram
1927

2028
int _PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame);
2129

22-
#endif
30+
#endif

src/vmp_stack.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
#ifndef RPYTHON_VMPROF
66
#if PY_VERSION_HEX >= 0x030b00f0 /* >= 3.11 */
7+
#define PY_BUILD_CORE
8+
#if PY_VERSION_HEX >= 0x030E0000 /* >= 3.14 */
9+
#include "internal/pycore_interpframe.h"
10+
#else
711
#include "internal/pycore_frame.h"
12+
#endif
13+
#undef PY_BUILD_CORE
814
#include "populate_frames.h"
915
#endif
1016
#endif

vmprof/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class IniParser(object):
118118

119119
def __init__(self, f):
120120
self.ini_parser = configparser.ConfigParser()
121-
self.ini_parser.readfp(f)
121+
self.ini_parser.read_file(f)
122122

123123
def get_option(self, name, type, default=None):
124124
if type == float:

vmprof/test/test_run.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,13 @@ def test_nested_call():
199199
assert len(t.children) == 1
200200
assert 'function_foo' in t[''].name
201201
if PY3K:
202-
assert len(t[''].children) == 1
203-
assert '<listcomp>' in t[''][''].name
202+
# In Python 3.12+, list comprehensions are inlined and don't create
203+
# a separate stack frame (PEP 709), so <listcomp> won't appear
204+
if sys.version_info >= (3, 12):
205+
assert len(t[''].children) == 0
206+
else:
207+
assert len(t[''].children) == 1
208+
assert '<listcomp>' in t[''][''].name
204209
else:
205210
assert len(t[''].children) == 0
206211

0 commit comments

Comments
 (0)