bindings: implement python bindings
authorBartosz Golaszewski <bartekgola@gmail.com>
Mon, 19 Feb 2018 11:52:38 +0000 (12:52 +0100)
committerBartosz Golaszewski <bartekgola@gmail.com>
Tue, 17 Apr 2018 08:19:34 +0000 (10:19 +0200)
Create a C extension module wrapping the functionality of libgpiod
in a set of Python classes.

Add code examples including simplified reimplementations of gpio-tools
in Python.

Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com>
13 files changed:
.gitignore
bindings/Makefile.am
bindings/python/Makefile.am [new file with mode: 0644]
bindings/python/examples/Makefile.am [new file with mode: 0644]
bindings/python/examples/gpiod_tests.py [new file with mode: 0755]
bindings/python/examples/gpiodetect.py [new file with mode: 0755]
bindings/python/examples/gpiofind.py [new file with mode: 0755]
bindings/python/examples/gpioget.py [new file with mode: 0755]
bindings/python/examples/gpioinfo.py [new file with mode: 0755]
bindings/python/examples/gpiomon.py [new file with mode: 0755]
bindings/python/examples/gpioset.py [new file with mode: 0755]
bindings/python/gpiodmodule.c [new file with mode: 0644]
configure.ac

index 03dd0a5b0e68acad56e6ce99b77681667e2ed67e..f16a9df60224d3bae457720f73383300cf88c2b7 100644 (file)
@@ -17,6 +17,7 @@ gpiomoncxx
 gpiosetcxx
 *.o
 *.lo
+*.la
 doc
 libgpiod.pc
 libgpiodcxx.pc
index 7d5643708ad412c7e00895d3e55412ad0ef38ba2..24e2c98ea8f0785a6401bd96d7eb598b99a9a0ac 100644 (file)
@@ -13,3 +13,9 @@ if WITH_BINDINGS_CXX
 SUBDIRS += cxx
 
 endif
+
+if WITH_BINDINGS_PYTHON
+
+SUBDIRS += python
+
+endif
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
new file mode 100644 (file)
index 0000000..e14191b
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+SUBDIRS = . examples
+
+pyexec_LTLIBRARIES = gpiod.la
+
+gpiod_la_SOURCES = gpiodmodule.c
+
+gpiod_la_CFLAGS = -I$(top_srcdir)/include/ -include $(top_builddir)/config.h
+gpiod_la_CFLAGS += $(PYTHON_CPPFLAGS) -Wall -Wextra -g
+gpiod_la_LDFLAGS = -module -avoid-version
+gpiod_la_LIBADD = $(top_builddir)/src/lib/libgpiod.la
diff --git a/bindings/python/examples/Makefile.am b/bindings/python/examples/Makefile.am
new file mode 100644 (file)
index 0000000..2745718
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+EXTRA_DIST =   gpiod_tests.py \
+               gpiodetect.py \
+               gpiofind.py \
+               gpioget.py \
+               gpioinfo.py \
+               gpiomon.py \
+               gpioset.py
diff --git a/bindings/python/examples/gpiod_tests.py b/bindings/python/examples/gpiod_tests.py
new file mode 100755 (executable)
index 0000000..e6765bc
--- /dev/null
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Misc tests of libgpiod python bindings.'''
+
+import gpiod
+import sys
+
+test_cases = []
+
+def add_test(name, func):
+    global test_cases
+
+    test_cases.append((name, func))
+
+def chip_open():
+    chip = gpiod.Chip('gpiochip0')
+    chip = gpiod.Chip('/dev/gpiochip0', gpiod.Chip.OPEN_BY_PATH)
+    chip = gpiod.Chip('gpiochip0', gpiod.Chip.OPEN_BY_NAME)
+    chip = gpiod.Chip('gpio-mockup-A', gpiod.Chip.OPEN_BY_LABEL)
+    chip = gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER)
+    chip = gpiod.Chip('gpio-mockup-A', gpiod.Chip.OPEN_LOOKUP)
+    print('All good')
+
+add_test('Open a GPIO chip using different modes', chip_open)
+
+def chip_open_no_args():
+    try:
+        chip = gpiod.Chip()
+    except TypeError:
+        print('Error as expected')
+        return
+
+    assert False, 'TypeError expected'
+
+add_test('Open a GPIO chip without arguments', chip_open_no_args)
+
+def chip_info():
+    chip = gpiod.Chip('gpiochip0')
+    print('name: {}'.format(chip.name()))
+    print('label: {}'.format(chip.label()))
+    print('lines: {}'.format(chip.num_lines()))
+
+add_test('Print chip info', chip_info)
+
+def print_chip():
+    chip = gpiod.Chip('/dev/gpiochip0')
+    print(chip)
+
+add_test('Print chip object', print_chip)
+
+def create_line_object():
+    try:
+        line = gpiod.Line()
+    except NotImplementedError:
+        print('Error as expected')
+        return
+
+    assert False, 'NotImplementedError expected'
+
+add_test('Create a line object - should fail', create_line_object)
+
+def print_line():
+    chip = gpiod.Chip('gpio-mockup-A')
+    line = chip.get_line(3)
+    print(line)
+
+add_test('Print line object', print_line)
+
+def find_line():
+    line = gpiod.find_line('gpio-mockup-A-4')
+    print('found line - offset: {}'.format(line.offset()))
+
+add_test('Find line globally', find_line)
+
+def create_empty_line_bulk():
+    try:
+        lines = gpiod.LineBulk()
+    except TypeError:
+        print('Error as expected')
+        return
+
+    assert False, 'TypeError expected'
+
+add_test('Create a line bulk object - should fail', create_empty_line_bulk)
+
+def get_lines():
+    chip = gpiod.Chip('gpio-mockup-A')
+    lines = chip.get_lines([2, 4, 5, 7])
+    print('Retrieved lines:')
+    for line in lines:
+        print(line)
+
+add_test('Get lines from chip', get_lines)
+
+def create_line_bulk_from_lines():
+    chip = gpiod.Chip('gpio-mockup-A')
+    line1 = chip.get_line(2)
+    line2 = chip.get_line(4)
+    line3 = chip.get_line(6)
+    lines = gpiod.LineBulk([line1, line2, line3])
+    print('Created LineBulk:')
+    print(lines)
+
+add_test('Create a LineBulk from a list of lines', create_line_bulk_from_lines)
+
+def line_bulk_to_list():
+    chip = gpiod.Chip('gpio-mockup-A')
+    lines = chip.get_lines((1, 2, 3))
+    print(lines.to_list())
+
+add_test('Convert a LineBulk to a list', line_bulk_to_list)
+
+def get_value_single_line():
+    chip = gpiod.Chip('gpio-mockup-A')
+    line = chip.get_line(2)
+    line.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
+    print('line value: {}'.format(line.get_value()))
+
+add_test('Get value - single line', get_value_single_line)
+
+def set_value_single_line():
+    chip = gpiod.Chip('gpiochip0')
+    line = chip.get_line(3)
+    line.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
+    print('line value before: {}'.format(line.get_value()))
+    line.release()
+    line.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_OUT)
+    line.set_value(1)
+    line.release()
+    line.request(consumer=sys.argv[0], type=gpiod.LINE_REQ_DIR_IN)
+    print('line value after: {}'.format(line.get_value()))
+
+add_test('Set value - single line', set_value_single_line)
+
+for name, func in test_cases:
+    print('==============================================')
+    print('{}:'.format(name))
+    func()
diff --git a/bindings/python/examples/gpiodetect.py b/bindings/python/examples/gpiodetect.py
new file mode 100755 (executable)
index 0000000..c13d4b3
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Reimplementation of the gpiodetect tool in Python.'''
+
+import gpiod
+
+for chip in gpiod.ChipIter():
+    print('{} [{}] ({} lines)'.format(chip.name(),
+                                      chip.label(),
+                                      chip.num_lines()))
diff --git a/bindings/python/examples/gpiofind.py b/bindings/python/examples/gpiofind.py
new file mode 100755 (executable)
index 0000000..3e50bdc
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Reimplementation of the gpiofind tool in Python.'''
+
+import gpiod
+import sys
+
+line = gpiod.find_line(sys.argv[1])
+print('{} {}'.format(line.owner().name(), line.offset()))
diff --git a/bindings/python/examples/gpioget.py b/bindings/python/examples/gpioget.py
new file mode 100755 (executable)
index 0000000..587db06
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioget tool in Python.'''
+
+import gpiod
+import sys
+
+if len(sys.argv) < 3:
+    raise TypeError('usage: gpioget.py <gpiochip> <offset1> <offset2> ...')
+
+chip = gpiod.Chip(sys.argv[1])
+
+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()
diff --git a/bindings/python/examples/gpioinfo.py b/bindings/python/examples/gpioinfo.py
new file mode 100755 (executable)
index 0000000..4612d5b
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioinfo tool in Python.'''
+
+import gpiod
+
+for chip in gpiod.ChipIter():
+    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_state = line.active_state()
+
+        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_state == gpiod.Line.ACTIVE_LOW else 'active-high'))
diff --git a/bindings/python/examples/gpiomon.py b/bindings/python/examples/gpiomon.py
new file mode 100755 (executable)
index 0000000..35e4310
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpiomon tool in Python.'''
+
+import gpiod
+import sys
+
+def print_event(event):
+    if event.type == gpiod.LineEvent.RISING_EDGE:
+        print(' RISING EDGE', end='')
+    elif event.type == gpiod.LineEvent.FALLING_EDGE:
+        print('FALLING EDGE', end='')
+    else:
+        raise TypeError('Invalid event type')
+
+    print(' {}.{} line: {}'.format(event.sec, event.nsec, event.source.offset()))
+
+if len(sys.argv) < 3:
+    raise TypeError('usage: gpiomon.py <gpiochip> <offset1> <offset2> ...')
+
+chip = gpiod.Chip(sys.argv[1])
+
+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_EV_BOTH_EDGES)
+
+try:
+    while True:
+        ev_lines = lines.event_wait(sec=1)
+        if ev_lines:
+            for line in ev_lines:
+                event = line.event_read()
+                print_event(event)
+except KeyboardInterrupt:
+    sys.exit(130)
diff --git a/bindings/python/examples/gpioset.py b/bindings/python/examples/gpioset.py
new file mode 100755 (executable)
index 0000000..9e76b98
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+#
+# This file is part of libgpiod.
+#
+# Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+#
+
+'''Simplified reimplementation of the gpioset tool in Python.'''
+
+import gpiod
+import sys
+
+if len(sys.argv) < 3:
+    raise TypeError('usage: gpioset.py <gpiochip> <offset1>=<value1> ...')
+
+chip = gpiod.Chip(sys.argv[1])
+
+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)
+vals = lines.set_values(values)
diff --git a/bindings/python/gpiodmodule.c b/bindings/python/gpiodmodule.c
new file mode 100644 (file)
index 0000000..d1e6f1e
--- /dev/null
@@ -0,0 +1,1767 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libgpiod.
+ *
+ * Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
+ */
+
+#include <Python.h>
+#include <gpiod.h>
+
+typedef struct {
+       PyObject_HEAD
+       struct gpiod_chip *chip;
+} gpiod_ChipObject;
+
+typedef struct {
+       PyObject_HEAD
+       struct gpiod_line *line;
+       gpiod_ChipObject *owner;
+} gpiod_LineObject;
+
+typedef struct {
+       PyObject_HEAD
+       struct gpiod_line_event event;
+       gpiod_LineObject *source;
+} gpiod_LineEventObject;
+
+typedef struct {
+       PyObject_HEAD
+       PyObject **lines;
+       Py_ssize_t num_lines;
+       Py_ssize_t iter_idx;
+} gpiod_LineBulkObject;
+
+typedef struct {
+       PyObject_HEAD
+       struct gpiod_chip_iter *iter;
+} gpiod_ChipIterObject;
+
+typedef struct {
+       PyObject_HEAD
+       struct gpiod_line_iter *iter;
+       gpiod_ChipObject *owner;
+} gpiod_LineIterObject;
+
+static PyObject *gpiod_Chip_name(gpiod_ChipObject *self);
+static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line);
+static PyObject *gpiod_LineBulk_request(gpiod_LineBulkObject *self,
+                                       PyObject *args, PyObject *kwds);
+static PyObject *gpiod_LineBulk_get_values(gpiod_LineBulkObject *self);
+static PyObject *gpiod_LineBulk_set_values(gpiod_LineBulkObject *self,
+                                          PyObject *args);
+static PyObject *gpiod_LineBulk_event_wait(gpiod_LineBulkObject *self,
+                                          PyObject *args, PyObject *kwds);
+static PyObject *gpiod_LineBulk_release(gpiod_LineBulkObject *self);
+static gpiod_LineObject *gpiod_MakeLineObject(gpiod_ChipObject *owner,
+                                             struct gpiod_line *line);
+static PyObject *gpiod_Line_repr(gpiod_LineObject *self);
+
+enum {
+       gpiod_LINE_REQ_DIR_AS_IS = 1,
+       gpiod_LINE_REQ_DIR_IN,
+       gpiod_LINE_REQ_DIR_OUT,
+       gpiod_LINE_REQ_EV_FALLING_EDGE,
+       gpiod_LINE_REQ_EV_RISING_EDGE,
+       gpiod_LINE_REQ_EV_BOTH_EDGES,
+};
+
+enum {
+       gpiod_LINE_REQ_FLAG_OPEN_DRAIN          = GPIOD_BIT(0),
+       gpiod_LINE_REQ_FLAG_OPEN_SOURCE         = GPIOD_BIT(1),
+       gpiod_LINE_REQ_FLAG_ACTIVE_LOW          = GPIOD_BIT(2),
+};
+
+enum {
+       gpiod_DIRECTION_INPUT = 1,
+       gpiod_DIRECTION_OUTPUT,
+};
+
+enum {
+       gpiod_ACTIVE_HIGH = 1,
+       gpiod_ACTIVE_LOW,
+};
+
+enum {
+       gpiod_RISING_EDGE = 1,
+       gpiod_FALLING_EDGE,
+};
+
+static int gpiod_LineEvent_init(void)
+{
+       PyErr_SetString(PyExc_NotImplementedError,
+                       "Only gpiod.Line can create new LineEvent objects.");
+       return -1;
+}
+
+static void gpiod_LineEvent_dealloc(gpiod_LineEventObject *self)
+{
+       if (self->source)
+               Py_DECREF(self->source);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_type_doc,
+"Event type of this line event.");
+
+PyObject *gpiod_LineEvent_get_type(gpiod_LineEventObject *self)
+{
+       int ret;
+
+       if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+               ret = gpiod_RISING_EDGE;
+       else
+               ret = gpiod_FALLING_EDGE;
+
+       return Py_BuildValue("I", ret);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_sec_doc,
+"Seconds value of the line event timestamp.");
+
+PyObject *gpiod_LineEvent_get_sec(gpiod_LineEventObject *self)
+{
+       return Py_BuildValue("I", self->event.ts.tv_sec);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_nsec_doc,
+"Nanoseconds value of the line event timestamp.");
+
+PyObject *gpiod_LineEvent_get_nsec(gpiod_LineEventObject *self)
+{
+       return Py_BuildValue("I", self->event.ts.tv_nsec);
+}
+
+PyDoc_STRVAR(gpiod_LineEvent_get_source_doc,
+"Line object representing the GPIO line on which this event occurred.");
+
+gpiod_LineObject *gpiod_LineEvent_get_source(gpiod_LineEventObject *self)
+{
+       Py_INCREF(self->source);
+       return self->source;
+}
+
+static PyGetSetDef gpiod_LineEvent_getset[] = {
+       {
+               .name = "type",
+               .get = (getter)gpiod_LineEvent_get_type,
+               .doc = gpiod_LineEvent_get_type_doc,
+       },
+       {
+               .name = "sec",
+               .get = (getter)gpiod_LineEvent_get_sec,
+               .doc = gpiod_LineEvent_get_sec_doc,
+       },
+       {
+               .name = "nsec",
+               .get = (getter)gpiod_LineEvent_get_nsec,
+               .doc = gpiod_LineEvent_get_nsec_doc,
+       },
+       {
+               .name = "source",
+               .get = (getter)gpiod_LineEvent_get_source,
+               .doc = gpiod_LineEvent_get_source_doc,
+       },
+       { }
+};
+
+static PyObject *gpiod_LineEvent_repr(gpiod_LineEventObject *self)
+{
+       PyObject *line_repr, *ret;
+       const char *edge;
+
+       if (self->event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+               edge = "RISING EDGE";
+       else
+               edge = "FALLING EDGE";
+
+       line_repr = gpiod_Line_repr(self->source);
+
+       ret = PyUnicode_FromFormat("'%s (%ld.%ld) source(%S)'",
+                                  edge, self->event.ts.tv_sec,
+                                  self->event.ts.tv_nsec, line_repr);
+       Py_DECREF(line_repr);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_LineEventType_doc,
+"Represents a single GPIO line event.");
+
+static PyTypeObject gpiod_LineEventType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.LineEvent",
+       .tp_basicsize = sizeof(gpiod_LineEventObject),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_LineEventType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_LineEvent_init,
+       .tp_dealloc = (destructor)gpiod_LineEvent_dealloc,
+       .tp_getset = gpiod_LineEvent_getset,
+       .tp_repr = (reprfunc)gpiod_LineEvent_repr,
+};
+
+static int gpiod_Line_init(void)
+{
+       PyErr_SetString(PyExc_NotImplementedError,
+                       "Only gpiod.Chip can create new Line objects.");
+       return -1;
+}
+
+static void gpiod_Line_dealloc(gpiod_LineObject *self)
+{
+       if (self->owner)
+               Py_DECREF(self->owner);
+}
+
+PyDoc_STRVAR(gpiod_Line_owner_doc,
+"Get the GPIO chip owning this line.");
+
+static PyObject *gpiod_Line_owner(gpiod_LineObject *self)
+{
+       Py_INCREF(self->owner);
+       return (PyObject *)self->owner;
+}
+
+PyDoc_STRVAR(gpiod_Line_offset_doc,
+"Get the offset of the GPIO line.");
+
+static PyObject *gpiod_Line_offset(gpiod_LineObject *self)
+{
+       return Py_BuildValue("I", gpiod_line_offset(self->line));
+}
+
+PyDoc_STRVAR(gpiod_Line_name_doc,
+"Get the name of the GPIO line.");
+
+static PyObject *gpiod_Line_name(gpiod_LineObject *self)
+{
+       const char *name = gpiod_line_name(self->line);
+
+       if (name)
+               return PyUnicode_FromFormat("%s", name);
+
+       Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_Line_consumer_doc,
+"Get the consumer of the GPIO line.");
+
+static PyObject *gpiod_Line_consumer(gpiod_LineObject *self)
+{
+       const char *consumer = gpiod_line_consumer(self->line);
+
+       if (consumer)
+               return PyUnicode_FromFormat("%s", consumer);
+
+       Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_Line_direction_doc,
+"Get the direction setting of this GPIO line.");
+
+static PyObject *gpiod_Line_direction(gpiod_LineObject *self)
+{
+       PyObject *ret;
+       int dir;
+
+       dir = gpiod_line_direction(self->line);
+
+       if (dir == GPIOD_LINE_DIRECTION_INPUT)
+               ret = Py_BuildValue("I", gpiod_DIRECTION_INPUT);
+       else
+               ret = Py_BuildValue("I", gpiod_DIRECTION_OUTPUT);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_active_state_doc,
+"Get the active state setting of this GPIO line.");
+
+static PyObject *gpiod_Line_active_state(gpiod_LineObject *self)
+{
+       PyObject *ret;
+       int active;
+
+       active = gpiod_line_active_state(self->line);
+
+       if (active == GPIOD_LINE_ACTIVE_STATE_HIGH)
+               ret = Py_BuildValue("I", gpiod_ACTIVE_HIGH);
+       else
+               ret = Py_BuildValue("I", gpiod_ACTIVE_LOW);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_used_doc,
+"Check if this line is used by the kernel or other user space process.");
+
+static PyObject *gpiod_Line_is_used(gpiod_LineObject *self)
+{
+       if (gpiod_line_is_used(self->line))
+               Py_RETURN_TRUE;
+
+       Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_open_drain_doc,
+"Check if this line represents an open-drain GPIO.");
+
+static PyObject *gpiod_Line_is_open_drain(gpiod_LineObject *self)
+{
+       if (gpiod_line_is_open_drain(self->line))
+               Py_RETURN_TRUE;
+
+       Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_is_open_source_doc,
+"Check if this line represents an open-source GPIO.");
+
+static PyObject *gpiod_Line_is_open_source(gpiod_LineObject *self)
+{
+       if (gpiod_line_is_open_source(self->line))
+               Py_RETURN_TRUE;
+
+       Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(gpiod_Line_request_doc,
+"Request this GPIO line.");
+
+static PyObject *gpiod_Line_request(gpiod_LineObject *self,
+                                   PyObject *args, PyObject *kwds)
+{
+       gpiod_LineBulkObject *bulk_obj;
+       PyObject *ret;
+
+       bulk_obj = gpiod_LineToLineBulk(self);
+       if (!bulk_obj)
+               return NULL;
+
+       ret = gpiod_LineBulk_request(bulk_obj, args, kwds);
+       Py_DECREF(bulk_obj);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_get_value_doc,
+"Read the current value of this GPIO line.");
+
+static PyObject *gpiod_Line_get_value(gpiod_LineObject *self)
+{
+       gpiod_LineBulkObject *bulk_obj;
+       PyObject *vals, *ret;
+
+       bulk_obj = gpiod_LineToLineBulk(self);
+       if (!bulk_obj)
+               return NULL;
+
+       vals = gpiod_LineBulk_get_values(bulk_obj);
+       Py_DECREF(bulk_obj);
+       if (!vals)
+               return NULL;
+
+       ret = PyList_GetItem(vals, 0);
+       Py_DECREF(vals);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_set_value_doc,
+"Set the value of this GPIO line.");
+
+static PyObject *gpiod_Line_set_value(gpiod_LineObject *self, PyObject *args)
+{
+       gpiod_LineBulkObject *bulk_obj;
+       PyObject *val, *vals, *ret;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "O", &val);
+       if (!rv)
+               return NULL;
+
+       bulk_obj = gpiod_LineToLineBulk(self);
+       if (!bulk_obj)
+               return NULL;
+
+       vals = Py_BuildValue("((O))", val);
+       if (!vals)
+               return NULL;
+
+       ret = gpiod_LineBulk_set_values(bulk_obj, vals);
+       Py_DECREF(vals);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_release_doc,
+"Release this GPIO line.");
+
+static PyObject *gpiod_Line_release(gpiod_LineObject *self)
+{
+       gpiod_LineBulkObject *bulk_obj;
+       PyObject *ret;
+
+       bulk_obj = gpiod_LineToLineBulk(self);
+       if (!bulk_obj)
+               return NULL;
+
+       ret = gpiod_LineBulk_release(bulk_obj);
+       Py_DECREF(bulk_obj);
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_wait_doc,
+"Wait for a line event to occur on this GPIO line.");
+
+static PyObject *gpiod_Line_event_wait(gpiod_LineObject *self,
+                                      PyObject *args, PyObject *kwds)
+{
+       gpiod_LineBulkObject *bulk_obj;
+       PyObject *ret;
+
+       bulk_obj = gpiod_LineToLineBulk(self);
+       if (!bulk_obj)
+               return NULL;
+
+       ret = gpiod_LineBulk_event_wait(bulk_obj, args, kwds);
+       Py_DECREF(bulk_obj);
+       if (!ret || ret == Py_False)
+               return ret;
+
+       Py_RETURN_TRUE;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_read_doc,
+"Read a single line event from this GPIO line object.");
+
+static gpiod_LineEventObject *gpiod_Line_event_read(gpiod_LineObject *self)
+{
+       gpiod_LineEventObject *ret;
+       int rv;
+
+       ret = PyObject_New(gpiod_LineEventObject, &gpiod_LineEventType);
+       if (!ret)
+               return NULL;
+
+       ret->source = NULL;
+
+       rv = gpiod_line_event_read(self->line, &ret->event);
+       if (rv) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               Py_DECREF(ret);
+               return NULL;
+       }
+
+       Py_INCREF(self);
+       ret->source = self;
+
+       return ret;
+}
+
+PyDoc_STRVAR(gpiod_Line_event_get_fd_doc,
+"Get the event file descriptor number associated with this line.");
+
+static PyObject *gpiod_Line_event_get_fd(gpiod_LineObject *self)
+{
+       int fd;
+
+       fd = gpiod_line_event_get_fd(self->line);
+       if (fd < 0) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       return PyLong_FromLong(fd);
+}
+
+static PyObject *gpiod_Line_repr(gpiod_LineObject *self)
+{
+       PyObject *chip_name, *ret;
+       const char *line_name;
+
+       chip_name = gpiod_Chip_name(self->owner);
+       if (!chip_name)
+               return NULL;
+
+       line_name = gpiod_line_name(self->line);
+
+       ret = PyUnicode_FromFormat("'%S:%u /%s/'", chip_name,
+                                  gpiod_line_offset(self->line),
+                                  line_name ?: "unnamed");
+       Py_DECREF(chip_name);
+       return ret;
+}
+
+static PyMethodDef gpiod_Line_methods[] = {
+       {
+               .ml_name = "owner",
+               .ml_meth = (PyCFunction)gpiod_Line_owner,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_owner_doc,
+       },
+       {
+               .ml_name = "offset",
+               .ml_meth = (PyCFunction)gpiod_Line_offset,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_offset_doc,
+       },
+       {
+               .ml_name = "name",
+               .ml_meth = (PyCFunction)gpiod_Line_name,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_name_doc,
+       },
+       {
+               .ml_name = "consumer",
+               .ml_meth = (PyCFunction)gpiod_Line_consumer,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_consumer_doc,
+       },
+       {
+               .ml_name = "direction",
+               .ml_meth = (PyCFunction)gpiod_Line_direction,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_direction_doc,
+       },
+       {
+               .ml_name = "active_state",
+               .ml_meth = (PyCFunction)gpiod_Line_active_state,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_active_state_doc,
+       },
+       {
+               .ml_name = "is_used",
+               .ml_meth = (PyCFunction)gpiod_Line_is_used,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_is_used_doc,
+       },
+       {
+               .ml_name = "is_open_drain",
+               .ml_meth = (PyCFunction)gpiod_Line_is_open_drain,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_is_open_drain_doc,
+       },
+       {
+               .ml_name = "is_open_source",
+               .ml_meth = (PyCFunction)gpiod_Line_is_open_source,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_is_open_source_doc,
+       },
+       {
+               .ml_name = "request",
+               .ml_meth = (PyCFunction)gpiod_Line_request,
+               .ml_flags = METH_VARARGS | METH_KEYWORDS,
+               .ml_doc = gpiod_Line_request_doc,
+       },
+       {
+               .ml_name = "get_value",
+               .ml_meth = (PyCFunction)gpiod_Line_get_value,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_get_value_doc,
+       },
+       {
+               .ml_name = "set_value",
+               .ml_meth = (PyCFunction)gpiod_Line_set_value,
+               .ml_flags = METH_VARARGS,
+               .ml_doc = gpiod_Line_set_value_doc,
+       },
+       {
+               .ml_name = "release",
+               .ml_meth = (PyCFunction)gpiod_Line_release,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_release_doc,
+       },
+       {
+               .ml_name = "event_wait",
+               .ml_meth = (PyCFunction)gpiod_Line_event_wait,
+               .ml_flags = METH_VARARGS | METH_KEYWORDS,
+               .ml_doc = gpiod_Line_event_wait_doc,
+       },
+       {
+               .ml_name = "event_read",
+               .ml_meth = (PyCFunction)gpiod_Line_event_read,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_event_read_doc,
+       },
+       {
+               .ml_name = "event_get_fd",
+               .ml_meth = (PyCFunction)gpiod_Line_event_get_fd,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Line_event_get_fd_doc,
+       },
+       { }
+};
+
+PyDoc_STRVAR(gpiod_LineType_doc,
+"Represents a GPIO line.");
+
+static PyTypeObject gpiod_LineType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.Line",
+       .tp_basicsize = sizeof(gpiod_LineObject),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_LineType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_Line_init,
+       .tp_dealloc = (destructor)gpiod_Line_dealloc,
+       .tp_repr = (reprfunc)gpiod_Line_repr,
+       .tp_methods = gpiod_Line_methods,
+};
+
+static int gpiod_LineBulk_init(gpiod_LineBulkObject *self, PyObject *args)
+{
+       PyObject *lines, *iter, *next;
+       Py_ssize_t i;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "O", &lines);
+       if (!rv)
+               return -1;
+
+       self->num_lines = PyObject_Size(lines);
+       if (self->num_lines < 1) {
+               PyErr_SetString(PyExc_TypeError,
+                               "Argument must be a non-empty sequence");
+               return -1;
+       }
+       if (self->num_lines > GPIOD_LINE_BULK_MAX_LINES) {
+               PyErr_SetString(PyExc_TypeError,
+                               "Too many objects in the sequence");
+               return -1;
+       }
+
+       self->lines = PyMem_RawCalloc(self->num_lines, sizeof(PyObject *));
+       if (!self->lines) {
+               PyErr_SetString(PyExc_MemoryError, "Out of memory");
+               return -1;
+       }
+
+       iter = PyObject_GetIter(lines);
+       if (!iter) {
+               PyMem_RawFree(self->lines);
+               return -1;
+       }
+
+       for (i = 0;;) {
+               next = PyIter_Next(iter);
+               if (!next) {
+                       Py_DECREF(iter);
+                       break;
+               }
+
+               if (next->ob_type != &gpiod_LineType) {
+                       PyErr_SetString(PyExc_TypeError,
+                                       "Argument must be a sequence of GPIO lines");
+                       Py_DECREF(next);
+                       Py_DECREF(iter);
+                       goto errout;
+               }
+
+               self->lines[i++] = next;
+       }
+
+       self->iter_idx = -1;
+
+       return 0;
+
+errout:
+
+       if (i > 0) {
+               for (--i; i >= 0; i--)
+                       Py_DECREF(self->lines[i]);
+       }
+       PyMem_RawFree(self->lines);
+       self->lines = NULL;
+
+       return -1;
+}
+
+static void gpiod_LineBulk_dealloc(gpiod_LineBulkObject *self)
+{
+       Py_ssize_t i;
+
+       if (!self->lines)
+               return;
+
+       for (i = 0; i < self->num_lines; i++)
+               Py_DECREF(self->lines[i]);
+
+       PyMem_RawFree(self->lines);
+}
+
+static PyObject *gpiod_LineBulk_iternext(gpiod_LineBulkObject *self)
+{
+       if (self->iter_idx < 0) {
+               self->iter_idx = 0; /* First element */
+       } else if (self->iter_idx >= self->num_lines) {
+               self->iter_idx = -1;
+               return NULL; /* Last element */
+       }
+
+       Py_INCREF(self->lines[self->iter_idx]);
+       return self->lines[self->iter_idx++];
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_to_list_doc,
+"Convert this LineBulk to a list");
+
+static PyObject *gpiod_LineBulk_to_list(gpiod_LineBulkObject *self)
+{
+       PyObject *list;
+       Py_ssize_t i;
+       int rv;
+
+       list = PyList_New(self->num_lines);
+       if (!list)
+               return NULL;
+
+       for (i = 0; i < self->num_lines; i++) {
+               Py_INCREF(self->lines[i]);
+               rv = PyList_SetItem(list, i, self->lines[i]);
+               if (rv < 0) {
+                       Py_DECREF(list);
+                       return NULL;
+               }
+       }
+
+       return list;
+}
+
+static void gpiod_LineBulkObjToCLineBulk(gpiod_LineBulkObject *bulk_obj,
+                                        struct gpiod_line_bulk *bulk)
+{
+       gpiod_LineObject *line_obj;
+       Py_ssize_t i;
+
+       gpiod_line_bulk_init(bulk);
+
+       for (i = 0; i < bulk_obj->num_lines; i++) {
+               line_obj = (gpiod_LineObject *)bulk_obj->lines[i];
+               gpiod_line_bulk_add(bulk, line_obj->line);
+       }
+}
+
+static void gpiod_MakeRequestConfig(struct gpiod_line_request_config *conf,
+                                   const char *consumer,
+                                   int request_type, int flags)
+{
+       memset(conf, 0, sizeof(*conf));
+
+       conf->consumer = consumer;
+
+       switch (request_type) {
+       case gpiod_LINE_REQ_DIR_IN:
+               conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
+               break;
+       case gpiod_LINE_REQ_DIR_OUT:
+               conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
+               break;
+       case gpiod_LINE_REQ_EV_FALLING_EDGE:
+               conf->request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+               break;
+       case gpiod_LINE_REQ_EV_RISING_EDGE:
+               conf->request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+               break;
+       case gpiod_LINE_REQ_EV_BOTH_EDGES:
+               conf->request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+               break;
+       case gpiod_LINE_REQ_DIR_AS_IS:
+       default:
+               conf->request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
+               break;
+       }
+
+       if (flags & gpiod_LINE_REQ_FLAG_OPEN_DRAIN)
+               conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+       if (flags & gpiod_LINE_REQ_FLAG_OPEN_SOURCE)
+               conf->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+       if (flags & gpiod_LINE_REQ_FLAG_ACTIVE_LOW)
+               conf->flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_request_doc,
+"Request all lines held by this LineBulk object.");
+
+static PyObject *gpiod_LineBulk_request(gpiod_LineBulkObject *self,
+                                       PyObject *args, PyObject *kwds)
+{
+       static char *kwlist[] = { "consumer",
+                                 "type",
+                                 "flags",
+                                 "default_vals",
+                                 NULL };
+
+       int rv, type = gpiod_LINE_REQ_DIR_AS_IS, flags = 0,
+           default_vals[GPIOD_LINE_BULK_MAX_LINES], val;
+       PyObject *def_vals_obj = NULL, *iter, *next;
+       struct gpiod_line_request_config conf;
+       struct gpiod_line_bulk bulk;
+       Py_ssize_t num_def_vals;
+       char *consumer = NULL;
+       Py_ssize_t i;
+
+       rv = PyArg_ParseTupleAndKeywords(args, kwds, "s|iiO", kwlist,
+                                        &consumer, &type,
+                                        &flags, &default_vals);
+       if (!rv)
+               return NULL;
+
+       gpiod_LineBulkObjToCLineBulk(self, &bulk);
+       gpiod_MakeRequestConfig(&conf, consumer, type, flags);
+
+       if (def_vals_obj) {
+               memset(default_vals, 0, sizeof(default_vals));
+
+               num_def_vals = PyObject_Size(def_vals_obj);
+               if (num_def_vals != self->num_lines) {
+                       PyErr_SetString(PyExc_TypeError,
+                                       "Number of default values is not the same as the number of lines");
+                       return NULL;
+               }
+
+               iter = PyObject_GetIter(def_vals_obj);
+               if (!iter)
+                       return NULL;
+
+               for (i = 0;; i++) {
+                       next = PyIter_Next(iter);
+                       if (!next) {
+                               Py_DECREF(iter);
+                               break;
+                       }
+
+                       val = PyLong_AsUnsignedLong(next);
+                       Py_DECREF(next);
+                       if (PyErr_Occurred()) {
+                               Py_DECREF(iter);
+                               return NULL;
+                       }
+
+                       default_vals[i] = !!val;
+               }
+       }
+
+       rv = gpiod_line_request_bulk(&bulk, &conf, default_vals);
+       if (rv) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_get_values_doc,
+"Read the values of all the lines held by this LineBulk object.");
+
+static PyObject *gpiod_LineBulk_get_values(gpiod_LineBulkObject *self)
+{
+       int rv, vals[GPIOD_LINE_BULK_MAX_LINES];
+       struct gpiod_line_bulk bulk;
+       PyObject *val_list, *val;
+       Py_ssize_t i;
+
+       gpiod_LineBulkObjToCLineBulk(self, &bulk);
+
+       memset(vals, 0, sizeof(vals));
+       rv = gpiod_line_get_value_bulk(&bulk, vals);
+       if (rv) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       val_list = PyList_New(self->num_lines);
+       if (!val_list)
+               return NULL;
+
+       for (i = 0; i < self->num_lines; i++) {
+               val = Py_BuildValue("i", vals[i]);
+               if (!val) {
+                       Py_DECREF(val_list);
+                       return NULL;
+               }
+
+               rv = PyList_SetItem(val_list, i, val);
+               if (rv < 0) {
+                       Py_DECREF(val);
+                       Py_DECREF(val_list);
+                       return NULL;
+               }
+       }
+
+       return val_list;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_set_values_doc,
+"Set the values of all the lines held by this LineBulk object.");
+
+static PyObject *gpiod_LineBulk_set_values(gpiod_LineBulkObject *self,
+                                          PyObject *args)
+{
+       int rv, vals[GPIOD_LINE_BULK_MAX_LINES], val;
+       PyObject *val_list, *iter, *next;
+       struct gpiod_line_bulk bulk;
+       Py_ssize_t num_vals, i;
+
+       gpiod_LineBulkObjToCLineBulk(self, &bulk);
+       memset(vals, 0, sizeof(vals));
+
+       rv = PyArg_ParseTuple(args, "O", &val_list);
+       if (!rv)
+               return NULL;
+
+       num_vals = PyObject_Size(val_list);
+       if (self->num_lines != num_vals) {
+               PyErr_SetString(PyExc_TypeError,
+                               "Number of values must correspond with the number of lines");
+               return NULL;
+       }
+
+       iter = PyObject_GetIter(val_list);
+       if (!iter)
+               return NULL;
+
+       for (i = 0;; i++) {
+               next = PyIter_Next(iter);
+               if (!next) {
+                       Py_DECREF(iter);
+                       break;
+               }
+
+               val = PyLong_AsLong(next);
+               Py_DECREF(next);
+               if (PyErr_Occurred())
+                       return NULL;
+
+               vals[i] = (int)val;
+       }
+
+       rv = gpiod_line_set_value_bulk(&bulk, vals);
+       if (rv) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_release_doc,
+"Release all lines held by this LineBulk object.");
+
+static PyObject *gpiod_LineBulk_release(gpiod_LineBulkObject *self)
+{
+       struct gpiod_line_bulk bulk;
+
+       gpiod_LineBulkObjToCLineBulk(self, &bulk);
+       gpiod_line_release_bulk(&bulk);
+
+       Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gpiod_LineBulk_event_wait_doc,
+"Poll the lines held by this LineBulk Object for line events.");
+
+static PyObject *gpiod_LineBulk_event_wait(gpiod_LineBulkObject *self,
+                                          PyObject *args, PyObject *kwds)
+{
+       static char *kwlist[] = { "sec", "nsec", NULL };
+
+       struct gpiod_line_bulk bulk, ev_bulk;
+       struct gpiod_line *line, **line_ptr;
+       gpiod_LineObject *line_obj;
+       gpiod_ChipObject *owner;
+       long sec = 0, nsec = 0;
+       struct timespec ts;
+       PyObject *ret;
+       Py_ssize_t i;
+       int rv;
+
+       rv = PyArg_ParseTupleAndKeywords(args, kwds,
+                                        "|ll", kwlist, &sec, &nsec);
+       if (!rv)
+               return NULL;
+
+       ts.tv_sec = sec;
+       ts.tv_nsec = nsec;
+
+       gpiod_LineBulkObjToCLineBulk(self, &bulk);
+
+       rv = gpiod_line_event_wait_bulk(&bulk, &ts, &ev_bulk);
+       if (rv < 0) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       } else if (rv == 0) {
+               Py_RETURN_FALSE;
+       }
+
+       ret = PyList_New(gpiod_line_bulk_num_lines(&ev_bulk));
+       if (!ret)
+               return NULL;
+
+       owner = ((gpiod_LineObject *)(self->lines[0]))->owner;
+
+       i = 0;
+       gpiod_line_bulk_foreach_line(&ev_bulk, line, line_ptr) {
+               line_obj = gpiod_MakeLineObject(owner, line);
+               if (!line_obj) {
+                       Py_DECREF(ret);
+                       return NULL;
+               }
+
+               rv = PyList_SetItem(ret, i++, (PyObject *)line_obj);
+               if (rv < 0) {
+                       Py_DECREF(ret);
+                       return NULL;
+               }
+       }
+
+       return ret;
+}
+
+static PyObject *gpiod_LineBulk_repr(gpiod_LineBulkObject *self)
+{
+       PyObject *list, *list_repr, *chip_name, *ret;
+       gpiod_LineObject *line;
+       gpiod_ChipObject *chip;
+
+       list = gpiod_LineBulk_to_list(self);
+       if (!list)
+               return NULL;
+
+       list_repr = PyObject_Repr(list);
+       Py_DECREF(list);
+       if (!list_repr)
+               return NULL;
+
+       line = (gpiod_LineObject *)self->lines[0];
+       chip = line->owner;
+       chip_name = gpiod_Chip_name(chip);
+       if (!chip_name) {
+               Py_DECREF(list_repr);
+               return NULL;
+       }
+
+       ret = PyUnicode_FromFormat("%U%U", chip_name, list_repr);
+       Py_DECREF(chip_name);
+       Py_DECREF(list_repr);
+       return ret;
+}
+
+static PyMethodDef gpiod_LineBulk_methods[] = {
+       {
+               .ml_name = "to_list",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_to_list,
+               .ml_doc = gpiod_LineBulk_to_list_doc,
+               .ml_flags = METH_NOARGS,
+       },
+       {
+               .ml_name = "request",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_request,
+               .ml_doc = gpiod_LineBulk_request_doc,
+               .ml_flags = METH_VARARGS | METH_KEYWORDS,
+       },
+       {
+               .ml_name = "get_values",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_get_values,
+               .ml_doc = gpiod_LineBulk_get_values_doc,
+               .ml_flags = METH_NOARGS,
+       },
+       {
+               .ml_name = "set_values",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_set_values,
+               .ml_doc = gpiod_LineBulk_set_values_doc,
+               .ml_flags = METH_VARARGS,
+       },
+       {
+               .ml_name = "release",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_release,
+               .ml_doc = gpiod_LineBulk_release_doc,
+               .ml_flags = METH_NOARGS,
+       },
+       {
+               .ml_name = "event_wait",
+               .ml_meth = (PyCFunction)gpiod_LineBulk_event_wait,
+               .ml_doc = gpiod_LineBulk_event_wait_doc,
+               .ml_flags = METH_VARARGS | METH_KEYWORDS,
+       },
+       { }
+};
+
+PyDoc_STRVAR(gpiod_LineBulkType_doc,
+"Represents a set of GPIO lines.");
+
+static PyTypeObject gpiod_LineBulkType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.LineBulk",
+       .tp_basicsize = sizeof(gpiod_LineBulkType),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_LineBulkType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_LineBulk_init,
+       .tp_dealloc = (destructor)gpiod_LineBulk_dealloc,
+       .tp_iter = PyObject_SelfIter,
+       .tp_iternext = (iternextfunc)gpiod_LineBulk_iternext,
+       .tp_repr = (reprfunc)gpiod_LineBulk_repr,
+       .tp_methods = gpiod_LineBulk_methods,
+};
+
+static gpiod_LineBulkObject *gpiod_LineToLineBulk(gpiod_LineObject *line)
+{
+       gpiod_LineBulkObject *ret;
+       PyObject *args;
+       int rv;
+
+       args = Py_BuildValue("((O))", line);
+       if (!args)
+               return NULL;
+
+       ret = PyObject_New(gpiod_LineBulkObject, &gpiod_LineBulkType);
+       if (!ret) {
+               Py_DECREF(args);
+               return NULL;
+       }
+
+       ret->lines = NULL;
+       ret->num_lines = 0;
+
+       rv = gpiod_LineBulk_init(ret, args);
+       Py_DECREF(args);
+       if (rv) {
+               Py_DECREF(ret);
+               return NULL;
+       }
+
+       return ret;
+}
+
+enum {
+       gpiod_OPEN_LOOKUP = 1,
+       gpiod_OPEN_BY_NAME,
+       gpiod_OPEN_BY_PATH,
+       gpiod_OPEN_BY_LABEL,
+       gpiod_OPEN_BY_NUMBER,
+};
+
+static int gpiod_Chip_init(gpiod_ChipObject *self, PyObject *args)
+{
+       int rv, how = gpiod_OPEN_LOOKUP;
+       char *descr;
+
+       rv = PyArg_ParseTuple(args, "s|i", &descr, &how);
+       if (!rv)
+               return -1;
+
+       switch (how) {
+       case gpiod_OPEN_LOOKUP:
+               self->chip = gpiod_chip_open_lookup(descr);
+               break;
+       case gpiod_OPEN_BY_NAME:
+               self->chip = gpiod_chip_open_by_name(descr);
+               break;
+       case gpiod_OPEN_BY_PATH:
+               self->chip = gpiod_chip_open(descr);
+               break;
+       case gpiod_OPEN_BY_LABEL:
+               self->chip = gpiod_chip_open_by_label(descr);
+               break;
+       case gpiod_OPEN_BY_NUMBER:
+               self->chip = gpiod_chip_open_by_number(atoi(descr));
+               break;
+       default:
+               PyErr_BadArgument();
+               return -1;
+       }
+       if (!self->chip) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return -1;
+       }
+
+       return 0;
+}
+
+static void gpiod_Chip_dealloc(gpiod_ChipObject *self)
+{
+       if (self->chip)
+               gpiod_chip_close(self->chip);
+}
+
+static PyObject *gpiod_Chip_repr(gpiod_ChipObject *self)
+{
+       return PyUnicode_FromFormat("'%s /%s/ %u lines'",
+                                   gpiod_chip_name(self->chip),
+                                   gpiod_chip_label(self->chip),
+                                   gpiod_chip_num_lines(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_name_doc,
+"Get the name of the GPIO chip");
+
+static PyObject *gpiod_Chip_name(gpiod_ChipObject *self)
+{
+       return PyUnicode_FromFormat("%s", gpiod_chip_name(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_label_doc,
+"Get the label of the GPIO chip");
+
+static PyObject *gpiod_Chip_label(gpiod_ChipObject *self)
+{
+       return PyUnicode_FromFormat("%s", gpiod_chip_label(self->chip));
+}
+
+PyDoc_STRVAR(gpiod_Chip_num_lines_doc,
+"Get the number of lines exposed by this GPIO chip.");
+
+static PyObject *gpiod_Chip_num_lines(gpiod_ChipObject *self)
+{
+       return Py_BuildValue("I", gpiod_chip_num_lines(self->chip));
+}
+
+static gpiod_LineObject *
+gpiod_MakeLineObject(gpiod_ChipObject *owner, struct gpiod_line *line)
+{
+       gpiod_LineObject *obj;
+
+       obj = PyObject_New(gpiod_LineObject, &gpiod_LineType);
+       if (!obj)
+               return NULL;
+
+       obj->line = line;
+       Py_INCREF(owner);
+       obj->owner = owner;
+
+       return obj;
+}
+
+PyDoc_STRVAR(gpiod_Chip_get_line_doc,
+"Get the GPIO line at given offset.");
+
+static gpiod_LineObject *
+gpiod_Chip_get_line(gpiod_ChipObject *self, PyObject *args)
+{
+       struct gpiod_line *line;
+       unsigned int offset;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "I", &offset);
+       if (!rv)
+               return NULL;
+
+       line = gpiod_chip_get_line(self->chip, offset);
+       if (!line) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       return gpiod_MakeLineObject(self, line);
+}
+
+PyDoc_STRVAR(gpiod_Chip_find_line_doc,
+"Get the GPIO line by name.");
+
+static gpiod_LineObject *
+gpiod_Chip_find_line(gpiod_ChipObject *self, PyObject *args)
+{
+       struct gpiod_line *line;
+       const char *name;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "s", &name);
+       if (!rv)
+               return NULL;
+
+       line = gpiod_chip_find_line(self->chip, name);
+       if (!line) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       return gpiod_MakeLineObject(self, line);
+}
+
+PyDoc_STRVAR(gpiod_Chip_get_lines_doc,
+"Get a set of GPIO lines by their offsets.");
+
+static gpiod_LineBulkObject *
+gpiod_Chip_get_lines(gpiod_ChipObject *self, PyObject *args)
+{
+       PyObject *offsets, *iter, *next, *lines, *arg;
+       gpiod_LineBulkObject *bulk;
+       Py_ssize_t num_offsets, i;
+       gpiod_LineObject *line;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "O", &offsets);
+       if (!rv)
+               return NULL;
+
+       num_offsets = PyObject_Size(offsets);
+       if (num_offsets < 1) {
+               PyErr_SetString(PyExc_TypeError,
+                               "Argument must be a non-empty sequence of offsets");
+               return NULL;
+       }
+
+       lines = PyList_New(num_offsets);
+       if (!lines)
+               return NULL;
+
+       iter = PyObject_GetIter(offsets);
+       if (!iter) {
+               Py_DECREF(lines);
+               return NULL;
+       }
+
+       for (i = 0;;) {
+               next = PyIter_Next(iter);
+               if (!next) {
+                       Py_DECREF(iter);
+                       break;
+               }
+
+               arg = PyTuple_Pack(1, next);
+               Py_DECREF(next);
+               if (!arg) {
+                       Py_DECREF(iter);
+                       Py_DECREF(lines);
+                       return NULL;
+               }
+
+               line = gpiod_Chip_get_line(self, arg);
+               Py_DECREF(arg);
+               if (!line) {
+                       Py_DECREF(iter);
+                       Py_DECREF(lines);
+                       return NULL;
+               }
+
+               rv = PyList_SetItem(lines, i++, (PyObject *)line);
+               if (rv < 0) {
+                       Py_DECREF(line);
+                       Py_DECREF(iter);
+                       Py_DECREF(lines);
+                       return NULL;
+               }
+       }
+
+       arg = PyTuple_Pack(1, lines);
+       Py_DECREF(lines);
+       if (!arg)
+               return NULL;
+
+       bulk = PyObject_New(gpiod_LineBulkObject, &gpiod_LineBulkType);
+       if (!bulk) {
+               Py_DECREF(arg);
+               return NULL;
+       }
+
+       rv = gpiod_LineBulkType.tp_init((PyObject *)bulk, arg, NULL);
+       Py_DECREF(arg);
+       if (rv < 0) {
+               Py_DECREF(bulk);
+               return NULL;
+       }
+
+       return bulk;
+}
+
+static PyMethodDef gpiod_Chip_methods[] = {
+       {
+               .ml_name = "name",
+               .ml_meth = (PyCFunction)gpiod_Chip_name,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Chip_name_doc,
+       },
+       {
+               .ml_name = "label",
+               .ml_meth = (PyCFunction)gpiod_Chip_label,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Chip_label_doc,
+       },
+       {
+               .ml_name = "num_lines",
+               .ml_meth = (PyCFunction)gpiod_Chip_num_lines,
+               .ml_flags = METH_NOARGS,
+               .ml_doc = gpiod_Chip_num_lines_doc,
+       },
+       {
+               .ml_name = "get_line",
+               .ml_meth = (PyCFunction)gpiod_Chip_get_line,
+               .ml_flags = METH_VARARGS,
+               .ml_doc = gpiod_Chip_get_line_doc,
+       },
+       {
+               .ml_name = "find_line",
+               .ml_meth = (PyCFunction)gpiod_Chip_find_line,
+               .ml_flags = METH_VARARGS,
+               .ml_doc = gpiod_Chip_find_line_doc,
+       },
+       {
+               .ml_name = "get_lines",
+               .ml_meth = (PyCFunction)gpiod_Chip_get_lines,
+               .ml_flags = METH_VARARGS,
+               .ml_doc = gpiod_Chip_get_lines_doc,
+       },
+       { }
+};
+
+PyDoc_STRVAR(gpiod_ChipType_doc,
+"Represents a GPIO chip.");
+
+static PyTypeObject gpiod_ChipType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.Chip",
+       .tp_basicsize = sizeof(gpiod_ChipObject),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_ChipType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_Chip_init,
+       .tp_dealloc = (destructor)gpiod_Chip_dealloc,
+       .tp_repr = (reprfunc)gpiod_Chip_repr,
+       .tp_methods = gpiod_Chip_methods,
+};
+
+static int gpiod_ChipIter_init(gpiod_ChipIterObject *self)
+{
+       self->iter = gpiod_chip_iter_new();
+       if (!self->iter) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return -1;
+       }
+
+       return 0;
+}
+
+static void gpiod_ChipIter_dealloc(gpiod_ChipIterObject *self)
+{
+       if (self->iter)
+               gpiod_chip_iter_free_noclose(self->iter);
+}
+
+static gpiod_ChipObject *gpiod_ChipIter_next(gpiod_ChipIterObject *self)
+{
+       gpiod_ChipObject *chip_obj;
+       struct gpiod_chip *chip;
+
+       chip = gpiod_chip_iter_next_noclose(self->iter);
+       if (!chip)
+               return NULL; /* Last element. */
+
+       chip_obj = PyObject_New(gpiod_ChipObject, &gpiod_ChipType);
+       if (!chip_obj) {
+               gpiod_chip_close(chip);
+               return NULL;
+       }
+
+       chip_obj->chip = chip;
+
+       return chip_obj;
+}
+
+PyDoc_STRVAR(gpiod_ChipIterType_doc,
+"Allows to iterate over all GPIO chips in the system.");
+
+static PyTypeObject gpiod_ChipIterType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.ChipIter",
+       .tp_basicsize = sizeof(gpiod_ChipIterObject),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_ChipIterType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_ChipIter_init,
+       .tp_dealloc = (destructor)gpiod_ChipIter_dealloc,
+       .tp_iter = PyObject_SelfIter,
+       .tp_iternext = (iternextfunc)gpiod_ChipIter_next,
+};
+
+static int gpiod_LineIter_init(gpiod_LineIterObject *self, PyObject *args)
+{
+       gpiod_ChipObject *chip_obj;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "O!", &gpiod_ChipType,
+                             (PyObject *)&chip_obj);
+       if (!rv)
+               return -1;
+
+       self->iter = gpiod_line_iter_new(chip_obj->chip);
+       if (!self->iter) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return -1;
+       }
+
+       self->owner = chip_obj;
+       Py_INCREF(chip_obj);
+
+       return 0;
+}
+
+static void gpiod_LineIter_dealloc(gpiod_LineIterObject *self)
+{
+       if (self->iter)
+               gpiod_line_iter_free(self->iter);
+}
+
+static gpiod_LineObject *gpiod_LineIter_next(gpiod_LineIterObject *self)
+{
+       struct gpiod_line *line;
+
+       line = gpiod_line_iter_next(self->iter);
+       if (!line)
+               return NULL; /* Last element. */
+
+       return gpiod_MakeLineObject(self->owner, line);
+}
+
+PyDoc_STRVAR(gpiod_LineIterType_doc,
+"Allows to iterate over all lines exposed by a GPIO chip.");
+
+static PyTypeObject gpiod_LineIterType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name = "gpiod.LineIter",
+       .tp_basicsize = sizeof(gpiod_LineIterObject),
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = gpiod_LineIterType_doc,
+       .tp_new = PyType_GenericNew,
+       .tp_init = (initproc)gpiod_LineIter_init,
+       .tp_dealloc = (destructor)gpiod_LineIter_dealloc,
+       .tp_iter = PyObject_SelfIter,
+       .tp_iternext = (iternextfunc)gpiod_LineIter_next,
+};
+
+PyDoc_STRVAR(gpiod_Module_find_line_doc,
+"Lookup a GPIO line by name. Search all gpiochips.");
+
+static gpiod_LineObject *gpiod_Module_find_line(PyObject *self GPIOD_UNUSED,
+                                               PyObject *args)
+{
+       gpiod_ChipObject *chip_obj;
+       struct gpiod_chip *chip;
+       struct gpiod_line *line;
+       const char *name;
+       int rv;
+
+       rv = PyArg_ParseTuple(args, "s", &name);
+       if (!rv)
+               return NULL;
+
+       line = gpiod_line_find(name);
+       if (!line) {
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       chip = gpiod_line_get_chip(line);
+
+       chip_obj = PyObject_New(gpiod_ChipObject, &gpiod_ChipType);
+       if (!chip_obj) {
+               gpiod_chip_close(chip);
+               return NULL;
+       }
+
+       chip_obj->chip = chip;
+
+       return gpiod_MakeLineObject(chip_obj, line);
+}
+
+static PyMethodDef gpiod_module_methods[] = {
+       {
+               .ml_name = "find_line",
+               .ml_meth = (PyCFunction)gpiod_Module_find_line,
+               .ml_flags = METH_VARARGS,
+               .ml_doc = gpiod_Module_find_line_doc,
+       },
+       { }
+};
+
+typedef struct {
+       const char *name;
+       PyTypeObject *typeobj;
+} gpiod_PyType;
+
+static gpiod_PyType gpiod_PyType_list[] = {
+       { .name = "Chip",       .typeobj = &gpiod_ChipType,             },
+       { .name = "Line",       .typeobj = &gpiod_LineType,             },
+       { .name = "LineEvent",  .typeobj = &gpiod_LineEventType,        },
+       { .name = "LineBulk",   .typeobj = &gpiod_LineBulkType,         },
+       { .name = "LineIter",   .typeobj = &gpiod_LineIterType,         },
+       { .name = "ChipIter",   .typeobj = &gpiod_ChipIterType          },
+       { }
+};
+
+typedef struct {
+       PyTypeObject *typeobj;
+       const char *name;
+       long int val;
+} gpiod_ConstDescr;
+
+static gpiod_ConstDescr gpiod_ConstList[] = {
+       {
+               .typeobj = &gpiod_ChipType,
+               .name = "OPEN_LOOKUP",
+               .val = gpiod_OPEN_LOOKUP,
+       },
+       {
+               .typeobj = &gpiod_ChipType,
+               .name = "OPEN_BY_PATH",
+               .val = gpiod_OPEN_BY_PATH,
+       },
+       {
+               .typeobj = &gpiod_ChipType,
+               .name = "OPEN_BY_NAME",
+               .val = gpiod_OPEN_BY_NAME,
+       },
+       {
+               .typeobj = &gpiod_ChipType,
+               .name = "OPEN_BY_LABEL",
+               .val = gpiod_OPEN_BY_LABEL,
+       },
+       {
+               .typeobj = &gpiod_ChipType,
+               .name = "OPEN_BY_NUMBER",
+               .val = gpiod_OPEN_BY_NUMBER,
+       },
+       {
+               .typeobj = &gpiod_LineType,
+               .name = "DIRECTION_INPUT",
+               .val = gpiod_DIRECTION_INPUT,
+       },
+       {
+               .typeobj = &gpiod_LineType,
+               .name = "DIRECTION_OUTPUT",
+               .val = gpiod_DIRECTION_OUTPUT,
+       },
+       {
+               .typeobj = &gpiod_LineType,
+               .name = "ACTIVE_HIGH",
+               .val = gpiod_ACTIVE_HIGH,
+       },
+       {
+               .typeobj = &gpiod_LineType,
+               .name = "ACTIVE_LOW",
+               .val = gpiod_ACTIVE_LOW,
+       },
+       {
+               .typeobj = &gpiod_LineEventType,
+               .name = "RISING_EDGE",
+               .val = gpiod_RISING_EDGE,
+       },
+       {
+               .typeobj = &gpiod_LineEventType,
+               .name = "FALLING_EDGE",
+               .val = gpiod_FALLING_EDGE,
+       },
+       { }
+};
+
+PyDoc_STRVAR(gpiod_Module_doc,
+"Python bindings for libgpiod.\n\
+\n\
+This module wraps the native C API of libgpiod in a set of python types.");
+
+static PyModuleDef gpiod_Module = {
+       PyModuleDef_HEAD_INIT,
+       .m_name = "gpiod",
+       .m_doc = gpiod_Module_doc,
+       .m_size = -1,
+       .m_methods = gpiod_module_methods,
+};
+
+typedef struct {
+       const char *name;
+       long int value;
+} gpiod_ModuleConst;
+
+static gpiod_ModuleConst gpiod_ModuleConsts[] = {
+       {
+               .name = "LINE_REQ_DIR_AS_IS",
+               .value = gpiod_LINE_REQ_DIR_AS_IS,
+       },
+       {
+               .name = "LINE_REQ_DIR_IN",
+               .value = gpiod_LINE_REQ_DIR_IN,
+       },
+       {
+               .name = "LINE_REQ_DIR_OUT",
+               .value = gpiod_LINE_REQ_DIR_OUT,
+       },
+       {
+               .name = "LINE_REQ_EV_FALLING_EDGE",
+               .value = gpiod_LINE_REQ_EV_FALLING_EDGE,
+       },
+       {
+               .name = "LINE_REQ_EV_RISING_EDGE",
+               .value = gpiod_LINE_REQ_EV_RISING_EDGE,
+       },
+       {
+               .name = "LINE_REQ_EV_BOTH_EDGES",
+               .value = gpiod_LINE_REQ_EV_BOTH_EDGES,
+       },
+       {
+               .name = "LINE_REQ_FLAG_OPEN_DRAIN",
+               .value = gpiod_LINE_REQ_FLAG_OPEN_DRAIN,
+       },
+       {
+               .name = "LINE_REQ_FLAG_OPEN_SOURCE",
+               .value = gpiod_LINE_REQ_FLAG_OPEN_SOURCE,
+       },
+       {
+               .name = "LINE_REQ_FLAG_ACTIVE_LOW",
+               .value = gpiod_LINE_REQ_FLAG_ACTIVE_LOW,
+       },
+       { }
+};
+
+PyMODINIT_FUNC PyInit_gpiod(void)
+{
+       gpiod_ConstDescr *const_descr;
+       gpiod_ModuleConst *mod_const;
+       PyObject *module, *val;
+       gpiod_PyType *type;
+       unsigned int i;
+       int rv;
+
+       module = PyModule_Create(&gpiod_Module);
+       if (!module)
+               return NULL;
+
+       for (i = 0; gpiod_PyType_list[i].typeobj; i++) {
+               type = &gpiod_PyType_list[i];
+
+               rv = PyType_Ready(type->typeobj);
+               if (rv)
+                       return NULL;
+
+               Py_INCREF(type->typeobj);
+               rv = PyModule_AddObject(module, type->name,
+                                       (PyObject *)type->typeobj);
+               if (rv < 0)
+                       return NULL;
+       }
+
+       for (i = 0; gpiod_ConstList[i].name; i++) {
+               const_descr = &gpiod_ConstList[i];
+
+               val = PyLong_FromLong(const_descr->val);
+               if (!val)
+                       return NULL;
+
+               rv = PyDict_SetItemString(const_descr->typeobj->tp_dict,
+                                         const_descr->name, val);
+               Py_DECREF(val);
+               if (rv)
+                       return NULL;
+       }
+
+       for (i = 0; gpiod_ModuleConsts[i].name; i++) {
+               mod_const = &gpiod_ModuleConsts[i];
+
+               rv = PyModule_AddIntConstant(module,
+                                            mod_const->name, mod_const->value);
+               if (rv < 0)
+                       return NULL;
+       }
+
+       return module;
+}
index d318a0e0274821581f0de4a7abcff3538593bdee..ee7b03b7792f9ee143c23702a0c8652878f4b1b5 100644 (file)
@@ -151,6 +151,31 @@ then
        AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
 fi
 
+AC_ARG_ENABLE([bindings-python],
+       [AC_HELP_STRING([--enable-bindings-python],
+               [enable python3 bindings [default=no]])],
+       [
+               if test "x$enableval" = xyes
+               then
+                       with_bindings_python=true
+               else
+                       with_bindings_python=false
+               fi
+       ],
+       [with_bindings_python=false])
+AM_CONDITIONAL([WITH_BINDINGS_PYTHON], [test "x$with_bindings_python" = xtrue])
+
+if test "x$with_bindings_python" = xtrue
+then
+       AX_PYTHON_DEVEL([>= '3.0.0'])
+       AM_PATH_PYTHON([3.0])
+       AC_CHECK_PROG([has_python3], [python3], [true], [false])
+       if test "X$has_python3" = xfalse
+       then
+               AC_MSG_ERROR([python3 not found - needed for python bindings], [1])
+       fi
+fi
+
 AC_CHECK_PROG([has_doxygen], [doxygen], [true], [false])
 AM_CONDITIONAL([HAS_DOXYGEN], [test "x$has_doxygen" = xtrue])
 if test "x$has_doxygen" = xfalse
@@ -168,6 +193,8 @@ AC_CONFIG_FILES([libgpiod.pc
                 bindings/cxx/libgpiodcxx.pc
                 bindings/Makefile
                 bindings/cxx/Makefile
-                bindings/cxx/examples/Makefile])
+                bindings/cxx/examples/Makefile
+                bindings/python/Makefile
+                bindings/python/examples/Makefile])
 
 AC_OUTPUT