Skip to contentSkip to conversation

bindings: implement python bindings for libgpiod v2

PATCH In review Bartosz Golaszewski 2 participants 8 messages Oct 26, 2022 – Oct 31, 2022 GPIO

lore
Share & tools
lore
#0 lore raw

v4 cover bindings: implement python bindings for libgpiod v2

This is the fourth iteration of python bindings for libgpiod v2.

The code has been split into high-level python and low-level C layers with the latter only doing the bare minimum.

The data model is mostly based on the C++ one with the main difference being utilizing dynamic typing and keyword arguments in place of the builder pattern. That allows us to reduce the number of methods and objects.

Because python doesn't have RAII, unlike C++, we provide a module-level request_lines() helper as gpiod.Chip(path).request_lines(...) one-liner could lead to the chip left dangling even after the last reference is dropped.

Because python forces us to dynamically allocate objects all the time even for fundamental types (which are also immutable) there's no point in trying to replicate the edge-event buffer. Instead LineRequest.read_edge_event() just returns a list of events.

v3 -> v4:
- use list and dict comprehensions where applicable
- add a helper for detecting GPIO devices in examples
- remove commented out code
- fix whitespace errors
- cache chip_info in the chip object
- rename Chip.map_line() to Chip.line_name_from_id()
- disallow repeating line offsets/names as well as offset-name conflicts in request configs
- don't modify python objects with GIL released (self->chip assignment)
- fix type conversion strings
- fix error check in request_offsets()
- fix type comparison warnings

Bartosz Golaszewski (4):
bindings: python: remove old version
bindings: python: add examples
bindings: python: add tests
bindings: python: implement python bindings for libgpiod v2

 bindings/python/.gitignore                   |    8 +
 bindings/python/Makefile.am                  |   26 +-
 bindings/python/examples/Makefile.am         |   16 +-
 bindings/python/examples/gpiodetect.py       |   17 +-
 bindings/python/examples/gpiofind.py         |   14 +-
 bindings/python/examples/gpioget.py          |   32 +-
 bindings/python/examples/gpioinfo.py         |   38 +-
 bindings/python/examples/gpiomon.py          |   50 +-
 bindings/python/examples/gpioset.py          |   45 +-
 bindings/python/examples/helpers.py          |   15 +
 bindings/python/gpiod/Makefile.am            |   17 +
 bindings/python/gpiod/__init__.py            |   53 +
 bindings/python/gpiod/chip.py                |  329 +++
 bindings/python/gpiod/chip_info.py           |   21 +
 bindings/python/gpiod/edge_event.py          |   46 +
 bindings/python/gpiod/exception.py           |   20 +
 bindings/python/gpiod/ext/Makefile.am        |   11 +
 bindings/python/gpiod/ext/chip.c             |  339 +++
 bindings/python/gpiod/ext/common.c           |   92 +
 bindings/python/gpiod/ext/internal.h         |   21 +
 bindings/python/gpiod/ext/line-config.c      |  133 +
 bindings/python/gpiod/ext/line-settings.c    |  130 +
 bindings/python/gpiod/ext/module.c           |  193 ++
 bindings/python/gpiod/ext/request.c          |  402 +++
 bindings/python/gpiod/info_event.py          |   33 +
 bindings/python/gpiod/internal.py            |   19 +
 bindings/python/gpiod/line.py                |   56 +
 bindings/python/gpiod/line_info.py           |   73 +
 bindings/python/gpiod/line_request.py        |  247 ++
 bindings/python/gpiod/line_settings.py       |   62 +
 bindings/python/gpiodmodule.c                | 2662 ------------------
 bindings/python/setup.py                     |   47 +
 bindings/python/tests/Makefile.am            |   26 +-
 bindings/python/tests/__init__.py            |   17 +
 bindings/python/tests/__main__.py            |   16 +
 bindings/python/tests/gpiod_py_test.py       |  832 ------
 bindings/python/tests/gpiomockupmodule.c     |  309 --
 bindings/python/tests/gpiosim/Makefile.am    |    7 +
 bindings/python/tests/gpiosim/__init__.py    |    4 +
 bindings/python/tests/gpiosim/chip.py        |   65 +
 bindings/python/tests/gpiosim/ext.c          |  345 +++
 bindings/python/tests/helpers.py             |   16 +
 bindings/python/tests/tests_chip.py          |  231 ++
 bindings/python/tests/tests_chip_info.py     |   52 +
 bindings/python/tests/tests_edge_event.py    |  212 ++
 bindings/python/tests/tests_info_event.py    |  189 ++
 bindings/python/tests/tests_line_info.py     |  101 +
 bindings/python/tests/tests_line_request.py  |  485 ++++
 bindings/python/tests/tests_line_settings.py |   79 +
 bindings/python/tests/tests_module.py        |   59 +
 configure.ac                                 |    3 +
 51 files changed, 4386 insertions(+), 3929 deletions(-)
 create mode 100644 bindings/python/.gitignore
 create mode 100644 bindings/python/examples/helpers.py
 create mode 100644 bindings/python/gpiod/Makefile.am
 create mode 100644 bindings/python/gpiod/__init__.py
 create mode 100644 bindings/python/gpiod/chip.py
 create mode 100644 bindings/python/gpiod/chip_info.py
 create mode 100644 bindings/python/gpiod/edge_event.py
 create mode 100644 bindings/python/gpiod/exception.py
 create mode 100644 bindings/python/gpiod/ext/Makefile.am
 create mode 100644 bindings/python/gpiod/ext/chip.c
 create mode 100644 bindings/python/gpiod/ext/common.c
 create mode 100644 bindings/python/gpiod/ext/internal.h
 create mode 100644 bindings/python/gpiod/ext/line-config.c
 create mode 100644 bindings/python/gpiod/ext/line-settings.c
 create mode 100644 bindings/python/gpiod/ext/module.c
 create mode 100644 bindings/python/gpiod/ext/request.c
 create mode 100644 bindings/python/gpiod/info_event.py
 create mode 100644 bindings/python/gpiod/internal.py
 create mode 100644 bindings/python/gpiod/line.py
 create mode 100644 bindings/python/gpiod/line_info.py
 create mode 100644 bindings/python/gpiod/line_request.py
 create mode 100644 bindings/python/gpiod/line_settings.py
 delete mode 100644 bindings/python/gpiodmodule.c
 create mode 100644 bindings/python/setup.py
 create mode 100644 bindings/python/tests/__init__.py
 create mode 100644 bindings/python/tests/__main__.py
 delete mode 100755 bindings/python/tests/gpiod_py_test.py
 delete mode 100644 bindings/python/tests/gpiomockupmodule.c
 create mode 100644 bindings/python/tests/gpiosim/Makefile.am
 create mode 100644 bindings/python/tests/gpiosim/__init__.py
 create mode 100644 bindings/python/tests/gpiosim/chip.py
 create mode 100644 bindings/python/tests/gpiosim/ext.c
 create mode 100644 bindings/python/tests/helpers.py
 create mode 100644 bindings/python/tests/tests_chip.py
 create mode 100644 bindings/python/tests/tests_chip_info.py
 create mode 100644 bindings/python/tests/tests_edge_event.py
 create mode 100644 bindings/python/tests/tests_info_event.py
 create mode 100644 bindings/python/tests/tests_line_info.py
 create mode 100644 bindings/python/tests/tests_line_request.py
 create mode 100644 bindings/python/tests/tests_line_settings.py
 create mode 100644 bindings/python/tests/tests_module.py
