Skip to content

Commit 888553e

Browse files
lysnikolaoubdracowebknjaz
authored
Implement support for the free-threaded build of CPython 3.13
PR #1015 Co-authored-by: J. Nick Koston <[email protected]> Co-authored-by: Sviatoslav Sydorenko <[email protected]>
1 parent 217d16e commit 888553e

File tree

8 files changed

+1631
-10
lines changed

8 files changed

+1631
-10
lines changed

.github/workflows/ci-cd.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ jobs:
150150
strategy:
151151
matrix:
152152
pyver:
153+
- 3.13-freethreading
153154
- 3.13
154155
- 3.12
155156
- 3.11
@@ -163,6 +164,10 @@ jobs:
163164
no-extensions: Y
164165
- os: windows
165166
no-extensions: Y
167+
- os: macos
168+
pyver: 3.13-freethreading # this is still tested within cibuildwheel
169+
- os: windows
170+
pyver: 3.13-freethreading # this is still tested within cibuildwheel
166171
include:
167172
- pyver: pypy-3.8
168173
no-extensions: Y
@@ -195,11 +200,44 @@ jobs:
195200
path: dist
196201

197202
- name: Setup Python ${{ matrix.pyver }}
198-
id: python-install
203+
if: >-
204+
!endsWith(matrix.pyver, '-freethreading')
199205
uses: actions/setup-python@v5
200206
with:
201207
python-version: ${{ matrix.pyver }}
202208
allow-prereleases: true
209+
- name: Setup Python ${{ matrix.pyver }}
210+
if: endsWith(matrix.pyver, '-freethreading')
211+
uses: deadsnakes/[email protected]
212+
with:
213+
python-version: 3.13-dev
214+
nogil: true
215+
- name: Compute runtime Python version
216+
id: python-install
217+
run: |
218+
import sys
219+
from os import environ
220+
from pathlib import Path
221+
222+
FILE_APPEND_MODE = 'a'
223+
224+
python_version_str = ".".join(
225+
map(str, sys.version_info[:3]),
226+
)
227+
freethreading_suffix = (
228+
'' if sys.version_info < (3, 13) or sys._is_gil_enabled()
229+
else 't'
230+
)
231+
232+
with Path(environ['GITHUB_OUTPUT']).open(
233+
mode=FILE_APPEND_MODE,
234+
) as outputs_file:
235+
print(
236+
f'python-version={python_version_str}{freethreading_suffix}',
237+
file=outputs_file,
238+
)
239+
shell: python
240+
203241
- name: Get pip cache dir
204242
id: pip-cache
205243
run: |

CHANGES/1015.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implemented support for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`.

CHANGES/1015.packaging.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Started publishing wheels made for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`.

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cChardet
1818
changelog
1919
charset
2020
charsetdetect
21+
CPython
2122
criterias
2223
css
2324
ctor

multidict/_multidict.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "Python.h"
22
#include "structmember.h"
33

