* [libgpiod][PATCH 1/9] bindings: python: make config iteration consistent
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 2/9] bindings: python: remove unused attribute from LineRequest Vincent Fazio
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, Chip.request_lines and LineRequest.reconfigure_lines
iterated over a config object but did so in subtly different ways.
Create a generator that iterates over a config object and returns line +
settings pairs. Update the aforementioned functions to use the generator
to consolidate and condense code.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/_internal.py | 22 ++++++++++++++--
bindings/python/gpiod/chip.py | 38 ++++++++++++---------------
bindings/python/gpiod/line_request.py | 12 +++------
3 files changed, 41 insertions(+), 31 deletions(-)
diff --git a/bindings/python/gpiod/_internal.py b/bindings/python/gpiod/_internal.py
index 37f6661..ee15796 100644
--- a/bindings/python/gpiod/_internal.py
+++ b/bindings/python/gpiod/_internal.py
@@ -1,11 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+from __future__ import annotations
+
from datetime import timedelta
from select import select
-from typing import Optional, Union
+from typing import TYPE_CHECKING, Optional, Union
+
+if TYPE_CHECKING:
+ from collections.abc import Generator, Iterable
+
+ from .line_settings import LineSettings
-__all__ = ["poll_fd"]
+__all__ = ["poll_fd", "config_iter"]
def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
@@ -17,3 +24,14 @@ def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
readable, _, _ = select([fd], [], [], sec)
return True if fd in readable else False
+
+
+def config_iter(
+ config: dict[Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]],
+) -> Generator[tuple[Union[int, str], Optional[LineSettings]]]:
+ for key, settings in config.items():
+ if isinstance(key, int) or isinstance(key, str):
+ yield key, settings
+ else:
+ for subkey in key:
+ yield subkey, settings
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 169488a..9f38ab7 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -8,7 +8,7 @@ from errno import ENOENT
from typing import TYPE_CHECKING, Optional, Union, cast
from . import _ext
-from ._internal import poll_fd
+from ._internal import config_iter, poll_fd
from .exception import ChipClosedError
from .line import Value
from .line_request import LineRequest
@@ -305,28 +305,24 @@ class Chip:
offset_map = dict()
global_output_values = list()
- for lines, settings in config.items():
+ for line, settings in config_iter(config):
offsets = list()
- if isinstance(lines, int) or isinstance(lines, str):
- lines = (lines,)
-
- for line in lines:
- offset = self.line_offset_from_id(line)
- offsets.append(offset)
-
- # If there's a global output value for this offset, store it in the
- # list for later.
- if mapped_output_values:
- global_output_values.append(
- mapped_output_values[offset]
- if offset in mapped_output_values
- else Value.INACTIVE
- )
-
- if isinstance(line, str):
- name_map[line] = offset
- offset_map[offset] = line
+ offset = self.line_offset_from_id(line)
+ offsets.append(offset)
+
+ # If there's a global output value for this offset, store it in the
+ # list for later.
+ if mapped_output_values:
+ global_output_values.append(
+ mapped_output_values[offset]
+ if offset in mapped_output_values
+ else Value.INACTIVE
+ )
+
+ if isinstance(line, str):
+ name_map[line] = offset
+ offset_map[offset] = line
line_cfg.add_line_settings(
offsets, _line_settings_to_ext(settings or LineSettings())
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index 69f86f6..7956e54 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -6,7 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union, cast
from . import _ext
-from ._internal import poll_fd
+from ._internal import config_iter, poll_fd
from .exception import RequestReleasedError
from .line_settings import LineSettings, _line_settings_to_ext
@@ -174,13 +174,9 @@ class LineRequest:
line_cfg = _ext.LineConfig()
line_settings = {}
- for lines, settings in config.items():
- if isinstance(lines, int) or isinstance(lines, str):
- lines = [lines]
-
- for line in lines:
- offset = self._line_to_offset(line)
- line_settings[offset] = settings
+ for line, settings in config_iter(config):
+ offset = self._line_to_offset(line)
+ line_settings[offset] = settings
for offset in self.offsets:
settings = line_settings.get(offset) or LineSettings()
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 2/9] bindings: python: remove unused attribute from LineRequest
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 1/9] bindings: python: make config iteration consistent Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 3/9] bindings: python: chip: track requested lines when enumerating Vincent Fazio
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
The _offset_map attribute was a map of offset -> line name. Values were
only inserted when a line was requested via name. As this map is never
utilized, remove it.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/chip.py | 1 -
bindings/python/gpiod/line_request.py | 1 -
2 files changed, 2 deletions(-)
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 9f38ab7..ba2877d 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -339,7 +339,6 @@ class Chip:
request._chip_name = req_internal.chip_name
request._offsets = req_internal.offsets
request._name_map = name_map
- request._offset_map = offset_map
request._lines = [
offset_map[off] if off in offset_map else off for off in request.offsets
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index 7956e54..c0798b5 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -36,7 +36,6 @@ class LineRequest:
self._chip_name: str
self._offsets: list[int]
self._name_map: dict[str, int]
- self._offset_map: dict[int, str]
self._lines: list[Union[int, str]]
def __bool__(self) -> bool:
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 3/9] bindings: python: chip: track requested lines when enumerating
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 1/9] bindings: python: make config iteration consistent Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 2/9] bindings: python: remove unused attribute from LineRequest Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 4/9] bindings: python: chip: simplify duplicate checking Vincent Fazio
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, an offset map was maintained to lookup the name used to
request a line so that a list could be generated that reflected the
identifier used to request a given line.
For example, if a config looked like {0: None, ("foo", "bar"): None}, the
LineRequest.lines property would contain [0, "foo", "bar"].
Now, the line identifier is tracked in request order as the config
object is iterated to avoid maintaining an offset map.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/chip.py | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index ba2877d..23662ef 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -302,7 +302,7 @@ class Chip:
mapped_output_values = None
name_map = dict()
- offset_map = dict()
+ requested_lines = list()
global_output_values = list()
for line, settings in config_iter(config):
@@ -310,6 +310,7 @@ class Chip:
offset = self.line_offset_from_id(line)
offsets.append(offset)
+ requested_lines.append(line)
# If there's a global output value for this offset, store it in the
# list for later.
@@ -322,7 +323,6 @@ class Chip:
if isinstance(line, str):
name_map[line] = offset
- offset_map[offset] = line
line_cfg.add_line_settings(
offsets, _line_settings_to_ext(settings or LineSettings())
@@ -340,9 +340,7 @@ class Chip:
request._offsets = req_internal.offsets
request._name_map = name_map
- request._lines = [
- offset_map[off] if off in offset_map else off for off in request.offsets
- ]
+ request._lines = requested_lines
return request
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 4/9] bindings: python: chip: simplify duplicate checking
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (2 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 3/9] bindings: python: chip: track requested lines when enumerating Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 5/9] bindings: python: chip: check mapped_output_values membership once Vincent Fazio
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, a check was performed to prevent requests that included
duplicative lines.
While this provided quick feedback on erroneous requests, it penalized
callers that had a good config due to the over head of iterating the
config multiple times.
Now, duplicate check is performed as the config object is iterated.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/chip.py | 31 +++++++------------------------
1 file changed, 7 insertions(+), 24 deletions(-)
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 23662ef..1f4a9bd 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -3,7 +3,6 @@
from __future__ import annotations
-from collections import Counter
from errno import ENOENT
from typing import TYPE_CHECKING, Optional, Union, cast
@@ -236,20 +235,6 @@ class Chip:
self._check_closed()
return cast("_ext.Chip", self._chip).read_info_event()
- def _resolve_config_keys_to_offsets(
- self,
- config_keys: Iterable[Union[Iterable[Union[int, str]], int, str]],
- ) -> list[int]:
- offsets: list[int] = list()
- for key in config_keys:
- # perform strict int/str check since str is also Iterable
- if isinstance(key, (int, str)):
- offsets.append(self.line_offset_from_id(key))
- else: # key is an iterable with multiple IDs to resolve
- for item in key:
- offsets.append(self.line_offset_from_id(item))
- return offsets
-
def request_lines(
self,
config: dict[
@@ -283,15 +268,6 @@ class Chip:
line_cfg = _ext.LineConfig()
- # Sanitize lines - don't allow offset repetitions or offset-name conflicts.
- for offset, count in Counter(
- self._resolve_config_keys_to_offsets(config.keys())
- ).items():
- if count != 1:
- raise ValueError(
- f"line must be configured exactly once - offset {offset} repeats"
- )
-
# If we have global output values - map line names to offsets
if output_values:
mapped_output_values = {
@@ -304,11 +280,18 @@ class Chip:
name_map = dict()
requested_lines = list()
global_output_values = list()
+ seen_offsets = set()
for line, settings in config_iter(config):
offsets = list()
offset = self.line_offset_from_id(line)
+ # don't allow offset repetitions or offset-name conflicts.
+ if offset in seen_offsets:
+ raise ValueError(
+ f"line must be configured exactly once - offset {offset} repeats"
+ )
+ seen_offsets.add(offset)
offsets.append(offset)
requested_lines.append(line)
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 5/9] bindings: python: chip: check mapped_output_values membership once
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (3 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 4/9] bindings: python: chip: simplify duplicate checking Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 6/9] bindings: python: line_request: ignore invalid line names in reconfigure_lines Vincent Fazio
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Simplify looking up mapped output values by providing a default value to
dict.get instead of checking for membership in the dict and then
fetching the value subsequently.
While hashing and lookup is fast, it's unnecessary overhead.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/chip.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 1f4a9bd..2e66018 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -269,13 +269,12 @@ class Chip:
line_cfg = _ext.LineConfig()
# If we have global output values - map line names to offsets
+ mapped_output_values = None
if output_values:
mapped_output_values = {
self.line_offset_from_id(line): value
for line, value in output_values.items()
}
- else:
- mapped_output_values = None
name_map = dict()
requested_lines = list()
@@ -299,9 +298,7 @@ class Chip:
# list for later.
if mapped_output_values:
global_output_values.append(
- mapped_output_values[offset]
- if offset in mapped_output_values
- else Value.INACTIVE
+ mapped_output_values.get(offset, Value.INACTIVE)
)
if isinstance(line, str):
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 6/9] bindings: python: line_request: ignore invalid line names in reconfigure_lines
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (4 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 5/9] bindings: python: chip: check mapped_output_values membership once Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 7/9] bindings: python: ext: add ability to query line name Vincent Fazio
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, calling `reconfigure_lines` with line identifiers that were
not included in the original request produced different behaviors
depending on the identifier type.
If the identifier was an integer, it would be silently ignored.
If the identifier was a string, it would raise a `ValueError`.
To be consistent, invalid line names are now silently ignored.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/line_request.py | 19 +++++++++++--------
bindings/python/tests/tests_line_request.py | 13 +++++++++++++
2 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index c0798b5..629df3c 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -94,12 +94,9 @@ class LineRequest:
def _line_to_offset(self, line: Union[int, str]) -> int:
if isinstance(line, int):
return line
- else:
- _line: Union[int, None]
- if (_line := self._name_map.get(line)) is None:
- raise ValueError(f"unknown line name: {line}")
- else:
- return _line
+ if (_line := self._name_map.get(line)) is None:
+ raise ValueError(f"unknown line name: {line}")
+ return _line
def get_values(
self, lines: Optional[Iterable[Union[int, str]]] = None
@@ -174,8 +171,14 @@ class LineRequest:
line_settings = {}
for line, settings in config_iter(config):
- offset = self._line_to_offset(line)
- line_settings[offset] = settings
+ try:
+ offset = self._line_to_offset(line)
+ line_settings[offset] = settings
+ except ValueError:
+ # _line_to_offset will raise a ValueError when it encounters
+ # an unrecognized line name. Ignore these like we do offsets
+ # that were not in the original request.
+ pass
for offset in self.offsets:
settings = line_settings.get(offset) or LineSettings()
diff --git a/bindings/python/tests/tests_line_request.py b/bindings/python/tests/tests_line_request.py
index 217c299..66e9e8d 100644
--- a/bindings/python/tests/tests_line_request.py
+++ b/bindings/python/tests/tests_line_request.py
@@ -596,6 +596,19 @@ class ReconfigureRequestedLines(TestCase):
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.INPUT)
+ def test_reconfigure_extra_names(self) -> None:
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {
+ (0, 2, "foo", "baz", "buzz"): gpiod.LineSettings(
+ direction=Direction.INPUT
+ )
+ }
+ )
+ info = self.chip.get_line_info(2)
+ self.assertEqual(info.direction, Direction.INPUT)
+
class ReleasedLineRequestCannotBeUsed(TestCase):
def test_using_released_line_request(self) -> None:
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 7/9] bindings: python: ext: add ability to query line name
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (5 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 6/9] bindings: python: line_request: ignore invalid line names in reconfigure_lines Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 8/9] bindings: python: chip: map names for lines requested by offset Vincent Fazio
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Investigation has shown that the construction of the Enum values to
create the LineInfo object is expensive [0].
Add a method to retrieve the line name from a given offset without the
overhead of generating a full LineInfo object.
[0]: https://github.com/brgl/libgpiod/issues/149#issuecomment-3319448712
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/_ext.pyi | 1 +
bindings/python/gpiod/ext/chip.c | 31 +++++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/bindings/python/gpiod/_ext.pyi b/bindings/python/gpiod/_ext.pyi
index 1beb80d..31fd352 100644
--- a/bindings/python/gpiod/_ext.pyi
+++ b/bindings/python/gpiod/_ext.pyi
@@ -47,6 +47,7 @@ class Chip:
def get_info(self) -> ChipInfo: ...
def line_offset_from_id(self, id: str) -> int: ...
def get_line_info(self, offset: int, watch: bool) -> LineInfo: ...
+ def get_line_name(self, offset: int) -> Optional[str]: ...
def request_lines(
self,
line_cfg: LineConfig,
diff --git a/bindings/python/gpiod/ext/chip.c b/bindings/python/gpiod/ext/chip.c
index 1e97d7d..98435d9 100644
--- a/bindings/python/gpiod/ext/chip.c
+++ b/bindings/python/gpiod/ext/chip.c
@@ -147,6 +147,32 @@ static PyObject *chip_get_line_info(chip_object *self, PyObject *args)
return info_obj;
}
+static PyObject *chip_get_line_name(chip_object *self, PyObject *args)
+{
+ int ret;
+ unsigned int offset;
+ struct gpiod_line_info *info;
+ PyObject *line_name;
+ const char *name;
+
+ ret = PyArg_ParseTuple(args, "I", &offset);
+ if (!ret)
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS;
+ info = gpiod_chip_get_line_info(self->chip, offset);
+ Py_END_ALLOW_THREADS;
+ if (!info)
+ return Py_gpiod_SetErrFromErrno();
+
+ name = gpiod_line_info_get_name(info);
+ line_name = (name == NULL) ? Py_None : PyUnicode_FromString(name);
+
+ gpiod_line_info_free(info);
+
+ return line_name;
+}
+
static PyObject *
chip_unwatch_line_info(chip_object *self, PyObject *args)
{
@@ -333,6 +359,11 @@ static PyMethodDef chip_methods[] = {
.ml_meth = (PyCFunction)chip_request_lines,
.ml_flags = METH_VARARGS,
},
+ {
+ .ml_name = "get_line_name",
+ .ml_meth = (PyCFunction)chip_get_line_name,
+ .ml_flags = METH_VARARGS,
+ },
{ }
};
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 8/9] bindings: python: chip: map names for lines requested by offset
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (6 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 7/9] bindings: python: ext: add ability to query line name Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-09 13:05 ` [libgpiod][PATCH 9/9] bindings: python: line_request: warn on unknown lines when reconfiguring Vincent Fazio
2025-10-13 15:31 ` [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Bartosz Golaszewski
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, lines requested by name could also be referred to by offset
when calling LineRequest.reconfigure_lines. However, lines requested by
offset could not be reconfigured by name if they had one.
Now, the name map is populated if the line has a name regardless of if
it's requested by name or offset.
To make behavior as consistent with earlier versions as possible, a line
requested by name always returns the first line on the chip with that
name. If there are duplicate line names and lines are requested by
offset, the first line in the request is mapped to the name. A line
requested by name always overrides a name mapped via an offset request.
If lines 0-3 all have the same name and a config object looks like:
{("foo", 1, 2): None}
or
{(1, "foo", 2): None}
Then lines 0-2 are requested and line 0 can be reconfigured by name.
If the config object looks like:
{(1, 2): None}
Then lines 1 and 2 are requested and line 1 can be reconfigured by name.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/chip.py | 9 ++++++
bindings/python/tests/tests_line_request.py | 35 ++++++++++++++++++---
2 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index 2e66018..a98fce6 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -302,7 +302,16 @@ class Chip:
)
if isinstance(line, str):
+ # lines specifically requested by name overwrite any entries
name_map[line] = offset
+ else:
+ name = cast("_ext.Chip", self._chip).get_line_name(offset)
+ # Only track lines with actual names. Names from offsets do not
+ # overwrite existing entries (such as requests by name). So if
+ # multiple lines have the same name, the first in the request
+ # list is the one addressable by name
+ if name and name not in name_map:
+ name_map[name] = offset
line_cfg.add_line_settings(
offsets, _line_settings_to_ext(settings or LineSettings())
diff --git a/bindings/python/tests/tests_line_request.py b/bindings/python/tests/tests_line_request.py
index 66e9e8d..aa2cd83 100644
--- a/bindings/python/tests/tests_line_request.py
+++ b/bindings/python/tests/tests_line_request.py
@@ -71,7 +71,9 @@ class ModuleLineRequestsBehaveCorrectlyWithInvalidArguments(TestCase):
class ChipLineRequestWorks(TestCase):
def setUp(self) -> None:
- self.sim = gpiosim.Chip(num_lines=8, line_names={5: "foo", 7: "bar"})
+ self.sim = gpiosim.Chip(
+ num_lines=8, line_names={0: "fizz", 1: "fizz", 5: "foo", 7: "bar"}
+ )
self.chip = gpiod.Chip(self.sim.dev_path)
def tearDown(self) -> None:
@@ -113,6 +115,16 @@ class ChipLineRequestWorks(TestCase):
with self.chip.request_lines(config={"foo": None}) as req:
self.assertEqual(req.offsets, [5])
+ def test_request_line_with_duplicate_names_by_offset(self) -> None:
+ with self.chip.request_lines(config={(1, 0): None}) as req:
+ self.assertEqual(req.offsets, [1, 0])
+ self.assertEqual(req.lines, [1, 0])
+
+ def test_request_line_with_duplicate_names_mixed_mode(self) -> None:
+ with self.chip.request_lines(config={(1, "fizz"): None}) as req:
+ self.assertEqual(req.offsets, [1, 0])
+ self.assertEqual(req.lines, [1, "fizz"])
+
class ModuleLineRequestWorks(TestCase):
def setUp(self) -> None:
@@ -500,11 +512,13 @@ class LineRequestSetOutputValues(TestCase):
class ReconfigureRequestedLines(TestCase):
def setUp(self) -> None:
- self.sim = gpiosim.Chip(num_lines=8, line_names={3: "foo", 4: "bar", 6: "baz"})
+ self.sim = gpiosim.Chip(
+ num_lines=8, line_names={2: "fizz", 3: "foo", 4: "bar", 6: "baz", 7: "foo"}
+ )
self.chip = gpiod.Chip(self.sim.dev_path)
self.req = self.chip.request_lines(
{
- (0, 2, "foo", "baz"): gpiod.LineSettings(
+ (7, 0, 2, "foo", "baz"): gpiod.LineSettings(
direction=Direction.OUTPUT, active_low=True, drive=Drive.OPEN_DRAIN
)
}
@@ -530,7 +544,7 @@ class ReconfigureRequestedLines(TestCase):
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.OUTPUT)
self.req.reconfigure_lines(
- {(0, 2, "foo", "baz"): gpiod.LineSettings(direction=Direction.INPUT)}
+ {(0, "fizz", "foo", "baz"): gpiod.LineSettings(direction=Direction.INPUT)}
)
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.INPUT)
@@ -609,6 +623,19 @@ class ReconfigureRequestedLines(TestCase):
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.INPUT)
+ def test_reconfigure_duplicate_names_with_offset(self) -> None:
+ info3 = self.chip.get_line_info(3)
+ info7 = self.chip.get_line_info(7)
+ self.assertEqual(info3.direction, Direction.OUTPUT)
+ self.assertEqual(info7.direction, Direction.OUTPUT)
+ self.req.reconfigure_lines(
+ {("foo", 7): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ info3 = self.chip.get_line_info(3)
+ info7 = self.chip.get_line_info(7)
+ self.assertEqual(info3.direction, Direction.INPUT)
+ self.assertEqual(info7.direction, Direction.INPUT)
+
class ReleasedLineRequestCannotBeUsed(TestCase):
def test_using_released_line_request(self) -> None:
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [libgpiod][PATCH 9/9] bindings: python: line_request: warn on unknown lines when reconfiguring
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (7 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 8/9] bindings: python: chip: map names for lines requested by offset Vincent Fazio
@ 2025-10-09 13:05 ` Vincent Fazio
2025-10-13 15:31 ` [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Bartosz Golaszewski
9 siblings, 0 replies; 11+ messages in thread
From: Vincent Fazio @ 2025-10-09 13:05 UTC (permalink / raw)
To: linux-gpio; +Cc: vfazio, Vincent Fazio
Previously, attempting to reconfigure a line that was not included in
the original request provided no feedback to the caller that there was
invalid data in the request.
Now, emit a warning when an unknown offset or line name is encountered.
Signed-off-by: Vincent Fazio <vfazio@gmail.com>
---
bindings/python/gpiod/line_request.py | 14 ++++++++--
bindings/python/tests/tests_line_request.py | 29 ++++++++++++++-------
2 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index 629df3c..deb48a7 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -3,6 +3,7 @@
from __future__ import annotations
+import warnings
from typing import TYPE_CHECKING, Optional, Union, cast
from . import _ext
@@ -173,12 +174,21 @@ class LineRequest:
for line, settings in config_iter(config):
try:
offset = self._line_to_offset(line)
- line_settings[offset] = settings
+ if offset in self.offsets:
+ line_settings[offset] = settings
+ else:
+ warnings.warn(
+ f"Line offset '{offset}' was not included in original request.",
+ stacklevel=2,
+ )
except ValueError:
# _line_to_offset will raise a ValueError when it encounters
# an unrecognized line name. Ignore these like we do offsets
# that were not in the original request.
- pass
+ warnings.warn(
+ f"Line name '{line}' was not included in original request.",
+ stacklevel=2,
+ )
for offset in self.offsets:
settings = line_settings.get(offset) or LineSettings()
diff --git a/bindings/python/tests/tests_line_request.py b/bindings/python/tests/tests_line_request.py
index aa2cd83..8cb0f2c 100644
--- a/bindings/python/tests/tests_line_request.py
+++ b/bindings/python/tests/tests_line_request.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
+import warnings
from unittest import TestCase
import gpiod
@@ -604,22 +605,30 @@ class ReconfigureRequestedLines(TestCase):
def test_reconfigure_extra_offsets(self) -> None:
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.OUTPUT)
- self.req.reconfigure_lines(
- {(0, 2, 3, 6, 5): gpiod.LineSettings(direction=Direction.INPUT)}
- )
+ with warnings.catch_warnings(record=True) as w:
+ self.req.reconfigure_lines(
+ {(0, 2, 3, 6, 5): gpiod.LineSettings(direction=Direction.INPUT)}
+ )
+ assert len(w) == 1
+ assert issubclass(w[0].category, UserWarning)
+ assert "Line offset '5'" in str(w[0].message)
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.INPUT)
def test_reconfigure_extra_names(self) -> None:
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.OUTPUT)
- self.req.reconfigure_lines(
- {
- (0, 2, "foo", "baz", "buzz"): gpiod.LineSettings(
- direction=Direction.INPUT
- )
- }
- )
+ with warnings.catch_warnings(record=True) as w:
+ self.req.reconfigure_lines(
+ {
+ (0, 2, "foo", "baz", "buzz"): gpiod.LineSettings(
+ direction=Direction.INPUT
+ )
+ }
+ )
+ assert len(w) == 1
+ assert issubclass(w[0].category, UserWarning)
+ assert "Line name 'buzz'" in str(w[0].message)
info = self.chip.get_line_info(2)
self.assertEqual(info.direction, Direction.INPUT)
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration
2025-10-09 13:05 [libgpiod][PATCH 0/9] bindings: python: improve line requests and reconfiguration Vincent Fazio
` (8 preceding siblings ...)
2025-10-09 13:05 ` [libgpiod][PATCH 9/9] bindings: python: line_request: warn on unknown lines when reconfiguring Vincent Fazio
@ 2025-10-13 15:31 ` Bartosz Golaszewski
9 siblings, 0 replies; 11+ messages in thread
From: Bartosz Golaszewski @ 2025-10-13 15:31 UTC (permalink / raw)
To: linux-gpio, Vincent Fazio; +Cc: Bartosz Golaszewski, vfazio
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
On Thu, 09 Oct 2025 08:05:06 -0500, Vincent Fazio wrote:
> This series makes a number of minor improvements to consistency, speed,
> and code clarity for line requests and reconfiguration.
>
> Patch 1 makes iterating through a configuration object consistent across
> line requests and reconfiguration and helps condense code, making it a
> bit more readable.
>
> [...]
Thanks! Great series, neatly split into well described commits. All applied.
I wasn't aware of the warnings module, thanks for bringing it to my attention.
[1/9] bindings: python: make config iteration consistent
https://git.kernel.org/brgl/libgpiod/c/8c9d5b4fdfd846a48043f7f01fbd70c727c0bcca
[2/9] bindings: python: remove unused attribute from LineRequest
https://git.kernel.org/brgl/libgpiod/c/11aa4254b2e5b67ba6c0b64c82d2647d2d8b3f3a
[3/9] bindings: python: chip: track requested lines when enumerating
https://git.kernel.org/brgl/libgpiod/c/728a2f9b22f6f1b944780632972b36d73e1c8db6
[4/9] bindings: python: chip: simplify duplicate checking
https://git.kernel.org/brgl/libgpiod/c/ea21b447d7f5631349bc53cf408ba88cc3ff68c8
[5/9] bindings: python: chip: check mapped_output_values membership once
https://git.kernel.org/brgl/libgpiod/c/0110c11626572a5aedd8b3487ccbb39980e85dc3
[6/9] bindings: python: line_request: ignore invalid line names in reconfigure_lines
https://git.kernel.org/brgl/libgpiod/c/e2d7b6abaf013f495b9875b28f556ccc25461085
[7/9] bindings: python: ext: add ability to query line name
https://git.kernel.org/brgl/libgpiod/c/0c0993569c54f573914656d30e2126957d933600
[8/9] bindings: python: chip: map names for lines requested by offset
https://git.kernel.org/brgl/libgpiod/c/6a7d3306e4afa86103c6af5d37a5fd3c04504d3b
[9/9] bindings: python: line_request: warn on unknown lines when reconfiguring
https://git.kernel.org/brgl/libgpiod/c/962a1924e106ffd3d9f2b7f03dbdfd71ebf701cf
Best regards,
--
Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
^ permalink raw reply [flat|nested] 11+ messages in thread