Linux GPIO subsystem development
 help / color / mirror / Atom feed
From: Vincent Fazio <vfazio@gmail.com>
To: linux-gpio@vger.kernel.org
Cc: brgl@kernel.org, Vincent Fazio <vfazio@gmail.com>
Subject: [libgpiod][PATCH v2 2/3] bindings: python: support free-threaded CPython
Date: Wed, 27 May 2026 16:59:21 -0500	[thread overview]
Message-ID: <20260527215922.18678-3-vfazio@gmail.com> (raw)
In-Reply-To: <20260527215922.18678-1-vfazio@gmail.com>

PEP 703 [0] discusses making the GIL optional in certain builds of the
CPython interpreter.

This build option was available experiementally in Python 3.13 but has
since been stabilized in Python 3.14 per PEP 779 [1].

According to the porting guide [2], there is no strict requirement that
C extensions must be thread-safe.

Experiments have shown that no logic changes are required if callers
use sychronization mechanisms provided by the Python standard library.

The documentation has been updated to call this out specifically using
terminology from the porting guide [3].

[0]: https://peps.python.org/pep-0703/
[1]: https://peps.python.org/pep-0779/
[2]: https://py-free-threading.github.io/porting/#define-and-document-thread-safety-guarantees
[3]: https://py-free-threading.github.io/documentation-principles/#free-threading-terminology

Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
 bindings/python/README.md                | 8 +++++++-
 bindings/python/gpiod/chip.py            | 3 +++
 bindings/python/gpiod/chip_info.py       | 4 +++-
 bindings/python/gpiod/edge_event.py      | 2 ++
 bindings/python/gpiod/ext/module.c       | 5 ++++-
 bindings/python/gpiod/info_event.py      | 2 ++
 bindings/python/gpiod/line_info.py       | 4 +++-
 bindings/python/gpiod/line_request.py    | 3 +++
 bindings/python/gpiod/line_settings.py   | 2 ++
 bindings/python/pyproject.toml           | 2 +-
 bindings/python/tests/gpiosim/ext.c      | 3 +++
 bindings/python/tests/system/ext.c       | 3 +++
 bindings/python/tests/tests_threading.py | 3 +++
 docs/python_api.rst                      | 6 ++++++
 14 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/bindings/python/README.md b/bindings/python/README.md
index 2faa6f4..f3cd77a 100644
--- a/bindings/python/README.md
+++ b/bindings/python/README.md
@@ -5,10 +5,16 @@
 
 These are the official Python bindings for [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/).
 
+Both GIL-enabled and free-threaded CPython are supported.
+
+The Python bindings, much like the C API they wrap, are not thread-safe and do
+require external synchronization by the caller to serialize access to objects
+shared across threads.
+
 The gpiod library has been vendored into this package for your convenience and
 this version of gpiod is independent from your system package.
 
-Binary wheels are not provided. The source package requires python3-dev.
+Binary wheels are provided for some platforms. The source package requires python3-dev.
 
 ## Rationale
 
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 6b7b32b..2acb872 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -50,6 +50,9 @@ class Chip:
 
         with gpiod.Chip(path="/dev/gpiochip0") as chip:
             do_something(chip)
+
+    Synchronization: objects of this class require external synchronization.
+    Protect calls with a lock when sharing an instance across threads.
     """
 
     def __init__(self, path: str):
diff --git a/bindings/python/gpiod/chip_info.py b/bindings/python/gpiod/chip_info.py
index 737a45e..1a81a9d 100644
--- a/bindings/python/gpiod/chip_info.py
+++ b/bindings/python/gpiod/chip_info.py
@@ -10,7 +10,9 @@ __all__ = ["ChipInfo"]
 @dataclass(frozen=True, repr=False)
 class ChipInfo:
     """
-    Snapshot of a chip's status.
+    Immutable snapshot of a chip's status.
+
+    Synchronization: none required
     """
 
     name: str
diff --git a/bindings/python/gpiod/edge_event.py b/bindings/python/gpiod/edge_event.py
index c888cb2..99bb83d 100644
--- a/bindings/python/gpiod/edge_event.py
+++ b/bindings/python/gpiod/edge_event.py
@@ -13,6 +13,8 @@ __all__ = ["EdgeEvent"]
 class EdgeEvent:
     """
     Immutable object containing data about a single edge event.
+
+    Synchronization: none required
     """
 
     class Type(Enum):
diff --git a/bindings/python/gpiod/ext/module.c b/bindings/python/gpiod/ext/module.c
index e567f07..21ea519 100644
--- a/bindings/python/gpiod/ext/module.c
+++ b/bindings/python/gpiod/ext/module.c
@@ -188,7 +188,10 @@ static int module_exec(PyObject* module)
 
 static struct PyModuleDef_Slot module_slots[] = {
 	{ Py_mod_exec, module_exec },
-	{ },
+#if PY_VERSION_HEX >= 0x030E0000 && defined(Py_GIL_DISABLED)
+	{Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+	{0, NULL},
 };
 
 static PyModuleDef module_def = {
diff --git a/bindings/python/gpiod/info_event.py b/bindings/python/gpiod/info_event.py
index cd2785e..e3dd582 100644
--- a/bindings/python/gpiod/info_event.py
+++ b/bindings/python/gpiod/info_event.py
@@ -14,6 +14,8 @@ __all__ = ["InfoEvent"]
 class InfoEvent:
     """
     Immutable object containing data about a single line info event.