Signature · 1 line
2.34.1
#1 lore raw

PATCH v4 1/4 bindings: python: remove old version

This removes v1 python bindings for easier review of v2.

---
 bindings/python/Makefile.am              |   25 -
 bindings/python/examples/Makefile.am     |   10 -
 bindings/python/examples/gpiodetect.py   |   16 -
 bindings/python/examples/gpiofind.py     |   20 -
 bindings/python/examples/gpioget.py      |   25 -
 bindings/python/examples/gpioinfo.py     |   28 -
 bindings/python/examples/gpiomon.py      |   42 -
 bindings/python/examples/gpioset.py      |   25 -
 bindings/python/gpiodmodule.c            | 2662 ----------------------
 bindings/python/tests/Makefile.am        |   13 -
 bindings/python/tests/gpiod_py_test.py   |  832 -------
 bindings/python/tests/gpiomockupmodule.c |  309 ---
 12 files changed, 4007 deletions(-)
 delete mode 100644 bindings/python/Makefile.am
 delete mode 100644 bindings/python/examples/Makefile.am
 delete mode 100755 bindings/python/examples/gpiodetect.py
 delete mode 100755 bindings/python/examples/gpiofind.py
 delete mode 100755 bindings/python/examples/gpioget.py
 delete mode 100755 bindings/python/examples/gpioinfo.py
 delete mode 100755 bindings/python/examples/gpiomon.py
 delete mode 100755 bindings/python/examples/gpioset.py
 delete mode 100644 bindings/python/gpiodmodule.c
 delete mode 100644 bindings/python/tests/Makefile.am
 delete mode 100755 bindings/python/tests/gpiod_py_test.py
 delete mode 100644 bindings/python/tests/gpiomockupmodule.c
