From 58a7826c3038aafd0ab9bf5e25ec2b9435644005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 12 Jul 2024 23:24:15 -0600 Subject: [PATCH] Build Python 3.13 wheels (not free-threaded) --- .github/workflows/ci.yml | 7 ++++--- msgspec/_core.c | 40 +++++++++++++++++++++++++++++++--------- msgspec/_utils.py | 1 + setup.py | 1 + 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f0839f0..be80c0dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,12 +80,13 @@ jobs: env: CIBW_TEST_REQUIRES: "pytest msgpack pyyaml tomli tomli_w" CIBW_TEST_COMMAND: "pytest {project}/tests" - CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-*" + CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp13-*" CIBW_SKIP: "*-win32 *_i686 *_s390x *_ppc64le" CIBW_ARCHS_MACOS: "x86_64 arm64" CIBW_ARCHS_LINUX: "x86_64 aarch64" CIBW_TEST_SKIP: "*_arm64 *-musllinux_*" CIBW_ENVIRONMENT: "CFLAGS=-g0" + CIBW_PRERELEASE_PYTHONS: "true" steps: - uses: actions/checkout@v2 @@ -99,10 +100,10 @@ jobs: - name: Set up Environment if: github.event_name != 'release' run: | - echo "CIBW_SKIP=${CIBW_SKIP} *-musllinux_* cp38-*_aarch64 cp39-*_aarch64 cp311-*_aarch64 cp312-*_aarch64" >> $GITHUB_ENV + echo "CIBW_SKIP=${CIBW_SKIP} *-musllinux_* cp38-*_aarch64 cp39-*_aarch64 cp311-*_aarch64 cp312-*_aarch64 cp313-*" >> $GITHUB_ENV - name: Build & Test Wheels - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.19.2 - name: Upload artifact uses: actions/upload-artifact@v2 diff --git a/msgspec/_core.c b/msgspec/_core.c index 0f287814..7683df10 100644 --- a/msgspec/_core.c +++ b/msgspec/_core.c @@ -20,6 +20,7 @@ #define PY310_PLUS (PY_VERSION_HEX >= 0x030a0000) #define PY311_PLUS (PY_VERSION_HEX >= 0x030b0000) #define PY312_PLUS (PY_VERSION_HEX >= 0x030c0000) +#define PY313_PLUS (PY_VERSION_HEX >= 0x030d0000) /* Hint to the compiler not to store `x` in a register since it is likely to * change. Results in much higher performance on GCC, with smaller benefits on @@ -497,7 +498,7 @@ find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key) for (i = 0; i < nkwargs; i++) { PyObject *kwname = PyTuple_GET_ITEM(kwnames, i); assert(PyUnicode_Check(kwname)); - if (_PyUnicode_EQ(kwname, key)) { + if (PyUnicode_Compare(kwname, key) == 0) { return kwstack[i]; } } @@ -4440,8 +4441,6 @@ typenode_collect_convert_structs(TypeNodeCollectState *state) { */ PyObject *tag_mapping = NULL, *tag_field = NULL, *set_item = NULL; PyObject *struct_info = NULL; - Py_ssize_t set_pos = 0; - Py_hash_t set_hash; bool array_like = false; bool tags_are_strings = true; int status = -1; @@ -4449,7 +4448,14 @@ typenode_collect_convert_structs(TypeNodeCollectState *state) { tag_mapping = PyDict_New(); if (tag_mapping == NULL) goto cleanup; +#if PY313_PLUS + PyObject *iter_set = PyObject_GetIter(state->structs_set); + while ((set_item = PyIter_Next(iter_set)) != NULL) { +#else + Py_ssize_t set_pos = 0; + Py_hash_t set_hash; while (_PySet_NextEntry(state->structs_set, &set_pos, &set_item, &set_hash)) { +#endif struct_info = StructInfo_Convert(set_item); if (struct_info == NULL) goto cleanup; @@ -7322,7 +7328,7 @@ Struct_vectorcall(PyTypeObject *cls, PyObject *const *args, size_t nargsf, PyObj * check for parameters passed both as arg and kwarg */ for (field_index = 0; field_index < nfields; field_index++) { PyObject *field = PyTuple_GET_ITEM(fields, field_index); - if (_PyUnicode_EQ(kwname, field)) { + if (PyUnicode_Compare(kwname, field) == 0) { if (MS_UNLIKELY(field_index < nargs)) { PyErr_Format( PyExc_TypeError, @@ -7729,7 +7735,7 @@ struct_replace(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject } for (field_index = 0; field_index < nfields; field_index++) { PyObject *field = PyTuple_GET_ITEM(fields, field_index); - if (_PyUnicode_EQ(kwname, field)) goto kw_found; + if (PyUnicode_Compare(kwname, field) == 0) goto kw_found; } /* Unknown keyword */ @@ -11255,7 +11261,11 @@ ms_uuid_to_16_bytes(MsgspecState *mod, PyObject *obj, unsigned char *buf) { PyErr_SetString(PyExc_TypeError, "uuid.int must be an int"); return -1; } +#if PY313_PLUS + int out = _PyLong_AsByteArray((PyLongObject *)int128, buf, 16, 0, 0, 1); +#else int out = _PyLong_AsByteArray((PyLongObject *)int128, buf, 16, 0, 0); +#endif Py_DECREF(int128); return out; } @@ -12404,8 +12414,7 @@ mpack_encode_list(EncoderState *self, PyObject *obj) static int mpack_encode_set(EncoderState *self, PyObject *obj) { - Py_ssize_t len, ppos = 0; - Py_hash_t hash; + Py_ssize_t len = 0; PyObject *item; int status = -1; @@ -12424,7 +12433,14 @@ mpack_encode_set(EncoderState *self, PyObject *obj) if (mpack_encode_array_header(self, len, "set") < 0) return -1; if (Py_EnterRecursiveCall(" while serializing an object")) return -1; +#if PY313_PLUS + PyObject *iter_set = PyObject_GetIter(obj); + while ((item = PyIter_Next(iter_set)) != NULL) { +#else + Py_ssize_t ppos = 0; + Py_hash_t hash; while (_PySet_NextEntry(obj, &ppos, &item, &hash)) { +#endif if (mpack_encode_inline(self, item) < 0) goto cleanup; } status = 0; @@ -13707,8 +13723,7 @@ json_encode_tuple(EncoderState *self, PyObject *obj) static int json_encode_set(EncoderState *self, PyObject *obj) { - Py_ssize_t len, ppos = 0; - Py_hash_t hash; + Py_ssize_t len = 0; PyObject *item; int status = -1; @@ -13727,7 +13742,14 @@ json_encode_set(EncoderState *self, PyObject *obj) if (ms_write(self, "[", 1) < 0) return -1; if (Py_EnterRecursiveCall(" while serializing an object")) return -1; +#if PY313_PLUS + PyObject *iter_set = PyObject_GetIter(obj); + while ((item = PyIter_Next(iter_set)) != NULL) { +#else + Py_ssize_t ppos = 0; + Py_hash_t hash; while (_PySet_NextEntry(obj, &ppos, &item, &hash)) { +#endif if (json_encode_inline(self, item) < 0) goto cleanup; if (ms_write(self, ",", 1) < 0) goto cleanup; } diff --git a/msgspec/_utils.py b/msgspec/_utils.py index ddf6f27c..c8416b1f 100644 --- a/msgspec/_utils.py +++ b/msgspec/_utils.py @@ -127,6 +127,7 @@ def get_class_annotations(obj): value = type(None) elif isinstance(value, str): value = _forward_ref(value) + # TODO: Pass `type_params` to `_eval_type` to fix deprecation warning value = typing._eval_type(value, cls_locals, cls_globals) if mapping is not None: value = _apply_params(value, mapping) diff --git a/setup.py b/setup.py index 300a45bf..2dcbf638 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], extras_require=extras_require, license="BSD",