+
+    Synchronization: none required
     """
 
     class Type(Enum):
diff --git a/bindings/python/gpiod/line_info.py b/bindings/python/gpiod/line_info.py
index d31565e..b11ed42 100644
--- a/bindings/python/gpiod/line_info.py
+++ b/bindings/python/gpiod/line_info.py
@@ -12,7 +12,9 @@ __all__ = ["LineInfo"]
 @dataclass(frozen=True, init=False, repr=False)
 class LineInfo:
     """
-    Snapshot of a line's status.
+    Immutable snapshot of a line's status.
+
+    Synchronization: none required
     """
 
     offset: int
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index a271080..dc7cd22 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -26,6 +26,9 @@ __all__ = ["LineRequest"]
 class LineRequest:
     """
     Stores the context of a set of requested GPIO lines.
+
+    Synchronization: objects of this class require external synchronization.
+    Protect calls with a lock when sharing an instance across threads.
     """
 
     def __init__(self, req: _ext.Request):
diff --git a/bindings/python/gpiod/line_settings.py b/bindings/python/gpiod/line_settings.py
index 3752acd..e54bc80 100644
--- a/bindings/python/gpiod/line_settings.py
+++ b/bindings/python/gpiod/line_settings.py
@@ -14,6 +14,8 @@ __all__ = ["LineSettings"]
 class LineSettings:
     """
     Stores a set of line properties.
+
+    Synchronization: none required
     """
 
     direction: Direction = Direction.AS_IS
diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml
index 98bb44c..7c4474a 100644
--- a/bindings/python/pyproject.toml
+++ b/bindings/python/pyproject.toml
@@ -27,6 +27,7 @@ classifiers = [
   "Programming Language :: Python :: 3.12",
   "Programming Language :: Python :: 3.13",
   "Programming Language :: Python :: 3.14",
+  "Programming Language :: Python :: Free Threading :: 3 - Stable",
 ]
 
 [project.urls]
@@ -70,5 +71,4 @@ ignore=[
 
 [tool.cibuildwheel]
 build = "cp*"
-skip = "cp31?t-*"  # Do not build free-threaded wheels
 archs = ["x86_64", "aarch64"]
diff --git a/bindings/python/tests/gpiosim/ext.c b/bindings/python/tests/gpiosim/ext.c
index c15ebf5..cc7bd37 100644
--- a/bindings/python/tests/gpiosim/ext.c
+++ b/bindings/python/tests/gpiosim/ext.c
@@ -367,6 +367,9 @@ static void free_module_state(void *mod)
 
 static struct PyModuleDef_Slot module_slots[] = {
 	{ Py_mod_exec, module_exec },
+#if PY_VERSION_HEX >= 0x030E0000 && defined(Py_GIL_DISABLED)
+	{Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
 	{ 0, NULL },
 };
 
diff --git a/bindings/python/tests/system/ext.c b/bindings/python/tests/system/ext.c
index 8f451fc..8b307f6 100644
--- a/bindings/python/tests/system/ext.c
+++ b/bindings/python/tests/system/ext.c
@@ -68,6 +68,9 @@ static PyMethodDef module_methods[] = {
 };
 
 static struct PyModuleDef_Slot module_slots[] = {
+#if PY_VERSION_HEX >= 0x030E0000 && defined(Py_GIL_DISABLED)
+	{Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
 	{ 0, NULL },
 };
 
diff --git a/bindings/python/tests/tests_threading.py b/bindings/python/tests/tests_threading.py
index a61af5b..6fbf8be 100644
--- a/bindings/python/tests/tests_threading.py
+++ b/bindings/python/tests/tests_threading.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2026 Vincent Fazio <vfazio@gmail.com>
+
 import errno
 import fcntl
 import os
diff --git a/docs/python_api.rst b/docs/python_api.rst
index 2c4f59d..57d45ff 100644
--- a/docs/python_api.rst
+++ b/docs/python_api.rst
@@ -17,6 +17,12 @@ easily through Python scripts, enabling tasks such as reading input values,
 setting outputs, monitoring events, and configuring more fine-grained pin
 options.
 
+The Python bindings, much like the C API they wrap, are not thread-safe and do
+require external synchronization by the caller to serialize access to objects
+shared across threads.
+
+The bindings support both GIL-enabled and free-threaded CPython interpreters.
+
 .. note::
    Python bindings require python3 support and libpython development files for
    building from sources.
-- 
2.43.0


  parent reply	other threads:[~2026-05-27 21:59 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-27 21:59 [libgpiod][PATCH v2 0/3] bindings: python: add support for free-threaded Python Vincent Fazio
2026-05-27 21:59 ` [libgpiod][PATCH v2 1/3] bindings: python: tests: add multi-threaded tests Vincent Fazio
2026-05-27 21:59 ` Vincent Fazio [this message]
2026-05-27 21:59 ` [libgpiod][PATCH v2 3/3] bindings: python: add a changelog Vincent Fazio
2026-05-28  9:03 ` [libgpiod][PATCH v2 0/3] bindings: python: add support for free-threaded Python Bartosz Golaszewski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260527215922.18678-3-vfazio@gmail.com \
    --to=vfazio@gmail.com \
    --cc=brgl@kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox