diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index fbc7ff7390..858d269e08 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -67,7 +67,7 @@ jobs: # Note: the default manylinux is manylinux2014 run: | python -m pip install -U pip - python -m pip install "cibuildwheel>=2.17,<3" + python -m pip install "cibuildwheel>=2.20,<3" - name: Build wheels env: @@ -89,6 +89,9 @@ jobs: ls wheelhouse/*cp310*.whl ls wheelhouse/*cp311*.whl ls wheelhouse/*cp312*.whl + ls wheelhouse/*cp313*.whl + # Free-threading builds: + ls wheelhouse/*cp313t*.whl - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index e55444ceca..40991440d3 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -51,11 +51,18 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - python-version: ["3.9", "pypy-3.9", "3.13"] + python-version: ["3.9", "pypy-3.9", "3.13", "3.13t"] name: CPython ${{ matrix.python-version }}-${{ matrix.os }} steps: - uses: actions/checkout@v4 - - name: Setup Python + - if: ${{ matrix.python-version == '3.13t' }} + name: Setup free-threaded Python + uses: deadsnakes/action@v3.2.0 + with: + python-version: 3.13 + nogil: true + - if: ${{ matrix.python-version != '3.13t' }} + name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -65,9 +72,13 @@ jobs: - name: Install dependencies run: | pip install -U pip - if [ "${{ matrix.python-version }}" == "3.13" ]; then + if [[ "${{ matrix.python-version }}" == "3.13" ]]; then pip install --pre cffi setuptools pip install --no-build-isolation hatch + elif [[ "${{ matrix.python-version }}" == "3.13t" ]]; then + # Hatch can't be installed on 3.13t, use pytest directly. + pip install . + pip install -r requirements/test.txt else pip install hatch fi @@ -77,7 +88,11 @@ jobs: mongodb-version: 6.0 - name: Run tests run: | - hatch run test:test + if [[ "${{ matrix.python-version }}" == "3.13t" ]]; then + pytest -v --durations=5 --maxfail=10 + else + hatch run test:test + fi doctest: runs-on: ubuntu-latest diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index 223c392280..a66071c285 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -3184,6 +3184,9 @@ static PyModuleDef_Slot _cbson_slots[] = { {Py_mod_exec, _cbson_exec}, #if defined(Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED) {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED}, +#endif +#if PY_VERSION_HEX >= 0x030D0000 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, #endif {0, NULL}, }; diff --git a/doc/changelog.rst b/doc/changelog.rst index 6a118f56ca..e7b160b176 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,6 +12,8 @@ PyMongo 4.11 brings a number of changes including: - Dropped support for Python 3.8. - Dropped support for MongoDB 3.6. +- Added support for free-threaded Python with the GIL disabled. For more information see: + `Free-threaded CPython `_. Issues Resolved ............... diff --git a/pymongo/_cmessagemodule.c b/pymongo/_cmessagemodule.c index b5adbeec32..eb457b341c 100644 --- a/pymongo/_cmessagemodule.c +++ b/pymongo/_cmessagemodule.c @@ -1022,6 +1022,9 @@ static PyModuleDef_Slot _cmessage_slots[] = { {Py_mod_exec, _cmessage_exec}, #ifdef Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED}, +#endif +#if PY_VERSION_HEX >= 0x030D0000 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, #endif {0, NULL}, }; diff --git a/pyproject.toml b/pyproject.toml index 2688aab27e..b4f59f67d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -236,6 +236,8 @@ partial_branches = ["if (.*and +)*not _use_c( and.*)*:"] directory = "htmlcov" [tool.cibuildwheel] +# Enable free-threaded support +free-threaded-support = true skip = "pp* *-musllinux*" build-frontend = "build" test-command = "python {project}/tools/fail_if_no_c.py" diff --git a/test/asynchronous/test_client_context.py b/test/asynchronous/test_client_context.py index a0cb53a14f..6d77818436 100644 --- a/test/asynchronous/test_client_context.py +++ b/test/asynchronous/test_client_context.py @@ -61,6 +61,13 @@ def test_setdefaultencoding_worked(self): self.assertEqual(sys.getdefaultencoding(), os.environ["SETDEFAULTENCODING"]) + def test_free_threading_is_enabled(self): + if "free-threading build" not in sys.version: + raise SkipTest("this test requires the Python free-threading build") + + # If the GIL is enabled then pymongo or one of our deps does not support free-threading. + self.assertFalse(sys._is_gil_enabled()) # type: ignore[attr-defined] + if __name__ == "__main__": unittest.main() diff --git a/test/test_client_context.py b/test/test_client_context.py index be8a562142..5996f9243b 100644 --- a/test/test_client_context.py +++ b/test/test_client_context.py @@ -61,6 +61,13 @@ def test_setdefaultencoding_worked(self): self.assertEqual(sys.getdefaultencoding(), os.environ["SETDEFAULTENCODING"]) + def test_free_threading_is_enabled(self): + if "free-threading build" not in sys.version: + raise SkipTest("this test requires the Python free-threading build") + + # If the GIL is enabled then pymongo or one of our deps does not support free-threading. + self.assertFalse(sys._is_gil_enabled()) # type: ignore[attr-defined] + if __name__ == "__main__": unittest.main()