bindings/python/Makefile.am deleted +0 -25
deleted file mode 100644
index 4405d8f..0000000
@@ -1,25 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-pyexec_LTLIBRARIES = gpiod.la
-
-gpiod_la_SOURCES = gpiodmodule.c
-
-gpiod_la_CFLAGS = -I$(top_srcdir)/include/
-gpiod_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
-gpiod_la_LDFLAGS = -module -avoid-version
-gpiod_la_LIBADD = $(top_builddir)/lib/libgpiod.la $(PYTHON_LIBS)
-
-SUBDIRS = .
-
-if WITH_TESTS
-
-SUBDIRS += tests
-
-endif
-
-if WITH_EXAMPLES
-
-SUBDIRS += examples
-
-endif
bindings/python/examples/Makefile.am deleted +0 -10
deleted file mode 100644
index 4169469..0000000
@@ -1,10 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-EXTRA_DIST =				\
-		gpiodetect.py		\
-		gpiofind.py		\
-		gpioget.py		\
-		gpioinfo.py		\
-		gpiomon.py		\
-		gpioset.py
bindings/python/examples/gpiodetect.py deleted +0 -16
deleted file mode 100755
index da6ee9a..0000000
@@ -1,16 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-'''Reimplementation of the gpiodetect tool in Python.'''
-
-import gpiod
-import os
-
-if __name__ == '__main__':
-    for entry in os.scandir('/dev/'):
-        if gpiod.is_gpiochip_device(entry.path):
-            with gpiod.Chip(entry.path) as chip:
-                print('{} [{}] ({} lines)'.format(chip.name(),
-                                                  chip.label(),
-                                                  chip.num_lines()))
bindings/python/examples/gpiofind.py deleted +0 -20
deleted file mode 100755
index a9ec734..0000000
@@ -1,20 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-'''Reimplementation of the gpiofind tool in Python.'''
-
-import gpiod
-import os
-import sys
-
-if __name__ == '__main__':
-    for entry in os.scandir('/dev/'):
-        if gpiod.is_gpiochip_device(entry.path):
-            with gpiod.Chip(entry.path) as chip:
-                offset = chip.find_line(sys.argv[1], unique=True)
-                if offset is not None:
-                     print('{} {}'.format(line.owner().name(), offset))
-                     sys.exit(0)
-
-    sys.exit(1)
bindings/python/examples/gpioget.py deleted +0 -25
deleted file mode 100755
index 26a2ced..0000000
@@ -1,25 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-'''Simplified reimplementation of the gpioget tool in Python.'''
-
-import gpiod
-import sys
-
-if __name__ == '__main__':
-    if len(sys.argv) < 3:
-        raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...')
-
-    with gpiod.Chip(sys.argv[1]) as chip:
-        offsets = []
-        for off in sys.argv[2:]:
-            offsets.append(int(off))
-
-        lines = chip.get_lines(offsets)
-        lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
-        vals = lines.get_values()
-
-        for val in vals:
-            print(val, end=' ')
-        print()
bindings/python/examples/gpioinfo.py deleted +0 -28
deleted file mode 100755
index 84188f1..0000000
@@ -1,28 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-'''Simplified reimplementation of the gpioinfo tool in Python.'''
-
-import gpiod
-import os
-
-if __name__ == '__main__':
-    for entry in os.scandir('/dev/'):
-        if gpiod.is_gpiochip_device(entry.path):
-            with gpiod.Chip(entry.path) as chip:
-                print('{} - {} lines:'.format(chip.name(), chip.num_lines()))
-
-                for line in gpiod.LineIter(chip):
-                    offset = line.offset()
-                    name = line.name()
-                    consumer = line.consumer()
-                    direction = line.direction()
-                    active_low = line.is_active_low()
-
-                    print('\tline {:>3}: {:>18} {:>12} {:>8} {:>10}'.format(
-                          offset,
-                          'unnamed' if name is None else name,
-                          'unused' if consumer is None else consumer,
-                          'input' if direction == gpiod.Line.DIRECTION_INPUT else 'output',
-                          'active-low' if active_low else 'active-high'))
bindings/python/examples/gpiomon.py deleted +0 -42

42 lines not on this page · raw message

bindings/python/examples/gpioset.py deleted +0 -25
deleted file mode 100755
index 63e08dc..0000000
@@ -1,25 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-'''Simplified reimplementation of the gpioset tool in Python.'''
-
-import gpiod
-import sys
-
-if __name__ == '__main__':
-    if len(sys.argv) < 3:
-        raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...')
-
-    with gpiod.Chip(sys.argv[1]) as chip:
-        offsets = []
-        values = []
-        for arg in sys.argv[2:]:
-            arg = arg.split('=')
-            offsets.append(int(arg[0]))
-            values.append(int(arg[1]))
-
-        lines = chip.get_lines(offsets)
-        lines.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
-        lines.set_values(values)
-        input()
bindings/python/gpiodmodule.c deleted +0 -2662

2,662 lines not on this page · raw message

bindings/python/tests/Makefile.am deleted +0 -13
deleted file mode 100644
index 972b669..0000000
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-# SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <[email protected]>
-
-dist_bin_SCRIPTS = gpiod_py_test.py
-
-pyexec_LTLIBRARIES = gpiomockup.la
-
-gpiomockup_la_SOURCES = gpiomockupmodule.c
-gpiomockup_la_CFLAGS = -I$(top_srcdir)/tests/mockup/
-gpiomockup_la_CFLAGS += -Wall -Wextra -g -std=gnu89 $(PYTHON_CPPFLAGS)
-gpiomockup_la_LDFLAGS = -module -avoid-version
-gpiomockup_la_LIBADD = $(top_builddir)/tests/mockup/libgpiomockup.la
-gpiomockup_la_LIBADD += $(PYTHON_LIBS)
bindings/python/tests/gpiod_py_test.py deleted +0 -832

134 lines not on this page · raw message

bindings/python/tests/gpiomockupmodule.c deleted +0 -309

309 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Signature · 1 line
2.34.1

Part of the diff is not shown here. View the raw message.

#2 lore raw

PATCH v4 2/4 bindings: python: add examples

In reply to Bartosz Golaszewski

This adds the regular set of example programs implemented using libgpiod python bindings.

---
 bindings/python/examples/Makefile.am   | 10 +++++++
 bindings/python/examples/gpiodetect.py | 15 +++++++++++
 bindings/python/examples/gpiofind.py   | 20 ++++++++++++++
 bindings/python/examples/gpioget.py    | 29 +++++++++++++++++++++
 bindings/python/examples/gpioinfo.py   | 28 ++++++++++++++++++++
 bindings/python/examples/gpiomon.py    | 26 +++++++++++++++++++
 bindings/python/examples/gpioset.py    | 36 ++++++++++++++++++++++++++
 bindings/python/examples/helpers.py    | 15 +++++++++++
 8 files changed, 179 insertions(+)
 create mode 100644 bindings/python/examples/Makefile.am
 create mode 100755 bindings/python/examples/gpiodetect.py
 create mode 100755 bindings/python/examples/gpiofind.py
 create mode 100755 bindings/python/examples/gpioget.py
 create mode 100755 bindings/python/examples/gpioinfo.py
 create mode 100755 bindings/python/examples/gpiomon.py
 create mode 100755 bindings/python/examples/gpioset.py
 create mode 100644 bindings/python/examples/helpers.py
