libgpiod icon indicating copy to clipboard operation
libgpiod copied to clipboard

bindings: python: Would type aliases make things easier to read?

Open vfazio opened this issue 4 months ago • 2 comments

We can alias types in python, so instead of littering the codebase with complex types like:

dict[Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]]

Or when we move to only support 3.10+:

dict[Iterable[int | str] | int | str, LineSettings | None] 

We can instead do something like:

RequestKey = Union[int, str]
IterableRequestKeys = Iterable[RequestKey]
IterableRequestKeys_or_RequestKey = Union[IterableRequestKeys, RequestKey]

config: dict[IterableRequestKeys_or_RequestKey, Optional[LineSettings]],

The resolved type is equivalent to the previous syntax, but it's maybe a little easier to digest.

https://docs.python.org/3.9/library/typing.html#type-aliases

vfazio avatar Aug 18 '25 13:08 vfazio

Adding a diff just to give a sense of what this may look like

diff --git a/bindings/python/gpiod/__init__.py b/bindings/python/gpiod/__init__.py
index 854e41f..e95cf21 100644
--- a/bindings/python/gpiod/__init__.py
+++ b/bindings/python/gpiod/__init__.py
@@ -7,8 +7,10 @@ Python bindings for libgpiod.
 This module wraps the native C API of libgpiod in a set of python classes.
 """
 
-from collections.abc import Iterable
-from typing import Optional, Union
+from typing import Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .line_request import RequestKey, IterableRequestKeys_or_RequestKey
 
 from . import (
     _ext,
@@ -87,10 +89,10 @@ def is_gpiochip_device(path: str) -> bool:
 
 def request_lines(
(venv) vfazio@vfazio2:~/development/libgpiod/bindings/python$ PAGER= git diff
diff --git a/bindings/python/gpiod/__init__.py b/bindings/python/gpiod/__init__.py
index 854e41f..e95cf21 100644
--- a/bindings/python/gpiod/__init__.py
+++ b/bindings/python/gpiod/__init__.py
@@ -7,8 +7,10 @@ Python bindings for libgpiod.
 This module wraps the native C API of libgpiod in a set of python classes.
 """
 
-from collections.abc import Iterable
-from typing import Optional, Union
+from typing import Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .line_request import RequestKey, IterableRequestKeys_or_RequestKey
 
 from . import (
     _ext,
@@ -87,10 +89,10 @@ def is_gpiochip_device(path: str) -> bool:
 
 def request_lines(
     path: str,
-    config: dict[Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]],
+    config: dict[IterableRequestKeys_or_RequestKey, Optional[LineSettings]],
     consumer: Optional[str] = None,
     event_buffer_size: Optional[int] = None,
-    output_values: Optional[dict[Union[int, str], line.Value]] = None,
+    output_values: Optional[dict[RequestKey, line.Value]] = None,
 ) -> LineRequest:
     """
     Open a GPIO chip pointed to by 'path', request lines according to the
diff --git a/bindings/python/gpiod/chip.py b/bindings/python/gpiod/chip.py
index cccfb03..3bdc6f1 100644
--- a/bindings/python/gpiod/chip.py
+++ b/bindings/python/gpiod/chip.py
@@ -22,6 +22,11 @@ if TYPE_CHECKING:
     from .chip_info import ChipInfo
     from .info_event import InfoEvent
     from .line_info import LineInfo
+    from .line_request import (
+        RequestKey,
+        IterableRequestKeys,
+        IterableRequestKeys_or_RequestKey,
+    )
 
 __all__ = ["Chip"]
 
@@ -118,7 +123,7 @@ class Chip:
 
         return self._info
 
-    def line_offset_from_id(self, id: Union[str, int]) -> int:
+    def line_offset_from_id(self, id: RequestKey) -> int:
         """
         Map a line's identifier to its offset within the chip.
 
@@ -156,13 +161,13 @@ class Chip:
 
         return offset
 
-    def _get_line_info(self, line: Union[int, str], watch: bool) -> LineInfo:
+    def _get_line_info(self, line: RequestKey, watch: bool) -> LineInfo:
         self._check_closed()
         return cast(_ext.Chip, self._chip).get_line_info(
             self.line_offset_from_id(line), watch
         )
 
-    def get_line_info(self, line: Union[int, str]) -> LineInfo:
+    def get_line_info(self, line: RequestKey) -> LineInfo:
         """
         Get the snapshot of information about the line at given offset.
 
@@ -175,7 +180,7 @@ class Chip:
         """
         return self._get_line_info(line, watch=False)
 
-    def watch_line_info(self, line: Union[int, str]) -> LineInfo:
+    def watch_line_info(self, line: RequestKey) -> LineInfo:
         """
         Get the snapshot of information about the line at given offset and
         start watching it for future changes.
@@ -189,7 +194,7 @@ class Chip:
         """
         return self._get_line_info(line, watch=True)
 
