From e52fa6317ed4f46e94c5a6feea111cd5b0a8f2c4 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Dec 2024 18:56:43 +0200 Subject: [PATCH 1/5] Support the free-threaded build of CPython 3.13 --- .github/workflows/ci-cd.yml | 8 ++++++- pyproject.toml | 7 ++++++ scripts/cibw_before_build.sh | 7 ++++++ yarl/_quoting_c.pyx | 45 ++++++++++++++++++------------------ 4 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 scripts/cibw_before_build.sh diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5e943f7a9..d12faa016 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -184,6 +184,7 @@ jobs: strategy: matrix: pyver: + - 3.13t - 3.13 - 3.12 - 3.11 @@ -231,7 +232,7 @@ jobs: - name: Setup Python ${{ matrix.pyver }} id: python-install - uses: actions/setup-python@v5 + uses: quansight-labs/setup-python@v5 with: python-version: ${{ matrix.pyver }} allow-prereleases: true @@ -241,6 +242,11 @@ jobs: uses: py-actions/py-dependency-install@v4 with: path: requirements/test.txt + - name: Install Cython nightly on free-threading + if: matrix.pyver == '3.13t' + run: | + python -m pip uninstall -y cython + python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython - name: Determine pre-compiled compatible wheel env: # NOTE: When `pip` is forced to colorize output piped into `jq`, diff --git a/pyproject.toml b/pyproject.toml index 81900af58..3de49d52b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ keep-going = false embedsignature = "True" emit_code_comments = "True" linetrace = "True" # Implies `profile=True` +freethreading_compatible = "True" [tool.local.cythonize.kwargs.compile-time-env] # This section can contain compile time env vars @@ -64,6 +65,7 @@ linetrace = "True" # Implies `profile=True` [tool.cibuildwheel] build-frontend = "build" +enable = ["cpython-freethreading"] before-test = [ # NOTE: Attempt to have pip pre-compile PyYAML wheel with our build # NOTE: constraints unset. The hope is that pip will cache that wheel @@ -91,3 +93,8 @@ pure-python = "false" [tool.cibuildwheel.windows] before-test = [] # Windows cmd has different syntax and pip chooses wheels + +[[tool.cibuildwheel.overrides]] +select = "cp313t-*" +build-frontend = "build; --no-isolation" +before-build = "bash {package}/scripts/cibw_before_build.sh" diff --git a/scripts/cibw_before_build.sh b/scripts/cibw_before_build.sh new file mode 100644 index 000000000..718d89d7d --- /dev/null +++ b/scripts/cibw_before_build.sh @@ -0,0 +1,7 @@ +# TODO: Delete when there's a PyPI Cython release (3.1.0) that supports free-threaded Python 3.13. +FREE_THREADED_BUILD="$(python -c"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))")" +if [[ $FREE_THREADED_BUILD == "True" ]]; then + python -m pip install -U pip + python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython + python -m pip install setuptools expandvars +fi diff --git a/yarl/_quoting_c.pyx b/yarl/_quoting_c.pyx index 067ba96e4..2d8c6e403 100644 --- a/yarl/_quoting_c.pyx +++ b/yarl/_quoting_c.pyx @@ -25,7 +25,6 @@ cdef str ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS cdef str QS = '+&=;' DEF BUF_SIZE = 8 * 1024 # 8KiB -cdef char BUFFER[BUF_SIZE] cdef inline Py_UCS4 _to_hex(uint8_t v) noexcept: if v < 10: @@ -49,14 +48,14 @@ cdef inline int _is_lower_hex(Py_UCS4 v) noexcept: return 'a' <= v <= 'f' -cdef inline Py_UCS4 _restore_ch(Py_UCS4 d1, Py_UCS4 d2): +cdef inline long _restore_ch(Py_UCS4 d1, Py_UCS4 d2): cdef int digit1 = _from_hex(d1) if digit1 < 0: - return -1 + return -1 cdef int digit2 = _from_hex(d2) if digit2 < 0: - return -1 - return (digit1 << 4 | digit2) + return -1 + return digit1 << 4 | digit2 cdef uint8_t ALLOWED_TABLE[16] @@ -91,15 +90,18 @@ cdef struct Writer: cdef inline void _init_writer(Writer* writer): - writer.buf = &BUFFER[0] + cdef char *buf = PyMem_Malloc(BUF_SIZE) + if buf == NULL: + PyErr_NoMemory() + return + writer.buf = buf writer.size = BUF_SIZE writer.pos = 0 writer.changed = 0 cdef inline void _release_writer(Writer* writer): - if writer.buf != BUFFER: - PyMem_Free(writer.buf) + PyMem_Free(writer.buf) cdef inline int _write_char(Writer* writer, Py_UCS4 ch, bint changed): @@ -109,17 +111,10 @@ cdef inline int _write_char(Writer* writer, Py_UCS4 ch, bint changed): if writer.pos == writer.size: # reallocate size = writer.size + BUF_SIZE - if writer.buf == BUFFER: - buf = PyMem_Malloc(size) - if buf == NULL: - PyErr_NoMemory() - return -1 - memcpy(buf, writer.buf, writer.size) - else: - buf = PyMem_Realloc(writer.buf, size) - if buf == NULL: - PyErr_NoMemory() - return -1 + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 writer.buf = buf writer.size = size writer.buf[writer.pos] = ch @@ -255,6 +250,7 @@ cdef class _Quoter: Writer *writer ): cdef Py_UCS4 ch + cdef long chl cdef int changed cdef Py_ssize_t idx = 0 @@ -262,11 +258,12 @@ cdef class _Quoter: ch = PyUnicode_READ(kind, data, idx) idx += 1 if ch == '%' and self._requote and idx <= length - 2: - ch = _restore_ch( + chl = _restore_ch( PyUnicode_READ(kind, data, idx), PyUnicode_READ(kind, data, idx + 1) ) - if ch != -1: + if chl != -1: + ch = chl idx += 2 if ch < 128: if bit_at(self._protected_table, ch): @@ -342,6 +339,7 @@ cdef class _Unquoter: cdef Py_ssize_t consumed cdef str unquoted cdef Py_UCS4 ch = 0 + cdef long chl = 0 cdef Py_ssize_t idx = 0 cdef Py_ssize_t start_pct cdef int kind = PyUnicode_KIND(val) @@ -352,11 +350,12 @@ cdef class _Unquoter: idx += 1 if ch == '%' and idx <= length - 2: changed = 1 - ch = _restore_ch( + chl = _restore_ch( PyUnicode_READ(kind, data, idx), PyUnicode_READ(kind, data, idx + 1) ) - if ch != -1: + if chl != -1: + ch = chl idx += 2 assert buflen < 4 buffer[buflen] = ch From 635b9cacfa56eb0e9a045aa92a4d65e9a80a0ee5 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Dec 2024 19:01:23 +0200 Subject: [PATCH 2/5] Add news fragments --- CHANGES/1456.feature.rst | 1 + CHANGES/1456.packaging.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 CHANGES/1456.feature.rst create mode 100644 CHANGES/1456.packaging.rst diff --git a/CHANGES/1456.feature.rst b/CHANGES/1456.feature.rst new file mode 100644 index 000000000..c5da0e048 --- /dev/null +++ b/CHANGES/1456.feature.rst @@ -0,0 +1 @@ +Implemented support for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`. diff --git a/CHANGES/1456.packaging.rst b/CHANGES/1456.packaging.rst new file mode 100644 index 000000000..911a78b2c --- /dev/null +++ b/CHANGES/1456.packaging.rst @@ -0,0 +1 @@ +Started building wheels for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`. From 3aca3a17ae583d43dcd3662384d053fa060e9655 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Dec 2024 19:20:07 +0200 Subject: [PATCH 3/5] Fix cibuildwheel and cython configs --- pyproject.toml | 3 +-- yarl/_quoting_c.pyx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3de49d52b..097ffda6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,6 @@ keep-going = false embedsignature = "True" emit_code_comments = "True" linetrace = "True" # Implies `profile=True` -freethreading_compatible = "True" [tool.local.cythonize.kwargs.compile-time-env] # This section can contain compile time env vars @@ -96,5 +95,5 @@ before-test = [] # Windows cmd has different syntax and pip chooses wheels [[tool.cibuildwheel.overrides]] select = "cp313t-*" -build-frontend = "build; --no-isolation" +build-frontend = "build; args: --no-isolation" before-build = "bash {package}/scripts/cibw_before_build.sh" diff --git a/yarl/_quoting_c.pyx b/yarl/_quoting_c.pyx index 2d8c6e403..6fed3926f 100644 --- a/yarl/_quoting_c.pyx +++ b/yarl/_quoting_c.pyx @@ -1,4 +1,4 @@ -# cython: language_level=3 +# cython: language_level=3, freethreading_compatible=True from cpython.exc cimport PyErr_NoMemory from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc From f273a6be9441c46c731466c71782868479596a2c Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Dec 2024 20:01:19 +0200 Subject: [PATCH 4/5] Fix cibuildwheel and linter --- .github/workflows/ci-cd.yml | 5 ++++- MANIFEST.in | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index d12faa016..9a3b953dd 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -246,7 +246,10 @@ jobs: if: matrix.pyver == '3.13t' run: | python -m pip uninstall -y cython - python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython + python -m pip install cython \ + --pre \ + --extra-index-url \ + https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - name: Determine pre-compiled compatible wheel env: # NOTE: When `pip` is forced to colorize output piped into `jq`, diff --git a/MANIFEST.in b/MANIFEST.in index 904cf6889..6bb7eb82f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,6 +12,7 @@ graft docs graft CHANGES graft requirements graft tests +graft scripts global-exclude *.pyc global-exclude *.cache exclude yarl/*.c From 4ce41638497090e934dd40f06487ab3782ccb8c4 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 20 Dec 2024 20:09:55 +0200 Subject: [PATCH 5/5] Fix PYTHON_LATEST env var in github actions --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/reusable-linters.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 9a3b953dd..4f6113433 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -38,7 +38,7 @@ env: PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` PYTHONIOENCODING: utf-8 PYTHONUTF8: 1 - PYTHON_LATEST: 3.12 + PYTHON_LATEST: 3.13 jobs: diff --git a/.github/workflows/reusable-linters.yml b/.github/workflows/reusable-linters.yml index 68b06d497..e127ea23e 100644 --- a/.github/workflows/reusable-linters.yml +++ b/.github/workflows/reusable-linters.yml @@ -21,7 +21,7 @@ env: PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` PYTHONIOENCODING: utf-8 PYTHONUTF8: 1 - PYTHON_LATEST: 3.12 + PYTHON_LATEST: 3.13 jobs: