From 032e05453aa28860995b72bf2e9efb7d5edea9ce Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Sun, 3 May 2026 15:37:13 -0400 Subject: [PATCH 1/2] Log a warning if the Rust extension doesn't import --- serialx/_serialx_rust.pyi | 14 ++++++++++++++ serialx/platforms/serial_darwin.py | 4 ++-- serialx/platforms/serial_win32.py | 4 ++-- serialx/serialx_rust.py | 25 +++++++++++++++++++++++++ src/lib.rs | 13 ++++--------- 5 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 serialx/_serialx_rust.pyi create mode 100644 serialx/serialx_rust.py diff --git a/serialx/_serialx_rust.pyi b/serialx/_serialx_rust.pyi new file mode 100644 index 0000000..fa273f4 --- /dev/null +++ b/serialx/_serialx_rust.pyi @@ -0,0 +1,14 @@ +"""Type stubs for the optional Rust extension.""" + +class RustSerialPortInfo: + device: str + vid: int | None + pid: int | None + serial_number: str | None + manufacturer: str | None + product: str | None + bcd_device: int | None + interface_description: str | None + interface_num: int | None + +def list_serial_ports_impl() -> list[RustSerialPortInfo]: ... diff --git a/serialx/platforms/serial_darwin.py b/serialx/platforms/serial_darwin.py index 9a4522e..a5e5cab 100644 --- a/serialx/platforms/serial_darwin.py +++ b/serialx/platforms/serial_darwin.py @@ -10,8 +10,8 @@ import sys import termios -from serialx._serialx_rust import list_serial_ports_darwin_impl from serialx.common import SerialPortInfo, register_uri_handler +from serialx.serialx_rust import list_serial_ports_impl from .serial_extended_posix import ExtendedPosixSerial, ExtendedPosixSerialTransport @@ -84,7 +84,7 @@ def darwin_list_serial_ports() -> list[SerialPortInfo]: interface_description=port.interface_description, interface_num=port.interface_num, ) - for port in list_serial_ports_darwin_impl() + for port in list_serial_ports_impl() ] diff --git a/serialx/platforms/serial_win32.py b/serialx/platforms/serial_win32.py index 54ad464..3320152 100644 --- a/serialx/platforms/serial_win32.py +++ b/serialx/platforms/serial_win32.py @@ -61,8 +61,8 @@ ) from winerror import ERROR_IO_PENDING -from serialx._serialx_rust import list_serial_ports_windows_impl from serialx.common import SerialPortInfo +from serialx.serialx_rust import list_serial_ports_impl from ..common import ( BaseSerial, @@ -736,7 +736,7 @@ def win32_list_serial_ports() -> list[SerialPortInfo]: interface_description=port.interface_description, interface_num=port.interface_num, ) - for port in list_serial_ports_windows_impl() + for port in list_serial_ports_impl() ] diff --git a/serialx/serialx_rust.py b/serialx/serialx_rust.py new file mode 100644 index 0000000..1b5ade0 --- /dev/null +++ b/serialx/serialx_rust.py @@ -0,0 +1,25 @@ +"""Centralized access to optional Rust extensions.""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from serialx._serialx_rust import RustSerialPortInfo + +LOGGER = logging.getLogger(__name__) + +__all__ = ["list_serial_ports_impl"] + +try: + from serialx._serialx_rust import list_serial_ports_impl +except ImportError: + LOGGER.warning( + "serialx Rust extension failed to load; serial port enumeration will " + "not be available" + ) + + def list_serial_ports_impl() -> list[RustSerialPortInfo]: + """Return an empty list when the Rust extension is unavailable.""" + return [] diff --git a/src/lib.rs b/src/lib.rs index 1c34945..71701b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,28 +22,23 @@ pub struct RustSerialPortInfo { #[cfg(target_os = "macos")] #[pyfunction] -fn list_serial_ports_darwin_impl() -> PyResult> { +fn list_serial_ports_impl() -> PyResult> { darwin::list_serial_ports().map_err(PyErr::new::) } #[cfg(target_os = "windows")] #[pyfunction] -fn list_serial_ports_windows_impl() -> PyResult> { +fn list_serial_ports_impl() -> PyResult> { windows::list_serial_ports().map_err(PyErr::new::) } #[pymodule] #[allow(unused_variables, clippy::missing_const_for_fn)] fn _serialx_rust(m: &Bound<'_, PyModule>) -> PyResult<()> { - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "windows"))] { m.add_class::()?; - m.add_function(wrap_pyfunction!(list_serial_ports_darwin_impl, m)?)?; - } - #[cfg(target_os = "windows")] - { - m.add_class::()?; - m.add_function(wrap_pyfunction!(list_serial_ports_windows_impl, m)?)?; + m.add_function(wrap_pyfunction!(list_serial_ports_impl, m)?)?; } Ok(()) } From 6d34d24516e5ae3becf301f4f8725ed1386d7abf Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Sun, 3 May 2026 16:30:47 -0400 Subject: [PATCH 2/2] Use consistent venv hash for all envs --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9801c2b..dff4271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: with: path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'src/lib.rs') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Install system dependencies if: steps.cache-venv.outputs.cache-hit != 'true' run: sudo apt-get update && sudo apt-get install -y libudev-dev @@ -80,7 +80,7 @@ jobs: fail-on-cache-miss: true path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'src/lib.rs') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libudev-dev - name: Restore prek environment from cache @@ -237,7 +237,7 @@ jobs: fail-on-cache-miss: true path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'src/lib.rs') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Restore cached ESPHome binary uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: @@ -330,7 +330,7 @@ jobs: with: path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Restore cached Rust extension id: cache-ext uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -410,7 +410,7 @@ jobs: with: path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Restore cached Rust extension id: cache-ext uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -479,7 +479,7 @@ jobs: fail-on-cache-miss: true path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'src/lib.rs') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Install serialx-compat and run tests run: | . venv/bin/activate @@ -538,7 +538,7 @@ jobs: fail-on-cache-miss: true path: venv key: >- - 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'src/lib.rs') }} + 1-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'Cargo.toml', 'Cargo.lock', 'src/**') }} - name: Download all coverage artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - name: Combine coverage results