-    def unwatch_line_info(self, line: Union[int, str]) -> None:
+    def unwatch_line_info(self, line: RequestKey) -> None:
         """
         Stop watching a line for status changes.
 
@@ -238,7 +243,7 @@ class Chip:
 
     def _resolve_config_keys_to_offsets(
         self,
-        config_keys: Iterable[Union[Iterable[Union[int, str]], int, str]],
+        config_keys: Iterable[IterableRequestKeys_or_RequestKey],
     ) -> list[int]:
         offsets: list[int] = list()
         for key in config_keys:
@@ -252,12 +257,10 @@ class Chip:
 
     def request_lines(
         self,
-        config: dict[
-            Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]
-        ],
+        config: dict[IterableRequestKeys_or_RequestKey, Optional[LineSettings]],
         consumer: Optional[str] = None,
         event_buffer_size: Optional[int] = None,
-        output_values: Optional[dict[Union[int, str], Value]] = None,
+        output_values: Optional[dict[RequestKey, Value]] = None,
     ) -> LineRequest:
         """
         Request a set of lines for exclusive usage.
diff --git a/bindings/python/gpiod/line_request.py b/bindings/python/gpiod/line_request.py
index 0220ba3..1ed2502 100644
--- a/bindings/python/gpiod/line_request.py
+++ b/bindings/python/gpiod/line_request.py
@@ -22,6 +22,11 @@ if TYPE_CHECKING:
 __all__ = ["LineRequest"]
 
 
+RequestKey = Union[int, str]
+IterableRequestKeys = Iterable[RequestKey]
+IterableRequestKeys_or_RequestKey = Union[IterableRequestKeys, RequestKey]
+
+
 class LineRequest:
     """
     Stores the context of a set of requested GPIO lines.
@@ -79,7 +84,7 @@ class LineRequest:
         cast(_ext.Request, self._req).release()
         self._req = None
 
-    def get_value(self, line: Union[int, str]) -> Value:
+    def get_value(self, line: RequestKey) -> Value:
         """
         Get a single GPIO line value.
 
@@ -92,7 +97,7 @@ class LineRequest:
         """
         return self.get_values([line])[0]
 
-    def _line_to_offset(self, line: Union[int, str]) -> int:
+    def _line_to_offset(self, line: RequestKey) -> int:
         if isinstance(line, int):
             return line
         else:
@@ -102,9 +107,7 @@ class LineRequest:
             else:
                 return _line
 
-    def get_values(
-        self, lines: Optional[Iterable[Union[int, str]]] = None
-    ) -> list[Value]:
+    def get_values(self, lines: Optional[IterableRequestKeys] = None) -> list[Value]:
         """
         Get values of a set of GPIO lines.
 
@@ -127,7 +130,7 @@ class LineRequest:
         cast(_ext.Request, self._req).get_values(offsets, buf)
         return buf
 
-    def set_value(self, line: Union[int, str], value: Value) -> None:
+    def set_value(self, line: RequestKey, value: Value) -> None:
         """
         Set the value of a single GPIO line.
 
@@ -139,7 +142,7 @@ class LineRequest:
         """
         self.set_values({line: value})
 
-    def set_values(self, values: dict[Union[int, str], Value]) -> None:
+    def set_values(self, values: dict[RequestKey, Value]) -> None:
         """
         Set the values of a subset of GPIO lines.
 
@@ -155,9 +158,7 @@ class LineRequest:
 
     def reconfigure_lines(
         self,
-        config: dict[
-            Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]
-        ],
+        config: dict[IterableRequestKeys_or_RequestKey, Optional[LineSettings]],
     ) -> None:
         """
         Reconfigure requested lines.

vfazio avatar Aug 18 '25 13:08 vfazio

We can alias types in python, so instead of littering the codebase with complex types like:

dict[Union[Iterable[Union[int, str]], int, str], Optional[LineSettings]]

Or when we move to only support 3.10+:

I guess this answers my question from under the other ticket about the minimum required version.

dict[Iterable[int | str] | int | str, LineSettings | None]

We can instead do something like:

RequestKey = Union[int, str] IterableRequestKeys = Iterable[RequestKey] IterableRequestKeys_or_RequestKey = Union[IterableRequestKeys, RequestKey]

config: dict[IterableRequestKeys_or_RequestKey, Optional[LineSettings]],

The resolved type is equivalent to the previous syntax, but it's maybe a little easier to digest.

https://docs.python.org/3.9/library/typing.html#type-aliases

I like it and am willing to accept it as long as you'll be the one driving it. :)

brgl avatar Aug 25 '25 12:08 brgl