diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37463ee..64eccf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "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.8 + - name: Set up Python 3.9 uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e02f420..34839c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/pycqa/flake8 - rev: '7.0.0' + rev: '7.3.0' hooks: - id: flake8 exclude: pbx_api_client/v1 - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: '7.0.0' hooks: - id: isort name: isort (python) diff --git a/alsa_midi/address.py b/alsa_midi/address.py index a816ea0..e6338d3 100644 --- a/alsa_midi/address.py +++ b/alsa_midi/address.py @@ -1,6 +1,5 @@ - from collections import namedtuple -from typing import TYPE_CHECKING, Any, Tuple, Union, overload +from typing import TYPE_CHECKING, Any, Union, overload from ._ffi import alsa, ffi from .util import _check_alsa_error @@ -10,7 +9,7 @@ from .port import Port, PortInfo -AddressType = Union['Address', 'Port', 'PortInfo', Tuple[int, int]] +AddressType = Union['Address', 'Port', 'PortInfo', tuple[int, int]] class Address(namedtuple("Address", "client_id port_id")): @@ -79,7 +78,7 @@ def __new__(cls, return tuple.__new__(cls, tple) @staticmethod - def _parse(arg: str) -> Tuple[int, int]: + def _parse(arg: str) -> tuple[int, int]: addr_p = ffi.new("snd_seq_addr_t *") result = alsa.snd_seq_parse_address(ffi.NULL, addr_p, arg.encode()) _check_alsa_error(result) diff --git a/alsa_midi/client.py b/alsa_midi/client.py index 22b1574..863b5ec 100644 --- a/alsa_midi/client.py +++ b/alsa_midi/client.py @@ -1,13 +1,12 @@ - import asyncio import errno import select import time +from collections.abc import MutableMapping from dataclasses import dataclass, field from enum import IntEnum, IntFlag from functools import partial -from typing import (Any, Callable, List, MutableMapping, NewType, Optional, Set, Tuple, Union, - overload) +from typing import Any, Callable, NewType, Optional, Union, overload from weakref import WeakValueDictionary from ._ffi import alsa, ffi @@ -21,7 +20,7 @@ from .util import _check_alsa_error _snd_seq_t = NewType("_snd_seq_t", object) -_snd_seq_t_p = NewType("_snd_seq_t_p", Tuple[_snd_seq_t]) +_snd_seq_t_p = NewType("_snd_seq_t_p", tuple[_snd_seq_t]) _snd_midi_event_t = NewType("_snd_midi_event_t", object) @@ -90,7 +89,7 @@ class ClientInfo: pid: Optional[int] = None num_ports: int = 0 event_lost: int = 0 - event_filter: Optional[Set[EventType]] = None + event_filter: Optional[set[EventType]] = None @classmethod def _from_alsa(cls, info: _snd_seq_client_info_t): @@ -661,7 +660,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, Optional[Event]]: buf = ffi.new("snd_seq_event_t**", ffi.NULL) result = alsa.snd_seq_event_input(self.handle, buf) if result < 0: @@ -730,7 +729,7 @@ def _prepare_event(self, queue: Union['Queue', int] = None, port: Union['Port', int] = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> Tuple[_snd_seq_event_t, Any]: + remainder: Optional[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 @@ -780,7 +779,7 @@ def _event_output(self, queue: Union['Queue', int] = None, port: Union['Port', int] = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> Tuple[int, Any]: + remainder: Optional[Any] = None) -> tuple[int, Any]: alsa_event, remainder = self._prepare_event(event, queue=queue, port=port, dest=dest, remainder=remainder) @@ -864,7 +863,7 @@ def _event_output_direct(self, queue: Union['Queue', int] = None, port: Union['Port', int] = None, dest: AddressType = None, - remainder: Optional[Any] = None) -> Tuple[int, Any]: + remainder: Optional[Any] = None) -> tuple[int, Any]: alsa_event, remainder = self._prepare_event(event, queue=queue, port=port, dest=dest, remainder=remainder) @@ -1114,7 +1113,7 @@ def list_ports(self, *, include_no_export: bool = True, only_connectable: bool = True, sort: Union[bool, Callable[[PortInfo], Any]] = True, - ) -> List[PortInfo]: + ) -> list[PortInfo]: """More friendly interface to list available ports. Queries ALSA for all clients and ports and returns those matching the selected criteria. @@ -1327,7 +1326,7 @@ def query_port_subscribers(self, def list_port_subscribers(self, port: AddressType, type: Optional[SubscriptionQueryType] = None, - ) -> List[SubscriptionQuery]: + ) -> list[SubscriptionQuery]: """Lists subscribers accessing a port. Wraps :alsa:`snd_seq_query_port_subscribers`. diff --git a/alsa_midi/event.py b/alsa_midi/event.py index aacc2fc..b8eb6ca 100644 --- a/alsa_midi/event.py +++ b/alsa_midi/event.py @@ -1,7 +1,7 @@ - +from collections.abc import Iterable from enum import IntEnum, IntFlag from functools import total_ordering -from typing import TYPE_CHECKING, Any, Iterable, NewType, Optional, Union +from typing import TYPE_CHECKING, Any, NewType, Optional, Union from ._ffi import alsa, ffi from .address import Address, AddressType diff --git a/alsa_midi/mido_backend.py b/alsa_midi/mido_backend.py index a2e3c3f..f1bb198 100644 --- a/alsa_midi/mido_backend.py +++ b/alsa_midi/mido_backend.py @@ -13,7 +13,8 @@ import queue import sys import threading -from typing import Any, Callable, List, MutableMapping, Optional +from collections.abc import MutableMapping +from typing import Any, Callable, Optional from weakref import WeakValueDictionary from mido.messages import Message @@ -92,7 +93,7 @@ def get_devices(*args, **kwargs): return client.get_devices(*args, **kwargs) -def _find_port(ports: List[alsa_midi.PortInfo], name: str) -> alsa_midi.PortInfo: +def _find_port(ports: list[alsa_midi.PortInfo], name: str) -> alsa_midi.PortInfo: try: addr = alsa_midi.Address(name) except (ValueError, alsa_midi.exceptions.ALSAError): @@ -104,7 +105,7 @@ def _find_port(ports: List[alsa_midi.PortInfo], name: str) -> alsa_midi.PortInfo if addr == alsa_midi.Address(port): return port else: - raise IOError(f"unknown port {name!r}") + raise OSError(f"unknown port {name!r}") # check for exact match with name from get_devices() for port in ports: @@ -128,10 +129,10 @@ def _find_port(ports: List[alsa_midi.PortInfo], name: str) -> alsa_midi.PortInfo if name == port.name: return port - raise IOError(f"unknown port {name!r}") + raise OSError(f"unknown port {name!r}") -class PortCommon(object): +class PortCommon: _last_num = 0 _name_prefix = "inout" _caps = alsa_midi.PortCaps.READ | alsa_midi.PortCaps.WRITE @@ -182,7 +183,7 @@ def _open(self, virtual=False, **kwargs): ports = client.client.list_ports(input=self._for_input, output=self._for_output) if not ports: - raise IOError('no ports available') + raise OSError('no ports available') if self.name is None: dest_port = ports[0] @@ -200,9 +201,9 @@ def _open(self, virtual=False, **kwargs): self._dest_port = dest_port api = 'seq' - self._device_type = 'AlsaMidi/{}'.format(api) + self._device_type = f'AlsaMidi/{api}' if virtual: - self._device_type = 'virtual {}'.format(self._device_type) + self._device_type = f'virtual {self._device_type}' client.ports[self._port.port_id] = self diff --git a/alsa_midi/port.py b/alsa_midi/port.py index 85aeda1..75b4e30 100644 --- a/alsa_midi/port.py +++ b/alsa_midi/port.py @@ -1,7 +1,6 @@ - from dataclasses import InitVar, dataclass, field from enum import IntFlag -from typing import TYPE_CHECKING, Any, Callable, List, NewType, Optional +from typing import TYPE_CHECKING, Any, Callable, NewType, Optional from ._ffi import alsa, ffi from .address import Address, AddressType @@ -176,7 +175,7 @@ def set_info(self, info: 'PortInfo'): raise StateError("Already closed") return self.client.set_port_info(self, info) - def list_subscribers(self, type: 'SubscriptionQueryType' = None) -> List['SubscriptionQuery']: + def list_subscribers(self, type: 'SubscriptionQueryType' = None) -> list['SubscriptionQuery']: """Lists subscribers accessing a port. Wraps :alsa:`snd_seq_query_port_subscribers`. @@ -286,7 +285,7 @@ def _to_alsa(self) -> _snd_seq_port_info_t: return info -def get_port_info_sort_key(preferred_types: List[PortType] = [] +def get_port_info_sort_key(preferred_types: list[PortType] = [] ) -> Callable[[PortInfo], Any]: """Return a :class:`PortInfo` sorting key function for given type preference.""" diff --git a/alsa_midi/queue.py b/alsa_midi/queue.py index fc13889..2a9af38 100644 --- a/alsa_midi/queue.py +++ b/alsa_midi/queue.py @@ -1,4 +1,3 @@ - from dataclasses import dataclass, field from enum import IntEnum from typing import TYPE_CHECKING, NamedTuple, NewType, Optional, Union diff --git a/alsa_midi/util.py b/alsa_midi/util.py index 6209726..f51be4d 100644 --- a/alsa_midi/util.py +++ b/alsa_midi/util.py @@ -1,4 +1,3 @@ - from ._ffi import alsa, ffi from .exceptions import ALSAError diff --git a/build-wheels.sh b/build-wheels.sh index e9f5333..a0e2e98 100755 --- a/build-wheels.sh +++ b/build-wheels.sh @@ -57,8 +57,12 @@ fi git config --global --add safe.directory "$GITHUB_WORKSPACE" # Compile wheels -for PYBIN in /opt/python/cp{37,38,39,310,311}*/bin; do +for PYBIN in /opt/python/cp{39,310,311,312,313,314}*/bin; do [ -d "$PYBIN" ] || continue + if [[ "$PYBIN" =~ 313t ]] ; then + echo "Skipping 313t variant, as it is incompatible with cffi" + continue + fi "${PYBIN}/pip" install --upgrade pip setuptools "${PYBIN}/pip" install -r "$GITHUB_WORKSPACE/requirements.txt" @@ -76,8 +80,12 @@ rm wheelhouse/*-linux_*.whl # Install packages and test cd / -for PYBIN in /opt/python/cp{37,38,39,310,311}*/bin/; do +for PYBIN in /opt/python/cp{39,310,311,312,313,314}*/bin/; do [ -d "$PYBIN" ] || continue + if [[ "$PYBIN" =~ 313t ]] ; then + echo "Skipping 313t variant, as it is incompatible with cffi" + continue + fi "${PYBIN}/pip" install pytest pytest-asyncio "${PYBIN}/pip" install alsa-midi --no-index -f "$GITHUB_WORKSPACE/wheelhouse/" diff --git a/requirements.txt b/requirements.txt index d765487..26fbf80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -cffi==1.15.0 +cffi==1.17.1 diff --git a/setup.cfg b/setup.cfg index 8f6fa79..ce75527 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,11 +6,12 @@ license = MIT classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 Development Status :: 5 - Production/Stable Framework :: AsyncIO Intended Audience :: Developers @@ -27,8 +28,8 @@ packages = alsa_midi zip_safe = False setup_requires = - cffi>=1.14.0 + cffi>=1.16.0 install_requires = - cffi>=1.14.0 + cffi>=1.16.0 #cffi_modules= # "alsa_midi/_ffi_defs.py:ffi" diff --git a/setup.py b/setup.py index 8c7d937..22cea19 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ - import os from setuptools import setup diff --git a/tests/conftest.py b/tests/conftest.py index 10d4dcd..3508341 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,10 @@ - import asyncio import os import re import subprocess import sys from statistics import mean -from typing import Dict, List, Optional, Tuple +from typing import Optional import pytest import pytest_asyncio @@ -16,8 +15,8 @@ class AlsaPortState: port_id: int name: str flags: str - connected_from: List[Tuple[int, int]] - connected_to: List[Tuple[int, int]] + connected_from: list[tuple[int, int]] + connected_to: list[tuple[int, int]] def __init__(self, client_id, port_id, name, flags): self.client_id = client_id @@ -44,8 +43,8 @@ class AlsaClientState: name: str type: str version: Optional[str] - ports: Dict[int, AlsaPortState] - queues: Dict[int, 'AlsaQueueState'] + ports: dict[int, AlsaPortState] + queues: dict[int, 'AlsaQueueState'] def __init__(self, client_id, name, client_type, midi_version): self.client_id = client_id @@ -80,7 +79,7 @@ class AlsaQueueState: timer_ppq: int current_tempo: int current_bpm: int - current_time: Tuple[int, int] + current_time: tuple[int, int] current_tick: int def __init__(self, lines): @@ -138,7 +137,7 @@ def __str__(self): PROC_CONN_FROM_LINE_RE = re.compile(r'\s+Connected From:\s*(\S.*)$') -def parse_port_list(string: str) -> List[Tuple[int, int]]: +def parse_port_list(string: str) -> list[tuple[int, int]]: port_list = [p.strip() for p in string.split(",")] port_list = [p.split("[", 1)[0] for p in port_list] port_list = [p.split(":", 1) for p in port_list] @@ -147,9 +146,9 @@ def parse_port_list(string: str) -> List[Tuple[int, int]]: class AlsaSequencerState: - clients: Dict[int, AlsaClientState] - ports: Dict[Tuple[int, int], AlsaPortState] - queues: Dict[int, AlsaQueueState] + clients: dict[int, AlsaClientState] + ports: dict[tuple[int, int], AlsaPortState] + queues: dict[int, AlsaQueueState] max_clients: int cur_clients: int @@ -174,7 +173,7 @@ def load(self): self.queues = {} print("clients:") - with open("/proc/asound/seq/clients", "r") as proc_f: + with open("/proc/asound/seq/clients") as proc_f: client = None port = None for line in proc_f: @@ -215,7 +214,7 @@ def load(self): continue print("queues:") - with open("/proc/asound/seq/queues", "r") as proc_f: + with open("/proc/asound/seq/queues") as proc_f: queues = [] queue_lines = [] for line in proc_f: @@ -247,10 +246,10 @@ def alsa_seq_state(): if not alsa_seq_present: try: # try triggering snd-seq module load - with open("/dev/snd/seq", "r"): + with open("/dev/snd/seq"): pass alsa_seq_present = os.path.exists("/proc/asound/seq/clients") - except IOError: + except OSError: pass @@ -328,7 +327,7 @@ def aseqdump(): class Aseqdump: process: Optional[subprocess.Popen] - output: List[Tuple[Address, str]] + output: list[tuple[Address, str]] port: Address def __init__(self, process: subprocess.Popen): diff --git a/tests/test_address.py b/tests/test_address.py index 7e6ad36..bbcf4ce 100644 --- a/tests/test_address.py +++ b/tests/test_address.py @@ -1,4 +1,3 @@ - import errno import pytest diff --git a/tests/test_client.py b/tests/test_client.py index d523898..b809ff5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,3 @@ - import errno import sys diff --git a/tests/test_compile_no_compile.py b/tests/test_compile_no_compile.py index 44c7bb1..c04b292 100644 --- a/tests/test_compile_no_compile.py +++ b/tests/test_compile_no_compile.py @@ -1,4 +1,3 @@ - import os import pytest diff --git a/tests/test_event.py b/tests/test_event.py index 2dcf20e..fc6f604 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -1,4 +1,3 @@ - import pytest from alsa_midi import (ActiveSensingEvent, Address, BounceEvent, ChannelPressureEvent, diff --git a/tests/test_event_async_input.py b/tests/test_event_async_input.py index 95b882e..57f60c6 100644 --- a/tests/test_event_async_input.py +++ b/tests/test_event_async_input.py @@ -1,4 +1,3 @@ - import asyncio import os import subprocess diff --git a/tests/test_event_async_output.py b/tests/test_event_async_output.py index 2c35010..3e66630 100644 --- a/tests/test_event_async_output.py +++ b/tests/test_event_async_output.py @@ -1,4 +1,3 @@ - import asyncio import pytest diff --git a/tests/test_event_input.py b/tests/test_event_input.py index d1813fd..3c66a63 100644 --- a/tests/test_event_input.py +++ b/tests/test_event_input.py @@ -1,4 +1,3 @@ - import os import subprocess import time diff --git a/tests/test_event_output.py b/tests/test_event_output.py index 30e7091..1e868b2 100644 --- a/tests/test_event_output.py +++ b/tests/test_event_output.py @@ -1,4 +1,3 @@ - import errno import time diff --git a/tests/test_port.py b/tests/test_port.py index c4f0a25..3ce81cb 100644 --- a/tests/test_port.py +++ b/tests/test_port.py @@ -1,4 +1,3 @@ - import errno from typing import Any diff --git a/tests/test_port_query.py b/tests/test_port_query.py index 8f96470..ab21345 100644 --- a/tests/test_port_query.py +++ b/tests/test_port_query.py @@ -1,4 +1,3 @@ - import pytest from alsa_midi import READ_PORT, PortCaps, PortType, SequencerClient diff --git a/tests/test_queue.py b/tests/test_queue.py index aa36d7c..4240235 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -1,4 +1,3 @@ - import errno import time diff --git a/tests/test_realtime.py b/tests/test_realtime.py index 2ba09ce..33743ef 100644 --- a/tests/test_realtime.py +++ b/tests/test_realtime.py @@ -1,4 +1,3 @@ - import pytest from alsa_midi import RealTime diff --git a/tests/test_remove_events.py b/tests/test_remove_events.py index 84ca345..0a7a295 100644 --- a/tests/test_remove_events.py +++ b/tests/test_remove_events.py @@ -1,4 +1,3 @@ - import pytest from alsa_midi import (READ_PORT, NoteOffEvent, NoteOnEvent, RemoveCondition, RemoveEvents, diff --git a/tests/test_subs_query.py b/tests/test_subs_query.py index f15ab3c..732a14d 100644 --- a/tests/test_subs_query.py +++ b/tests/test_subs_query.py @@ -1,4 +1,3 @@ - import errno import pytest diff --git a/tests/test_util.py b/tests/test_util.py index e896127..9b702f2 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,4 +1,3 @@ - import errno import os diff --git a/tox.ini b/tox.ini index d3ee612..9e7c555 100644 --- a/tox.ini +++ b/tox.ini @@ -26,9 +26,9 @@ commands = pytest --basetemp="{envtmpdir}" {posargs} basepython = python3 skip_install = true deps = - flake8==7.0.0 + flake8==7.3.0 flake8-junit-report==2.1.0 - isort==5.13.2 + isort==7.0.0 commands = flake8 alsa_midi tests examples isort --check --dont-follow-links alsa_midi tests