Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions serialx/_serialx_rust.pyi
Original file line number Diff line number Diff line change
@@ -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]: ...
4 changes: 2 additions & 2 deletions serialx/platforms/serial_darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
]


Expand Down
4 changes: 2 additions & 2 deletions serialx/platforms/serial_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
]


Expand Down
25 changes: 25 additions & 0 deletions serialx/serialx_rust.py
Original file line number Diff line number Diff line change
@@ -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 []
13 changes: 4 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,23 @@ pub struct RustSerialPortInfo {

#[cfg(target_os = "macos")]
#[pyfunction]
fn list_serial_ports_darwin_impl() -> PyResult<Vec<RustSerialPortInfo>> {
fn list_serial_ports_impl() -> PyResult<Vec<RustSerialPortInfo>> {
darwin::list_serial_ports().map_err(PyErr::new::<pyo3::exceptions::PyOSError, _>)
}

#[cfg(target_os = "windows")]
#[pyfunction]
fn list_serial_ports_windows_impl() -> PyResult<Vec<RustSerialPortInfo>> {
fn list_serial_ports_impl() -> PyResult<Vec<RustSerialPortInfo>> {
windows::list_serial_ports().map_err(PyErr::new::<pyo3::exceptions::PyOSError, _>)
}

#[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::<RustSerialPortInfo>()?;
m.add_function(wrap_pyfunction!(list_serial_ports_darwin_impl, m)?)?;
}
#[cfg(target_os = "windows")]
{
m.add_class::<RustSerialPortInfo>()?;
m.add_function(wrap_pyfunction!(list_serial_ports_windows_impl, m)?)?;
m.add_function(wrap_pyfunction!(list_serial_ports_impl, m)?)?;
}
Ok(())
}
Loading