From 716751bda18334e97e9bd5e0566a02b24a19cc4a Mon Sep 17 00:00:00 2001 From: Jacek Konieczny Date: Sun, 18 Oct 2026 11:10:48 +0200 Subject: [PATCH 1/2] Drop Python 3.9 support and modernize code - Remove Python 3.9 from classifiers in setup.cfg - Add python_requires >= 3.10 to setup.cfg - Update CI workflow to test on Python 3.10-3.14 instead of 3.9-3.14 - Update build-wheels.sh to build for Python 3.10-3.14 instead of 3.9-3.14 - Modernize typing to use PEP 604 union syntax (X | Y) instead of Union[X, Y] - Modernize typing to use PEP 604 optional syntax (X | None) instead of Optional[X] - Add from __future__ import annotations to all source files to enable new syntax --- .github/workflows/ci.yml | 6 +- alsa_midi/address.py | 2 +- alsa_midi/client.py | 136 +++++++++++++++++++------------------- alsa_midi/event.py | 52 ++++++++------- alsa_midi/mido_backend.py | 12 ++-- alsa_midi/port.py | 12 ++-- alsa_midi/queue.py | 18 ++--- build-wheels.sh | 4 +- setup.cfg | 2 +- tests/conftest.py | 5 +- 10 files changed, 129 insertions(+), 120 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64eccf2..5807e87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v3 @@ -110,10 +110,10 @@ jobs: with: fetch-depth: 0 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | diff --git a/alsa_midi/address.py b/alsa_midi/address.py index e6338d3..1a3da46 100644 --- a/alsa_midi/address.py +++ b/alsa_midi/address.py @@ -56,7 +56,7 @@ def __new__(cls, arg1: 'SequencerClient', arg2: int = 0) -> 'Address': ... def __new__(cls, - arg1: Union[int, str, AddressType, 'SequencerClient'], + arg1: int | str | AddressType | 'SequencerClient', arg2: int = 0) -> 'Address': if isinstance(arg1, str): tple: Any = cls._parse(arg1) diff --git a/alsa_midi/client.py b/alsa_midi/client.py index af690c4..6684d1f 100644 --- a/alsa_midi/client.py +++ b/alsa_midi/client.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import errno import select @@ -6,7 +8,7 @@ from dataclasses import dataclass, field from enum import IntEnum, IntFlag from functools import partial -from typing import Any, Callable, NewType, Optional, Union, overload +from typing import Any, Callable, NewType, overload from weakref import WeakValueDictionary from ._ffi import alsa, ffi @@ -84,12 +86,12 @@ class ClientInfo: name: str = "" broadcast_filter: bool = False error_bounce: bool = False - type: Optional[ClientType] = None - card_id: Optional[int] = None - pid: Optional[int] = None + type: ClientType | None = None + card_id: int | None = None + pid: int | None = None num_ports: int = 0 event_lost: int = 0 - event_filter: Optional[set[EventType]] = None + event_filter: set[EventType] | None = None @classmethod def _from_alsa(cls, info: _snd_seq_client_info_t): @@ -336,7 +338,7 @@ class RemoveEvents: Represents :alsa:`snd_seq_remove_events_t` data.""" condition: RemoveCondition = RemoveCondition(0) queue_id: int = 0 - time: Union[RealTime, int] = field(default_factory=RealTime) + time: RealTime | int = field(default_factory=RealTime) dest: AddressType = Address(0, 0) channel: int = 0 event_type: EventType = EventType.NONE @@ -359,7 +361,7 @@ class SequencerClientBase: handle: _snd_seq_t _handle_p: _snd_seq_t_p _fd: int = -1 - _event_parser: Optional[_snd_midi_event_t] = None + _event_parser: _snd_midi_event_t | None = None _queues: MutableMapping[int, Queue] def __init__( @@ -501,13 +503,13 @@ def create_port(self, caps: PortCaps = RW_PORT, type: PortType = DEFAULT_PORT_TYPE, *, - port_id: Optional[int] = None, - midi_channels: Optional[int] = None, - midi_voices: Optional[int] = None, - synth_voices: Optional[int] = None, - timestamping: Optional[bool] = None, - timestamp_real: Optional[bool] = None, - timestamp_queue: Optional[Union[Queue, int]] = None, + port_id: int | None = None, + midi_channels: int | None = None, + midi_voices: int | None = None, + synth_voices: int | None = None, + timestamping: bool | None = None, + timestamp_real: bool | None = None, + timestamp_queue: Queue | int | None = None, ) -> Port: """Create a sequencer port. @@ -676,7 +678,7 @@ def drop_output_buffer(self): err = alsa.snd_seq_drop_output_buffer(self.handle) _check_alsa_error(err) - def _event_input(self, prefer_bytes: bool = False) -> tuple[int, Optional[Event]]: + def _event_input(self, prefer_bytes: bool = False) -> tuple[int, Event | None]: buf = ffi.new("snd_seq_event_t**", ffi.NULL) result = alsa.snd_seq_event_input(self.handle, buf) if result < 0: @@ -742,10 +744,10 @@ def event_input_pending(self, fetch_sequencer: bool = False) -> int: def _prepare_event(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> tuple[_snd_seq_event_t, Any]: + remainder: Any = None) -> tuple[_snd_seq_event_t, Any]: """Prepare ALSA :alsa:`snd_seq_event_t` for given `event` object for output. For :class:`alsa_midi.MidiBytesEvent` may need to be called more than @@ -792,10 +794,10 @@ def _prepare_event(self, def _event_output(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> tuple[int, Any]: + remainder: Any = None) -> tuple[int, Any]: alsa_event, remainder = self._prepare_event(event, queue=queue, port=port, dest=dest, remainder=remainder) @@ -806,8 +808,8 @@ def _event_output(self, def event_output(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event. @@ -839,8 +841,8 @@ def event_output(self, def event_output_buffer(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event to a buffer. @@ -876,10 +878,10 @@ def event_output_buffer(self, def _event_output_direct(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> tuple[int, Any]: + remainder: Any = None) -> tuple[int, Any]: alsa_event, remainder = self._prepare_event(event, queue=queue, port=port, dest=dest, remainder=remainder) @@ -890,8 +892,8 @@ def _event_output_direct(self, def event_output_direct(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event directly to the sequencer. @@ -960,7 +962,7 @@ def get_system_info(self) -> SystemInfo: result = SystemInfo._from_alsa(info) return result - def get_client_info(self, client_id: Optional[int] = None) -> ClientInfo: + def get_client_info(self, client_id: int | None = None) -> ClientInfo: """Obtain information about a client. Wraps :alsa:`snd_seq_get_client_info` or :alsa:`snd_seq_get_any_client_info`. @@ -1005,15 +1007,15 @@ def set_client_event_filter(self, event_type: EventType): _check_alsa_error(err) @overload - def query_next_client(self, previous: ClientInfo) -> Optional[ClientInfo]: + def query_next_client(self, previous: ClientInfo) -> ClientInfo | None: ... @overload - def query_next_client(self, previous: Optional[int] = None) -> Optional[ClientInfo]: + def query_next_client(self, previous: int | None = None) -> ClientInfo | None: ... - def query_next_client(self, previous: Optional[Union[ClientInfo, int]] = None - ) -> Optional[ClientInfo]: + def query_next_client(self, previous: ClientInfo | int | None = None + ) -> ClientInfo | None: """Obtain information about the first or the next sequencer client. Wraps :alsa:`snd_seq_query_next_client`. @@ -1038,7 +1040,7 @@ def query_next_client(self, previous: Optional[Union[ClientInfo, int]] = None result = ClientInfo._from_alsa(info) return result - def get_port_info(self, port: Union[int, AddressType]) -> PortInfo: + def get_port_info(self, port: int | AddressType) -> PortInfo: """Obtain information about a specific port. Wraps :alsa:`snd_seq_get_port_info` or :alsa:`snd_seq_get_any_port_info`. @@ -1064,7 +1066,7 @@ def get_port_info(self, port: Union[int, AddressType]) -> PortInfo: result = PortInfo._from_alsa(info) return result - def set_port_info(self, port: Union[int, Port], info: PortInfo): + def set_port_info(self, port: int | Port, info: PortInfo): """Set information about a specific own port. Wraps :alsa:`snd_seq_set_port_info`. @@ -1079,18 +1081,18 @@ def set_port_info(self, port: Union[int, Port], info: PortInfo): @overload def query_next_port(self, client_id: int, previous: PortInfo - ) -> Optional[PortInfo]: + ) -> PortInfo | None: ... @overload - def query_next_port(self, client_id: int, previous: Optional[int] = None - ) -> Optional[PortInfo]: + def query_next_port(self, client_id: int, previous: int | None = None + ) -> PortInfo | None: ... def query_next_port(self, client_id: int, - previous: Optional[Union[PortInfo, int]] = None - ) -> Optional[PortInfo]: + previous: PortInfo | int | None = None + ) -> PortInfo | None: """Obtain information about the first or the next port of a sequencer client. Wraps :alsa:`snd_seq_query_next_port`. @@ -1128,7 +1130,7 @@ def list_ports(self, *, include_midi_through: bool = True, include_no_export: bool = True, only_connectable: bool = True, - sort: Union[bool, Callable[[PortInfo], Any]] = True, + sort: bool | Callable[[PortInfo], Any] = True, ) -> list[PortInfo]: """More friendly interface to list available ports. @@ -1250,7 +1252,7 @@ def list_ports(self, *, def _subunsub_port(self, func, sender: AddressType, dest: AddressType, *, - queue: Optional[Union[Queue, int]] = None, + queue: Queue | int | None = None, exclusive: bool = False, time_update: bool = False, time_real: bool = False): @@ -1278,7 +1280,7 @@ def _subunsub_port(self, func, _check_alsa_error(err) def subscribe_port(self, sender: AddressType, dest: AddressType, *, - queue: Optional[Union[Queue, int]] = None, + queue: Queue | int | None = None, exclusive: bool = False, time_update: bool = False, time_real: bool = False): @@ -1341,7 +1343,7 @@ def query_port_subscribers(self, def list_port_subscribers(self, port: AddressType, - type: Optional[SubscriptionQueryType] = None, + type: SubscriptionQueryType | None = None, ) -> list[SubscriptionQuery]: """Lists subscribers accessing a port. @@ -1504,8 +1506,8 @@ def remove_events(self, condition: RemoveEvents): @overload def remove_events(self, condition: RemoveCondition = None, - queue: Union[Queue, int] = None, - time: RealTime = None, + queue: Queue | int = None, + time: RealTime | int = None, dest: AddressType = None, channel: int = None, event_type: EventType = None, @@ -1516,9 +1518,9 @@ def remove_events(self, ... def remove_events(self, - condition: Union[RemoveEvents, RemoveCondition] = None, - queue: Union[Queue, int] = None, - time: Union[RealTime, int] = None, + condition: RemoveEvents | RemoveCondition = None, + queue: Queue | int = None, + time: RealTime | int = None, dest: AddressType = None, channel: int = None, event_type: EventType = None, @@ -1640,8 +1642,8 @@ def __init__(self, client_name: str, **kwargs): self._write_poll = select.poll() self._write_poll.register(self._fd, select.POLLOUT) - def event_input(self, prefer_bytes: bool = False, timeout: Optional[float] = None - ) -> Optional[Event]: + def event_input(self, prefer_bytes: bool = False, timeout: float | None = None + ) -> Event | None: """Wait for and receive an incoming event. Wraps :alsa:`snd_seq_event_input` and :alsa:`snd_midi_event_decode` when `prefer_bytes` is @@ -1677,10 +1679,10 @@ def _event_output_wait(self, func) -> int: ... @overload - def _event_output_wait(self, func, timeout: float) -> Union[int, None]: + def _event_output_wait(self, func, timeout: float) -> int | None: ... - def _event_output_wait(self, func, timeout: Optional[float] = None) -> Union[int, None]: + def _event_output_wait(self, func, timeout: float | None = None) -> int | None: if timeout: until = time.monotonic() + timeout else: @@ -1722,8 +1724,8 @@ def func(remainder=None): def event_output(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event. @@ -1749,8 +1751,8 @@ def event_output(self, def event_output_direct(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event without buffering. @@ -1800,8 +1802,8 @@ async def aclose(self): """ self.close() - async def event_input(self, prefer_bytes: bool = False, timeout: Optional[float] = None - ) -> Optional[Event]: + async def event_input(self, prefer_bytes: bool = False, timeout: float | None = None + ) -> Event | None: """Wait for and receive an incoming event. Wraps :alsa:`snd_seq_event_input` and alsa:`snd_midi_event_decode` when `prefer_bytes` is @@ -1853,10 +1855,10 @@ async def _event_output_wait(self, func) -> int: ... @overload - async def _event_output_wait(self, func, timeout: float) -> Union[int, None]: + async def _event_output_wait(self, func, timeout: float) -> int | None: ... - async def _event_output_wait(self, func, timeout: Optional[float] = None) -> Union[int, None]: + async def _event_output_wait(self, func, timeout: float | None = None) -> int | None: result, remainder = func() if result != -errno.EAGAIN and remainder is None: _check_alsa_error(result) @@ -1914,8 +1916,8 @@ def func(remainder=None): async def event_output(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event. @@ -1941,8 +1943,8 @@ async def event_output(self, async def event_output_direct(self, event: Event, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None) -> int: """Output an event without buffering. diff --git a/alsa_midi/event.py b/alsa_midi/event.py index b8eb6ca..e7e98de 100644 --- a/alsa_midi/event.py +++ b/alsa_midi/event.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from collections.abc import Iterable from enum import IntEnum, IntFlag from functools import total_ordering -from typing import TYPE_CHECKING, Any, NewType, Optional, Union +from typing import TYPE_CHECKING, Any, NewType from ._ffi import alsa, ffi from .address import Address, AddressType @@ -30,7 +32,7 @@ class RealTime: seconds: int nanoseconds: int - def __init__(self, seconds: Union[float, int, str, 'RealTime'] = 0, nanoseconds: int = 0): + def __init__(self, seconds: float | int | str | 'RealTime' = 0, nanoseconds: int = 0): if isinstance(seconds, RealTime): self.seconds = seconds.seconds self.nanoseconds = seconds.nanoseconds + nanoseconds @@ -229,28 +231,28 @@ class Event: _specialized = {} type = None - flags: Optional[EventFlags] + flags: EventFlags | None tag: int - queue_id: Optional[int] - time: Optional[RealTime] - tick: Optional[int] - source: Optional[Address] - dest: Optional[Address] - relative: Optional[bool] - raw_data: Optional[bytes] + queue_id: int | None + time: RealTime | None + tick: int | None + source: Address | None + dest: Address | None + relative: bool | None + raw_data: bytes | None def __init__(self, - type: Optional[EventType] = None, + type: EventType | None = None, *, - flags: Optional[Union[EventFlags, int]] = 0, + flags: EventFlags | int = 0, tag: int = 0, - queue_id: Optional[int] = None, - time: Optional[Union[RealTime, float, int]] = None, - tick: Optional[int] = None, - source: Optional[AddressType] = None, - dest: Optional[AddressType] = None, - relative: Optional[bool] = None, - raw_data: bytes = None, + queue_id: int | None = None, + time: RealTime | float | int | None = None, + tick: int | None = None, + source: AddressType | None = None, + dest: AddressType | None = None, + relative: bool | None = None, + raw_data: bytes | None = None, ): if type is None and self.__class__.type is not None: @@ -323,8 +325,8 @@ def _from_alsa(cls, a_event: _snd_seq_event_t, **kwargs): **kwargs) def _to_alsa(self, a_event: _snd_seq_event_t, *, - queue: Union['Queue', int] = None, - port: Union['Port', int] = None, + queue: 'Queue' | int = None, + port: 'Port' | int = None, dest: AddressType = None ) -> _snd_seq_event_t: if self.type is not None: @@ -404,7 +406,7 @@ class MidiBytesEvent(Event): midi_bytes: bytes def __init__(self, - midi_bytes: Union[bytes, Iterable[int]], + midi_bytes: bytes | Iterable[int], **kwargs): super().__init__(**kwargs) self.midi_bytes = bytes(midi_bytes) @@ -603,10 +605,10 @@ class QueueControlEventBase(Event): :ivar control_queue: affected queue id """ - control_queue: Optional[int] + control_queue: int | None def __init__(self, - control_queue: Optional[Union[int, 'Queue']] = None, + control_queue: int | 'Queue' | None = None, **kwargs): assert self.type is not None super().__init__(self.type, **kwargs) @@ -945,7 +947,7 @@ class SetQueuePositionTimeEvent(QueueControlEventBase): position: RealTime def __init__(self, - position: Union[RealTime, int, float], + position: RealTime | int | float, *args, **kwargs): super().__init__(*args, **kwargs) self.position = RealTime(position) diff --git a/alsa_midi/mido_backend.py b/alsa_midi/mido_backend.py index f1bb198..efdbe79 100644 --- a/alsa_midi/mido_backend.py +++ b/alsa_midi/mido_backend.py @@ -9,12 +9,14 @@ .. _MIDO: https://mido.readthedocs.io/ """ +from __future__ import annotations + import logging import queue import sys import threading from collections.abc import MutableMapping -from typing import Any, Callable, Optional +from typing import Any, Callable from weakref import WeakValueDictionary from mido.messages import Message @@ -27,7 +29,7 @@ class _Client: - instance: Optional['_Client'] = None + instance: '_Client' | None = None client: alsa_midi.SequencerClient in_thread: threading.Thread @@ -142,10 +144,10 @@ class PortCommon: _parser: Parser _virtual: bool = False - _callback: Optional[Callable[[Message], Any]] = None + _callback: Callable[[Message], Any] | None = None - _port: Optional[alsa_midi.Port] = None - _dest_port: Optional[alsa_midi.PortInfo] = None + _port: alsa_midi.Port | None = None + _dest_port: alsa_midi.PortInfo | None = None name: str @classmethod diff --git a/alsa_midi/port.py b/alsa_midi/port.py index 75b4e30..4e769eb 100644 --- a/alsa_midi/port.py +++ b/alsa_midi/port.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from dataclasses import InitVar, dataclass, field from enum import IntFlag -from typing import TYPE_CHECKING, Any, Callable, NewType, Optional +from typing import TYPE_CHECKING, Any, Callable, NewType from ._ffi import alsa, ffi from .address import Address, AddressType @@ -73,7 +75,7 @@ class Port: :ivar port_id: port identifier """ - client: Optional['SequencerClientBase'] + client: 'SequencerClientBase' | None client_id: int port_id: int @@ -217,7 +219,7 @@ class PortInfo: """ client_id: int = 0 - port_id: Optional[int] = None + port_id: int | None = None name: str = "" capability: PortCaps = PortCaps._NONE type: PortType = PortType.ANY @@ -226,12 +228,12 @@ class PortInfo: synth_voices: int = 0 read_use: int = 0 write_use: int = 0 - port_specified: InitVar[Optional[bool]] = None + port_specified: InitVar[bool | None] = None timestamping: bool = False timestamp_real: bool = False timestamp_queue_id: int = 0 - client_name: Optional[str] = field(init=False, default=None) + client_name: str | None = field(init=False, default=None) def __post_init__(self, port_specified=None): if port_specified is not None and not port_specified: diff --git a/alsa_midi/queue.py b/alsa_midi/queue.py index 2a9af38..a39465b 100644 --- a/alsa_midi/queue.py +++ b/alsa_midi/queue.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from dataclasses import dataclass, field from enum import IntEnum -from typing import TYPE_CHECKING, NamedTuple, NewType, Optional, Union +from typing import TYPE_CHECKING, NamedTuple, NewType from ._ffi import alsa, ffi from .event import EventType, RealTime @@ -128,8 +130,8 @@ class QueueTempo: tempo: int = 500000 ppq: int = 96 - skew: Optional[int] = None - skew_base: Optional[int] = None + skew: int | None = None + skew_base: int | None = None @classmethod def _from_alsa(cls, tempo: _snd_seq_queue_tempo_t): @@ -245,11 +247,11 @@ class Queue: release or free on close). """ - client: Optional['SequencerClientBase'] + client: 'SequencerClientBase' | None queue_id: int - _own: Optional[bool] + _own: bool | None - def __init__(self, client: 'SequencerClientBase', queue_id: int, *, _own: bool = None): + def __init__(self, client: 'SequencerClientBase', queue_id: int, *, _own: bool | None = None): self.client = client self.queue_id = queue_id self._own = _own @@ -287,8 +289,8 @@ def close(self): else: alsa.snd_seq_set_queue_usage(handle, queue, 0) - def set_tempo(self, tempo: Union[int, QueueTempo] = None, ppq: int = None, - skew=None, skew_base=None, bpm=None): + def set_tempo(self, tempo: int | QueueTempo = None, ppq: int | None = None, + skew: int | None = None, skew_base: int | None = None, bpm: float | None = None): """Set the tempo of the queue. :param tempo: MIDI tempo – microseconds per quarter note diff --git a/build-wheels.sh b/build-wheels.sh index a0e2e98..3e914f3 100755 --- a/build-wheels.sh +++ b/build-wheels.sh @@ -57,7 +57,7 @@ fi git config --global --add safe.directory "$GITHUB_WORKSPACE" # Compile wheels -for PYBIN in /opt/python/cp{39,310,311,312,313,314}*/bin; do +for PYBIN in /opt/python/cp{310,311,312,313,314}*/bin; do [ -d "$PYBIN" ] || continue if [[ "$PYBIN" =~ 313t ]] ; then echo "Skipping 313t variant, as it is incompatible with cffi" @@ -80,7 +80,7 @@ rm wheelhouse/*-linux_*.whl # Install packages and test cd / -for PYBIN in /opt/python/cp{39,310,311,312,313,314}*/bin/; do +for PYBIN in /opt/python/cp{310,311,312,313,314}*/bin/; do [ -d "$PYBIN" ] || continue if [[ "$PYBIN" =~ 313t ]] ; then echo "Skipping 313t variant, as it is incompatible with cffi" diff --git a/setup.cfg b/setup.cfg index ce75527..3554231 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,6 @@ license = MIT classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 @@ -27,6 +26,7 @@ project_urls = packages = alsa_midi zip_safe = False +python_requires = >=3.10 setup_requires = cffi>=1.16.0 install_requires = diff --git a/tests/conftest.py b/tests/conftest.py index 3508341..4c0d6d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,6 @@ import subprocess import sys from statistics import mean -from typing import Optional import pytest import pytest_asyncio @@ -42,7 +41,7 @@ class AlsaClientState: client_id: int name: str type: str - version: Optional[str] + version: str | None ports: dict[int, AlsaPortState] queues: dict[int, 'AlsaQueueState'] @@ -326,7 +325,7 @@ def aseqdump(): from alsa_midi import Address class Aseqdump: - process: Optional[subprocess.Popen] + process: subprocess.Popen | None output: list[tuple[Address, str]] port: Address From bc74160a288b0e5eef28683a3e6b53e49b25476c Mon Sep 17 00:00:00 2001 From: Jacek Konieczny Date: Sun, 18 Oct 2026 11:20:37 +0200 Subject: [PATCH 2/2] ci: properly quote python versions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5807e87..7c29cdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Python 3.11 uses: actions/setup-python@v4 with: - python-version: 3.11 + python-version: "3.11" - name: Install dependencies run: | @@ -113,7 +113,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: |