bindings/python/examples/Makefile.am new file +10 -0
new file mode 100644
index 0000000..f42b80e
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = \
+	gpiodetect.py \
+	gpiofind.py \
+	gpioget.py \
+	gpioinfo.py \
+	gpiomon.py \
+	gpioset.py
bindings/python/examples/gpiodetect.py new file +15 -0
new file mode 100755
index 0000000..dc98b03
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Reimplementation of the gpiodetect tool in Python."""
+
+import gpiod
+import os
+
+from helpers import gpio_chips
+
+if __name__ == "__main__":
+    for chip in gpio_chips():
+        info = chip.get_info()
+        print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))
bindings/python/examples/gpiofind.py new file +20 -0
new file mode 100755
index 0000000..d41660d
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Reimplementation of the gpiofind tool in Python."""
+
+import gpiod
+import os
+import sys
+
+if __name__ == "__main__":
+    for entry in os.scandir("/dev/"):
+        if gpiod.is_gpiochip_device(entry.path):
+            with gpiod.Chip(entry.path) as chip:
+                offset = chip.line_offset_from_id(sys.argv[1])
+                if offset is not None:
+                    print("{} {}".format(chip.get_info().name, offset))
+                    sys.exit(0)
+
+    sys.exit(1)
bindings/python/examples/gpioget.py new file +29 -0
new file mode 100755
index 0000000..bf7e0a6
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Simplified reimplementation of the gpioget tool in Python."""
+
+import gpiod
+import sys
+
+from gpiod.line import Direction
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")
+
+    path = sys.argv[1]
+    lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
+
+    request = gpiod.request_lines(
+        path,
+        consumer="gpioget.py",
+        config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)},
+    )
+
+    vals = request.get_values()
+
+    for val in vals:
+        print("{} ".format(val.value), end="")
+    print()
bindings/python/examples/gpioinfo.py new file +28 -0
new file mode 100755
index 0000000..3996dcf
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Simplified reimplementation of the gpioinfo tool in Python."""
+
+import gpiod
+import os
+
+from helpers import gpio_chips
+
+if __name__ == "__main__":
+    for chip in gpio_chips():
+        cinfo = chip.get_info()
+        print("{} - {} lines:".format(cinfo.name, cinfo.num_lines))
+
+        for offset in range(0, cinfo.num_lines):
+            linfo = chip.get_line_info(offset)
+            is_input = linfo.direction == gpiod.line.Direction.INPUT
+            print(
+                "\tline {:>3}: {:>18} {:>12} {:>8} {:>10}".format(
+                    linfo.offset,
+                    linfo.name or "unnamed",
+                    linfo.consumer or "unused",
+                    "input" if is_input else "output",
+                    "active-low" if linfo.active_low else "active-high",
+                )
+            )
bindings/python/examples/gpiomon.py new file +26 -0
new file mode 100755
index 0000000..58d47a5
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Simplified reimplementation of the gpiomon tool in Python."""
+
+import gpiod
+import sys
+
+from gpiod.line import Edge
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        raise TypeError("usage: gpiomon.py <gpiochip> <offset1> <offset2> ...")
+
+    path = sys.argv[1]
+    lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
+
+    with gpiod.request_lines(
+        path,
+        consumer="gpiomon.py",
+        config={tuple(lines): gpiod.LineSettings(edge_detection=Edge.BOTH)},
+    ) as request:
+        while True:
+            for event in request.read_edge_event():
+                print(event)
bindings/python/examples/gpioset.py new file +36 -0
new file mode 100755
index 0000000..372a9a8
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+"""Simplified reimplementation of the gpioset tool in Python."""
+
+import gpiod
+import sys
+
+from gpiod.line import Direction, Value
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        raise TypeError(
+            "usage: gpioset.py <gpiochip> <offset1>=<value1> <offset2>=<value2> ..."
+        )
+
+    path = sys.argv[1]
+
+    def parse_value(arg):
+        x, y = arg.split("=")
+        return (x, Value(int(y)))
+
+    lvs = [parse_value(arg) for arg in sys.argv[2:]]
+    lines = [x[0] for x in lvs]
+    values = dict(lvs)
+
+    request = gpiod.request_lines(
+        path,
+        consumer="gpioset.py",
+        config={tuple(lines): gpiod.LineSettings(direction=Direction.OUTPUT)},
+    )
+
+    vals = request.set_values(values)
+
+    input()
bindings/python/examples/helpers.py new file +15 -0
new file mode 100644
index 0000000..8b91173
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+import gpiod
+import os
+
+
+def gpio_chips():
+    for path in [
+        entry.path
+        for entry in os.scandir("/dev/")
+        if gpiod.is_gpiochip_device(entry.path)
+    ]:
+        with gpiod.Chip(path) as chip:
+            yield chip
Signature · 1 line
2.34.1
#3 lore raw

Re: [libgpiod v2][PATCH v4 2/4] bindings: python: add examples

Quoted 2 lines · Bartosz Golaszewski

This adds the regular set of example programs implemented using libgpiod python bindings.

...

bindings/python/examples/gpiodetect.py line 15

Quoted 4 lines
bindings/python/examples/gpiodetect.py new file +4 -0
+if __name__ == "__main__":
+    for chip in gpio_chips():
+        info = chip.get_info()
+        print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))

In all of them I would prefer to see the main() explicitly, like

def main():

	...

if __name__ == "__main__":

    main()

(In this case the module can be imported by another one and main be reused)

Also have you considered use of SystemExit() wrapper?

...

bindings/python/examples/gpiofind.py line 20

Quoted 3 lines
bindings/python/examples/gpiofind.py new file +3 -0
+                    sys.exit(0)
+
+    sys.exit(1)

Is it in the original C code?!
I would expect that no chips -- no error.

...

bindings/python/examples/gpioget.py line 14

Quoted 3 lines
bindings/python/examples/gpioget.py new file +3 -0
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")

SystemExit(main(sys.argv)) ?

Quoted 5 lines
bindings/python/examples/gpioget.py new file +5 -0
+    path = sys.argv[1]
+    lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
+
+    request = gpiod.request_lines(
+        path,

bindings/python/examples/gpioget.py line 29

Quoted 10 lines
bindings/python/examples/gpioget.py new file +9 -0
+        consumer="gpioget.py",
+        config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)},
+    )
+
+    vals = request.get_values()
+
+    for val in vals:
+        print("{} ".format(val.value), end="")
 
+    print()

Without any conditional it will print an empty line, was it originally in the C variant?

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#4 lore raw

Re: [libgpiod v2][PATCH v4 2/4] bindings: python: add examples

Quoted 55 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski

This adds the regular set of example programs implemented using libgpiod python bindings.

...

bindings/python/examples/gpiodetect.py line 15

Quoted 4 lines
bindings/python/examples/gpiodetect.py new file +4 -0
+if __name__ == "__main__":
+    for chip in gpio_chips():
+        info = chip.get_info()
+        print("{} [{}] ({} lines)".format(info.name, info.label, info.num_lines))

In all of them I would prefer to see the main() explicitly, like

def main():

        ...

if __name__ == "__main__":

    main()

(In this case the module can be imported by another one and main be reused)

Also have you considered use of SystemExit() wrapper?

...

bindings/python/examples/gpiofind.py line 20

Quoted 3 lines
bindings/python/examples/gpiofind.py new file +3 -0
+                    sys.exit(0)
+
+    sys.exit(1)

Is it in the original C code?!
I would expect that no chips -- no error.

...

bindings/python/examples/gpioget.py line 14

Quoted 3 lines
bindings/python/examples/gpioget.py new file +3 -0
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        raise TypeError("usage: gpioget.py <gpiochip> <offset1> <offset2> ...")

SystemExit(main(sys.argv)) ?

Quoted 13 lines
bindings/python/examples/gpioget.py new file +13 -0
+    path = sys.argv[1]
+    lines = [int(line) if line.isdigit() else line for line in sys.argv[2:]]
+
+    request = gpiod.request_lines(
+        path,
+        consumer="gpioget.py",
+        config={tuple(lines): gpiod.LineSettings(direction=Direction.INPUT)},
+    )
+
+    vals = request.get_values()
+
+    for val in vals:
+        print("{} ".format(val.value), end="")
Quoted 10 lines
Quoted 1 line

+ print()

Without any conditional it will print an empty line, was it originally in the C variant?

--
With Best Regards,
Andy Shevchenko

Thanks Andy but this is unnecessary churn, these are literally just code samples. Unless some new issues pop up for the other patches, I'll leave it like that and apply it to master. Then we can work on it further in there.

Bart

#5 lore raw

PATCH v4 3/4 bindings: python: add tests

In reply to Bartosz Golaszewski

This adds a test-suite for python bindings based on the gpio-sim kernel module.

---
 bindings/python/tests/Makefile.am            |  17 +
 bindings/python/tests/__init__.py            |  17 +
 bindings/python/tests/__main__.py            |  16 +
 bindings/python/tests/gpiosim/Makefile.am    |   7 +
 bindings/python/tests/gpiosim/__init__.py    |   4 +
 bindings/python/tests/gpiosim/chip.py        |  65 +++
 bindings/python/tests/gpiosim/ext.c          | 345 +++++++++++++
 bindings/python/tests/helpers.py             |  16 +
 bindings/python/tests/tests_chip.py          | 231 +++++++++
 bindings/python/tests/tests_chip_info.py     |  52 ++
 bindings/python/tests/tests_edge_event.py    | 212 ++++++++
 bindings/python/tests/tests_info_event.py    | 189 ++++++++
 bindings/python/tests/tests_line_info.py     | 101 ++++
 bindings/python/tests/tests_line_request.py  | 485 +++++++++++++++++++
 bindings/python/tests/tests_line_settings.py |  79 +++
 bindings/python/tests/tests_module.py        |  59 +++
 16 files changed, 1895 insertions(+)
 create mode 100644 bindings/python/tests/Makefile.am
 create mode 100644 bindings/python/tests/__init__.py
 create mode 100644 bindings/python/tests/__main__.py
 create mode 100644 bindings/python/tests/gpiosim/Makefile.am
 create mode 100644 bindings/python/tests/gpiosim/__init__.py
 create mode 100644 bindings/python/tests/gpiosim/chip.py
 create mode 100644 bindings/python/tests/gpiosim/ext.c
 create mode 100644 bindings/python/tests/helpers.py
 create mode 100644 bindings/python/tests/tests_chip.py
 create mode 100644 bindings/python/tests/tests_chip_info.py
 create mode 100644 bindings/python/tests/tests_edge_event.py
 create mode 100644 bindings/python/tests/tests_info_event.py
 create mode 100644 bindings/python/tests/tests_line_info.py
 create mode 100644 bindings/python/tests/tests_line_request.py
 create mode 100644 bindings/python/tests/tests_line_settings.py
 create mode 100644 bindings/python/tests/tests_module.py
bindings/python/tests/Makefile.am new file +17 -0
new file mode 100644
index 0000000..7dcdebb
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+SUBDIRS = gpiosim
+
+EXTRA_DIST = \
+	helpers.py \
+	__init__.py \
+	__main__.py \
+	tests_chip_info.py \
+	tests_chip.py \
+	tests_edge_event.py \
+	tests_info_event.py \
+	tests_line_info.py \
+	tests_line_request.py \
+	tests_line_settings.py \
+	tests_module.py
bindings/python/tests/__init__.py new file +17 -0
new file mode 100644
index 0000000..2bf14e6
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+import os
+import unittest
+
+from packaging import version
+
+required_kernel_version = "5.19.0"
+current_version = os.uname().release.split("-")[0]
+
+if version.parse(current_version) < version.parse(required_kernel_version):
+    raise NotImplementedError(
+        "linux kernel version must be at least {} - got {}".format(
+            required_kernel_version, current_version
+        )
+    )
bindings/python/tests/__main__.py new file +16 -0
new file mode 100644
index 0000000..b5d7f0a
@@ -0,0 +1,16 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+import unittest
+
+from .tests_chip import *
+from .tests_chip_info import *
+from .tests_edge_event import *
+from .tests_info_event import *
+from .tests_line_info import *
+from .tests_line_settings import *
+from .tests_module import *
+from .tests_line_request import *
+
+unittest.main()
bindings/python/tests/gpiosim/Makefile.am new file +7 -0
new file mode 100644
index 0000000..7004f3a
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = \
+	chip.py \
+	ext.c \
+	__init__.py
bindings/python/tests/gpiosim/__init__.py new file +4 -0
new file mode 100644
index 0000000..f65e413
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+from .chip import Chip
bindings/python/tests/gpiosim/chip.py new file +65 -0

65 lines not on this page · raw message

bindings/python/tests/gpiosim/ext.c new file +345 -0

345 lines not on this page · raw message

bindings/python/tests/helpers.py new file +16 -0
new file mode 100644
index 0000000..f9a15e8
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+import os
+
+
+class LinkGuard:
+    def __init__(self, src, dst):
+        self.src = src
+        self.dst = dst
+
+    def __enter__(self):
+        os.symlink(self.src, self.dst)
+
+    def __exit__(self, type, val, tb):
+        os.unlink(self.dst)
bindings/python/tests/tests_chip.py new file +231 -0

231 lines not on this page · raw message

bindings/python/tests/tests_chip_info.py new file +52 -0

52 lines not on this page · raw message

bindings/python/tests/tests_edge_event.py new file +212 -0

212 lines not on this page · raw message

bindings/python/tests/tests_info_event.py new file +189 -0

189 lines not on this page · raw message

bindings/python/tests/tests_line_info.py new file +101 -0

101 lines not on this page · raw message

bindings/python/tests/tests_line_request.py new file +485 -0

485 lines not on this page · raw message

bindings/python/tests/tests_line_settings.py new file +79 -0

79 lines not on this page · raw message

bindings/python/tests/tests_module.py new file +59 -0

59 lines not on this page · raw message

Signature · 1 line
2.34.1
#6 lore raw

PATCH v4 4/4 bindings: python: implement python bindings for libgpiod v2

In reply to Bartosz Golaszewski

This adds python bindings for libgpiod v2. As opposed to v1, they are mostly written in python with just low-level elements written in C and interfacing with libgpiod.so.

We've also added setup.py which will allow to use pip for managing the bindings and split them into a separate meta-openembedded recipe.

---
 bindings/python/.gitignore                |   8 +
 bindings/python/Makefile.am               |  35 ++
 bindings/python/gpiod/Makefile.am         |  17 +
 bindings/python/gpiod/__init__.py         |  53 +++
 bindings/python/gpiod/chip.py             | 329 ++++++++++++++++++
 bindings/python/gpiod/chip_info.py        |  21 ++
 bindings/python/gpiod/edge_event.py       |  46 +++
 bindings/python/gpiod/exception.py        |  20 ++
 bindings/python/gpiod/ext/Makefile.am     |  11 +
 bindings/python/gpiod/ext/chip.c          | 339 ++++++++++++++++++
 bindings/python/gpiod/ext/common.c        |  92 +++++
 bindings/python/gpiod/ext/internal.h      |  21 ++
 bindings/python/gpiod/ext/line-config.c   | 133 +++++++
 bindings/python/gpiod/ext/line-settings.c | 130 +++++++
 bindings/python/gpiod/ext/module.c        | 193 +++++++++++
 bindings/python/gpiod/ext/request.c       | 402 ++++++++++++++++++++++
 bindings/python/gpiod/info_event.py       |  33 ++
 bindings/python/gpiod/internal.py         |  19 +
 bindings/python/gpiod/line.py             |  56 +++
 bindings/python/gpiod/line_info.py        |  73 ++++
 bindings/python/gpiod/line_request.py     | 247 +++++++++++++
 bindings/python/gpiod/line_settings.py    |  62 ++++
 bindings/python/setup.py                  |  47 +++
 configure.ac                              |   3 +
 24 files changed, 2390 insertions(+)
 create mode 100644 bindings/python/.gitignore
 create mode 100644 bindings/python/Makefile.am
 create mode 100644 bindings/python/gpiod/Makefile.am
 create mode 100644 bindings/python/gpiod/__init__.py
 create mode 100644 bindings/python/gpiod/chip.py
 create mode 100644 bindings/python/gpiod/chip_info.py
 create mode 100644 bindings/python/gpiod/edge_event.py
 create mode 100644 bindings/python/gpiod/exception.py
 create mode 100644 bindings/python/gpiod/ext/Makefile.am
 create mode 100644 bindings/python/gpiod/ext/chip.c
 create mode 100644 bindings/python/gpiod/ext/common.c
 create mode 100644 bindings/python/gpiod/ext/internal.h
 create mode 100644 bindings/python/gpiod/ext/line-config.c
 create mode 100644 bindings/python/gpiod/ext/line-settings.c
 create mode 100644 bindings/python/gpiod/ext/module.c
 create mode 100644 bindings/python/gpiod/ext/request.c
 create mode 100644 bindings/python/gpiod/info_event.py
 create mode 100644 bindings/python/gpiod/internal.py
 create mode 100644 bindings/python/gpiod/line.py
 create mode 100644 bindings/python/gpiod/line_info.py
 create mode 100644 bindings/python/gpiod/line_request.py
 create mode 100644 bindings/python/gpiod/line_settings.py
 create mode 100644 bindings/python/setup.py
bindings/python/.gitignore new file +8 -0
new file mode 100644
index 0000000..b603068
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2017-2022 Bartosz Golaszewski <[email protected]>
+
+build/
+__pycache__/
+dist/
+gpiod.egg-info/
+*.so
bindings/python/Makefile.am new file +35 -0
new file mode 100644
index 0000000..3212a8f
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = setup.py
+
+if WITH_TESTS
+
+BUILD_TESTS = 1
+
+endif
+
+all-local:
+	GPIOD_VERSION_STRING=$(VERSION_STR) \
+	GPIOD_WITH_TESTS=$(BUILD_TESTS) \
+	$(PYTHON) setup.py build_ext --inplace \
+		--include-dirs=$(top_srcdir)/include/:$(top_srcdir)/tests/gpiosim/ \
+		--library-dirs=$(top_builddir)/lib/.libs/:$(top_srcdir)/tests/gpiosim/.libs/
+
+install-exec-local:
+	GPIOD_WITH_TESTS= \
+	$(PYTHON) setup.py install --prefix=$(prefix)
+
+SUBDIRS = gpiod
+
+if WITH_TESTS
+
+SUBDIRS += tests
+
+endif
+
+if WITH_EXAMPLES
+
+SUBDIRS += examples
+
+endif
bindings/python/gpiod/Makefile.am new file +17 -0
new file mode 100644
index 0000000..278f823
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+SUBDIRS = ext
+
+EXTRA_DIST = \
+	chip_info.py \
+	chip.py \
+	edge_event.py \
+	exception.py \
+	info_event.py \
+	__init__.py \
+	internal.py \
+	line_info.py \
+	line.py \
+	line_request.py \
+	line_settings.py
bindings/python/gpiod/__init__.py new file +53 -0

53 lines not on this page · raw message

bindings/python/gpiod/chip.py new file +329 -0

329 lines not on this page · raw message

bindings/python/gpiod/chip_info.py new file +21 -0
new file mode 100644
index 0000000..a506b55
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True, repr=False)
+class ChipInfo:
+    """
+    Snapshot of a chip's status.
+    """
+
+    name: str
+    label: str
+    num_lines: int
+
+    def __str__(self):
+        return '<ChipInfo name="{}" label="{}" num_lines={}>'.format(
+            self.name, self.label, self.num_lines
+        )
bindings/python/gpiod/edge_event.py new file +46 -0

46 lines not on this page · raw message

bindings/python/gpiod/exception.py new file +20 -0
new file mode 100644
index 0000000..07ffaa6
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+
+class ChipClosedError(Exception):
+    """
+    Error raised when an already closed chip is used.
+    """
+
+    def __init__(self):
+        super().__init__("I/O operation on closed chip")
+
+
+class RequestReleasedError(Exception):
+    """
+    Error raised when a released request is used.
+    """
+
+    def __init__(self):
+        super().__init__("GPIO lines have been released")
bindings/python/gpiod/ext/Makefile.am new file +11 -0
new file mode 100644
index 0000000..9c81b17
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+EXTRA_DIST = \
+	chip.c \
+	common.c \
+	internal.h \
+	line-config.c \
+	line-settings.c \
+	module.c \
+	request.c
bindings/python/gpiod/ext/chip.c new file +339 -0

339 lines not on this page · raw message

bindings/python/gpiod/ext/common.c new file +92 -0

92 lines not on this page · raw message

bindings/python/gpiod/ext/internal.h new file +21 -0
new file mode 100644
index 0000000..210fdf1
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]> */
+
+#ifndef __LIBGPIOD_PYTHON_MODULE_H__
+#define __LIBGPIOD_PYTHON_MODULE_H__
+
+#include <gpiod.h>
+#include <Python.h>
+
+PyObject *_Py_gpiod_SetErrFromErrno(const char *filename);
+#define Py_gpiod_SetErrFromErrno() _Py_gpiod_SetErrFromErrno(__FILE__)
+
+PyObject *Py_gpiod_GetGlobalType(const char *type_name);
+unsigned int Py_gpiod_PyLongAsUnsignedInt(PyObject *pylong);
+void Py_gpiod_dealloc(PyObject *self);
+PyObject *Py_gpiod_MakeRequestObject(struct gpiod_line_request *request,
+				     size_t event_buffer_size);
+struct gpiod_line_config *Py_gpiod_LineConfigGetData(PyObject *obj);
+struct gpiod_line_settings *Py_gpiod_LineSettingsGetData(PyObject *obj);
+
+#endif /* __LIBGPIOD_PYTHON_MODULE_H__ */
bindings/python/gpiod/ext/line-config.c new file +133 -0

133 lines not on this page · raw message

bindings/python/gpiod/ext/line-settings.c new file +130 -0

130 lines not on this page · raw message

bindings/python/gpiod/ext/module.c new file +193 -0

193 lines not on this page · raw message

bindings/python/gpiod/ext/request.c new file +402 -0

402 lines not on this page · raw message

bindings/python/gpiod/info_event.py new file +33 -0
new file mode 100644
index 0000000..78b1459
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+from . import _ext
+from .line_info import LineInfo
+from dataclasses import dataclass
+from enum import Enum
+
+
+@dataclass(frozen=True, init=False, repr=False)
+class InfoEvent:
+    """
+    Immutable object containing data about a single line info event.
+    """
+
+    class Type(Enum):
+        LINE_REQUESTED = _ext.INFO_EVENT_TYPE_LINE_REQUESTED
+        LINE_RELEASED = _ext.INFO_EVENT_TYPE_LINE_RELEASED
+        LINE_CONFIG_CHANGED = _ext.INFO_EVENT_TYPE_LINE_CONFIG_CHANGED
+
+    event_type: Type
+    timestamp_ns: int
+    line_info: LineInfo
+
+    def __init__(self, event_type: int, timestamp_ns: int, line_info: LineInfo):
+        object.__setattr__(self, "event_type", InfoEvent.Type(event_type))
+        object.__setattr__(self, "timestamp_ns", timestamp_ns)
+        object.__setattr__(self, "line_info", line_info)
+
+    def __str__(self):
+        return "<InfoEvent type={} timestamp_ns={} line_info={}>".format(
+            self.event_type, self.timestamp_ns, self.line_info
+        )
bindings/python/gpiod/internal.py new file +19 -0
new file mode 100644
index 0000000..37e8b62
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <[email protected]>
+
+from datetime import timedelta
+from select import select
+from typing import Optional, Union
+
+
+def poll_fd(fd: int, timeout: Optional[Union[timedelta, float]] = None) -> bool:
+    if timeout is None:
+        timeout = 0.0
+
+    if isinstance(timeout, timedelta):
+        sec = timeout.total_seconds()
+    else:
+        sec = timeout
+
+    readable, _, _ = select([fd], [], [], sec)
+    return True if fd in readable else False
bindings/python/gpiod/line.py new file +56 -0

56 lines not on this page · raw message

bindings/python/gpiod/line_info.py new file +73 -0

73 lines not on this page · raw message

bindings/python/gpiod/line_request.py new file +247 -0

247 lines not on this page · raw message

bindings/python/gpiod/line_settings.py new file +62 -0

62 lines not on this page · raw message

bindings/python/setup.py new file +47 -0

47 lines not on this page · raw message

configure.ac +3 -0
index 6ac1d8e..048b2ac 100644
@@ -244,8 +244,11 @@ AC_CONFIG_FILES([Makefile
 		 bindings/cxx/examples/Makefile
 		 bindings/cxx/tests/Makefile
 		 bindings/python/Makefile
+		 bindings/python/gpiod/Makefile
+		 bindings/python/gpiod/ext/Makefile
 		 bindings/python/examples/Makefile
 		 bindings/python/tests/Makefile
+		 bindings/python/tests/gpiosim/Makefile
 		 man/Makefile])
 
 AC_OUTPUT
Signature · 1 line
2.34.1
#7 lore raw
In reply to Bartosz Golaszewski
Quoted 21 lines · Bartosz Golaszewski

This is the fourth iteration of python bindings for libgpiod v2.

The code has been split into high-level python and low-level C layers with the latter only doing the bare minimum.

The data model is mostly based on the C++ one with the main difference being utilizing dynamic typing and keyword arguments in place of the builder pattern. That allows us to reduce the number of methods and objects.

Because python doesn't have RAII, unlike C++, we provide a module-level request_lines() helper as gpiod.Chip(path).request_lines(...) one-liner could lead to the chip left dangling even after the last reference is dropped.

Because python forces us to dynamically allocate objects all the time even for fundamental types (which are also immutable) there's no point in trying to replicate the edge-event buffer. Instead LineRequest.read_edge_event() just returns a list of events.

Quoted 13 lines

v3 -> v4:
- use list and dict comprehensions where applicable
- add a helper for detecting GPIO devices in examples
- remove commented out code
- fix whitespace errors
- cache chip_info in the chip object
- rename Chip.map_line() to Chip.line_name_from_id()
- disallow repeating line offsets/names as well as offset-name conflicts in request configs
- don't modify python objects with GIL released (self->chip assignment)
- fix type conversion strings
- fix error check in request_offsets()
- fix type comparison warnings

I applied these patches, squashed the entire libgpiod v2 patchset to preserve bisectability and pulled the entire thing into the master branch. Let's continue the development from here. The new release is definitely not done yet but let's just have some solid base to work on.

Bart