4+
#include "_multilib/pythoncapi_compat.h"
5+
46
// Include order important
57
#include "_multilib/defs.h"
68
#include "_multilib/istr.h"
@@ -155,13 +157,17 @@ _multidict_append_items_seq(MultiDictObject *self, PyObject *arg,
155157
Py_INCREF(value);
156158
}
157159
else if (PyList_CheckExact(item)) {
158-
if (PyList_GET_SIZE(item) != 2) {
160+
if (PyList_Size(item) != 2) {
161+
goto invalid_type;
162+
}
163+
key = PyList_GetItemRef(item, 0);
164+
if (key == NULL) {
165+
goto invalid_type;
166+
}
167+
value = PyList_GetItemRef(item, 1);
168+
if (value == NULL) {
159169
goto invalid_type;
160170
}
161-
key = PyList_GET_ITEM(item, 0);
162-
Py_INCREF(key);
163-
value = PyList_GET_ITEM(item, 1);
164-
Py_INCREF(value);
165171
}
166172
else if (PySequence_Check(item)) {
167173
if (PySequence_Size(item) != 2) {
@@ -2070,6 +2076,13 @@ PyInit__multidict(void)
20702076

20712077
/* Instantiate this module */
20722078
module = PyModule_Create(&multidict_module);
2079+
if (module == NULL) {
2080+
goto fail;
2081+
}
2082+
2083+
#ifdef Py_GIL_DISABLED
2084+
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
2085+
#endif
20732086

20742087
Py_INCREF(&istr_type);
20752088
if (PyModule_AddObject(

multidict/_multilib/pair_list.h

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -916,13 +916,18 @@ _pair_list_post_update(pair_list_t *list, PyObject* used_keys, Py_ssize_t pos)
916916

917917
for (; pos < list->size; pos++) {
918918
pair = pair_list_get(list, pos);
919-
tmp = PyDict_GetItem(used_keys, pair->identity);
920-
if (tmp == NULL) {
919+
int status = PyDict_GetItemRef(used_keys, pair->identity, &tmp);
920+
if (status == -1) {
921+
// exception set
922+
return -1;
923+
}
924+
else if (status == 0) {
921925
// not found
922926
continue;
923927
}
924928

925929
num = PyLong_AsSsize_t(tmp);
930+
Py_DECREF(tmp);
926931
if (num == -1) {
927932
if (!PyErr_Occurred()) {
928933
PyErr_SetString(PyExc_RuntimeError, "invalid internal state");
@@ -955,12 +960,18 @@ _pair_list_update(pair_list_t *list, PyObject *key,
955960
int found;
956961
int ident_cmp_res;
957962

958-
item = PyDict_GetItem(used_keys, identity);
959-
if (item == NULL) {
963+
int status = PyDict_GetItemRef(used_keys, identity, &item);
964+
if (status == -1) {
965+
// exception set
966+
return -1;
967+
}
968+
else if (status == 0) {
969+
// not found
960970
pos = 0;
961971
}
962972
else {
963973
pos = PyLong_AsSsize_t(item);
974+
Py_DECREF(item);
964975
if (pos == -1) {
965976
if (!PyErr_Occurred()) {
966977
PyErr_SetString(PyExc_RuntimeError, "invalid internal state");
@@ -1087,18 +1098,28 @@ pair_list_update_from_seq(pair_list_t *list, PyObject *seq)
10871098
}
10881099

10891100
// Convert item to sequence, and verify length 2.
1101+
#ifdef Py_GIL_DISABLED
1102+
if (!PySequence_Check(item)) {
1103+
#else
10901104
fast = PySequence_Fast(item, "");
10911105
if (fast == NULL) {
10921106
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
1107+
#endif
10931108
PyErr_Format(PyExc_TypeError,
10941109
"multidict cannot convert sequence element #%zd"
10951110
" to a sequence",
10961111
i);
1112+
#ifndef Py_GIL_DISABLED
10971113
}
1114+
#endif
10981115
goto fail_1;
10991116
}
11001117

1118+
#ifdef Py_GIL_DISABLED
1119+
n = PySequence_Size(item);
1120+
#else
11011121
n = PySequence_Fast_GET_SIZE(fast);
1122+
#endif
11021123
if (n != 2) {
11031124
PyErr_Format(PyExc_ValueError,
11041125
"multidict update sequence element #%zd "
@@ -1107,10 +1128,27 @@ pair_list_update_from_seq(pair_list_t *list, PyObject *seq)
11071128
goto fail_1;
11081129
}
11091130

1131+
#ifdef Py_GIL_DISABLED
1132+
key = PySequence_ITEM(item, 0);
1133+
if (key == NULL) {
1134+
PyErr_Format(PyExc_ValueError,
1135+
"multidict update sequence element #%zd's "
1136+
"key could not be fetched", i);
1137+
goto fail_1;
1138+
}
1139+
value = PySequence_ITEM(item, 1);
1140+
if (value == NULL) {
1141+
PyErr_Format(PyExc_ValueError,
1142+
"multidict update sequence element #%zd's "
1143+
"value could not be fetched", i);
1144+
goto fail_1;
1145+
}
1146+
#else
11101147
key = PySequence_Fast_GET_ITEM(fast, 0);
11111148
value = PySequence_Fast_GET_ITEM(fast, 1);
11121149
Py_INCREF(key);
11131150
Py_INCREF(value);
1151+
#endif
11141152

11151153
identity = pair_list_calc_identity(list, key);
11161154
if (identity == NULL) {
@@ -1128,7 +1166,9 @@ pair_list_update_from_seq(pair_list_t *list, PyObject *seq)
11281166

11291167
Py_DECREF(key);
11301168
Py_DECREF(value);
1169+
#ifndef Py_GIL_DISABLED
11311170
Py_DECREF(fast);
1171+
#endif
11321172
Py_DECREF(item);
11331173
Py_DECREF(identity);
11341174
}

0 commit comments

Comments
 (0)