From 3f643de4c130ef14d364268e40cb0b2ed8787182 Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Thu, 1 Jan 2026 03:07:06 +0800 Subject: [PATCH] Ruff: Add and fix UP rules --- apps/auracast.py | 23 +- apps/bench.py | 9 +- apps/console.py | 11 +- apps/controller_loopback.py | 3 +- apps/device_info.py | 4 +- apps/pandora_server.py | 2 +- apps/player/player.py | 13 +- apps/rfcomm_bridge.py | 21 +- apps/speaker/speaker.py | 7 +- bumble/a2dp.py | 8 +- bumble/at.py | 7 +- bumble/att.py | 39 ++- bumble/audio/io.py | 3 +- bumble/avc.py | 3 +- bumble/avctp.py | 3 +- bumble/avdtp.py | 110 +++++---- bumble/avrcp.py | 50 ++-- bumble/colors.py | 15 +- bumble/controller.py | 222 +++++++++--------- bumble/core.py | 89 ++++--- bumble/crypto/builtin.py | 5 +- bumble/data_types.py | 3 +- bumble/decoder.py | 5 +- bumble/device.py | 204 ++++++++-------- bumble/drivers/__init__.py | 5 +- bumble/drivers/intel.py | 8 +- bumble/gatt.py | 13 +- bumble/gatt_adapters.py | 11 +- bumble/gatt_client.py | 58 +++-- bumble/gatt_server.py | 25 +- bumble/hci.py | 60 ++--- bumble/helpers.py | 10 +- bumble/hfp.py | 45 ++-- bumble/hid.py | 18 +- bumble/host.py | 57 +++-- bumble/keys.py | 34 +-- bumble/l2cap.py | 52 ++-- bumble/link.py | 4 +- bumble/pairing.py | 29 ++- bumble/pandora/__init__.py | 4 +- bumble/pandora/device.py | 6 +- bumble/pandora/host.py | 5 +- bumble/pandora/l2cap.py | 12 +- bumble/pandora/security.py | 24 +- bumble/pandora/utils.py | 8 +- bumble/profiles/aics.py | 13 +- bumble/profiles/ams.py | 8 +- bumble/profiles/ancs.py | 22 +- bumble/profiles/ascs.py | 10 +- bumble/profiles/asha.py | 19 +- bumble/profiles/bass.py | 5 +- bumble/profiles/battery_service.py | 3 +- bumble/profiles/csip.py | 19 +- bumble/profiles/device_information_service.py | 33 ++- bumble/profiles/gap.py | 7 +- bumble/profiles/gmap.py | 25 +- bumble/profiles/hap.py | 6 +- bumble/profiles/heart_rate_service.py | 15 +- bumble/profiles/mcp.py | 56 ++--- bumble/profiles/pacs.py | 30 +-- bumble/profiles/vcs.py | 2 +- bumble/profiles/vocs.py | 13 +- bumble/rfcomm.py | 37 +-- bumble/sdp.py | 21 +- bumble/smp.py | 39 +-- bumble/snoop.py | 3 +- bumble/transport/__init__.py | 3 +- bumble/transport/android_emulator.py | 5 +- bumble/transport/android_netsim.py | 15 +- bumble/transport/common.py | 18 +- bumble/transport/file.py | 3 +- bumble/transport/hci_socket.py | 5 +- bumble/transport/pty.py | 8 +- bumble/transport/pyusb.py | 5 +- bumble/transport/serial.py | 3 +- bumble/transport/vhci.py | 3 +- bumble/transport/ws_server.py | 5 +- bumble/utils.py | 13 +- bumble/vendor/android/hci.py | 3 +- examples/hid_report_parser.py | 4 +- examples/run_ams_client.py | 4 +- examples/run_ancs_client.py | 14 +- examples/run_asha_sink.py | 3 +- examples/run_avrcp.py | 3 +- examples/run_extended_advertiser_2.py | 6 +- examples/run_gatt_client_and_server.py | 4 +- examples/run_gatt_server.py | 4 +- examples/run_gatt_with_adapters.py | 6 +- examples/run_hfp_gateway.py | 8 +- examples/run_hfp_handsfree.py | 5 +- examples/run_mcp_client.py | 5 +- examples/run_vcp_renderer.py | 3 +- pyproject.toml | 3 + tests/a2dp_test.py | 2 +- tests/hfp_test.py | 5 +- tests/keystore_test.py | 4 +- tests/l2cap_test.py | 1 - tests/mcp_test.py | 2 +- tests/smp_test.py | 4 +- tests/test_utils.py | 3 +- tools/generate_company_id_list.py | 2 +- tools/intel_util.py | 4 +- 102 files changed, 922 insertions(+), 999 deletions(-) diff --git a/apps/auracast.py b/apps/auracast.py index 8ab89d50..fd43f15e 100644 --- a/apps/auracast.py +++ b/apps/auracast.py @@ -23,7 +23,8 @@ import contextlib import dataclasses import functools import logging -from typing import Any, AsyncGenerator, Coroutine, Optional +from collections.abc import AsyncGenerator, Coroutine +from typing import Any import click @@ -112,12 +113,12 @@ class BroadcastScanner(bumble.utils.EventEmitter): sync: bumble.device.PeriodicAdvertisingSync broadcast_id: int rssi: int = 0 - public_broadcast_announcement: Optional[pbp.PublicBroadcastAnnouncement] = None - broadcast_audio_announcement: Optional[bap.BroadcastAudioAnnouncement] = None - basic_audio_announcement: Optional[bap.BasicAudioAnnouncement] = None - appearance: Optional[core.Appearance] = None - biginfo: Optional[bumble.device.BigInfoAdvertisement] = None - manufacturer_data: Optional[tuple[str, bytes]] = None + public_broadcast_announcement: pbp.PublicBroadcastAnnouncement | None = None + broadcast_audio_announcement: bap.BroadcastAudioAnnouncement | None = None + basic_audio_announcement: bap.BasicAudioAnnouncement | None = None + appearance: core.Appearance | None = None + biginfo: bumble.device.BigInfoAdvertisement | None = None + manufacturer_data: tuple[str, bytes] | None = None def __post_init__(self) -> None: super().__init__() @@ -433,7 +434,7 @@ async def create_device(transport: str) -> AsyncGenerator[bumble.device.Device, async def find_broadcast_by_name( - device: bumble.device.Device, name: Optional[str] + device: bumble.device.Device, name: str | None ) -> BroadcastScanner.Broadcast: result = asyncio.get_running_loop().create_future() @@ -474,8 +475,8 @@ async def run_scan( async def run_assist( - broadcast_name: Optional[str], - source_id: Optional[int], + broadcast_name: str | None, + source_id: int | None, command: str, transport: str, address: str, @@ -658,7 +659,7 @@ async def run_pair(transport: str, address: str) -> None: async def run_receive( transport: str, - broadcast_id: Optional[int], + broadcast_id: int | None, output: str, broadcast_code: str | None, sync_timeout: float, diff --git a/apps/bench.py b/apps/bench.py index 52f81c07..0b39dcf3 100644 --- a/apps/bench.py +++ b/apps/bench.py @@ -22,7 +22,6 @@ import logging import statistics import struct import time -from typing import Optional import click @@ -257,8 +256,8 @@ async def pre_power_on(device: Device, classic: bool) -> None: async def post_power_on( device: Device, - le_scan: Optional[tuple[int, int]], - le_advertise: Optional[int], + le_scan: tuple[int, int] | None, + le_advertise: int | None, classic_page_scan: bool, classic_inquiry_scan: bool, ) -> None: @@ -1300,7 +1299,7 @@ class IsoClient(StreamedPacketIO): super().__init__() self.device = device self.ready = asyncio.Event() - self.cis_link: Optional[CisLink] = None + self.cis_link: CisLink | None = None async def on_connection( self, connection: Connection, cis_link: CisLink, sender: bool @@ -1341,7 +1340,7 @@ class IsoServer(StreamedPacketIO): ): super().__init__() self.device = device - self.cis_link: Optional[CisLink] = None + self.cis_link: CisLink | None = None self.ready = asyncio.Event() logging.info( diff --git a/apps/console.py b/apps/console.py index f99694cd..5a66c381 100644 --- a/apps/console.py +++ b/apps/console.py @@ -24,7 +24,6 @@ import logging import os import re from collections import OrderedDict -from typing import Optional, Union import click import humanize @@ -126,8 +125,8 @@ def parse_phys(phys): # Console App # ----------------------------------------------------------------------------- class ConsoleApp: - connected_peer: Optional[Peer] - connection_phy: Optional[ConnectionPHY] + connected_peer: Peer | None + connection_phy: ConnectionPHY | None def __init__(self): self.known_addresses = set() @@ -520,7 +519,7 @@ class ConsoleApp: self.show_attributes(attributes) - def find_remote_characteristic(self, param) -> Optional[CharacteristicProxy]: + def find_remote_characteristic(self, param) -> CharacteristicProxy | None: if not self.connected_peer: return None parts = param.split('.') @@ -542,9 +541,7 @@ class ConsoleApp: return None - def find_local_attribute( - self, param - ) -> Optional[Union[Characteristic, Descriptor]]: + def find_local_attribute(self, param) -> Characteristic | Descriptor | None: parts = param.split('.') if len(parts) == 3: service_uuid = UUID(parts[0]) diff --git a/apps/controller_loopback.py b/apps/controller_loopback.py index 87c94566..ab8ba2c3 100644 --- a/apps/controller_loopback.py +++ b/apps/controller_loopback.py @@ -17,7 +17,6 @@ # ----------------------------------------------------------------------------- import asyncio import time -from typing import Optional import click @@ -41,7 +40,7 @@ class Loopback: self.transport = transport self.packet_size = packet_size self.packet_count = packet_count - self.connection_handle: Optional[int] = None + self.connection_handle: int | None = None self.connection_event = asyncio.Event() self.done = asyncio.Event() self.expected_cid = 0 diff --git a/apps/device_info.py b/apps/device_info.py index 9ad50887..8c9367e2 100644 --- a/apps/device_info.py +++ b/apps/device_info.py @@ -16,7 +16,7 @@ # Imports # ----------------------------------------------------------------------------- import asyncio -from typing import Callable, Iterable, Optional +from collections.abc import Callable, Iterable import click @@ -174,7 +174,7 @@ async def show_vcs(vcs: VolumeControlServiceProxy) -> None: # ----------------------------------------------------------------------------- -async def show_device_info(peer, done: Optional[asyncio.Future]) -> None: +async def show_device_info(peer, done: asyncio.Future | None) -> None: try: # Discover all services print(color('### Discovering Services and Characteristics', 'magenta')) diff --git a/apps/pandora_server.py b/apps/pandora_server.py index 02d523e3..eed72d3f 100644 --- a/apps/pandora_server.py +++ b/apps/pandora_server.py @@ -44,7 +44,7 @@ def retrieve_config(config: str) -> dict[str, Any]: if not config: return {} - with open(config, 'r') as f: + with open(config) as f: return json.load(f) diff --git a/apps/player/player.py b/apps/player/player.py index 1edfb228..25756962 100644 --- a/apps/player/player.py +++ b/apps/player/player.py @@ -19,7 +19,6 @@ from __future__ import annotations import asyncio import logging -from typing import Optional, Union import click @@ -190,7 +189,7 @@ class Player: def __init__( self, transport: str, - device_config: Optional[str], + device_config: str | None, authenticate: bool, encrypt: bool, ) -> None: @@ -198,8 +197,8 @@ class Player: self.device_config = device_config self.authenticate = authenticate self.encrypt = encrypt - self.avrcp_protocol: Optional[AvrcpProtocol] = None - self.done: Optional[asyncio.Event] + self.avrcp_protocol: AvrcpProtocol | None = None + self.done: asyncio.Event | None async def run(self, workload) -> None: self.done = asyncio.Event() @@ -314,7 +313,7 @@ class Player: codec_type: int, vendor_id: int, codec_id: int, - packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource], + packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource, codec_capabilities: MediaCodecCapabilities, ): # Discover all endpoints on the remote device @@ -419,7 +418,7 @@ class Player: async def play( self, device: Device, - address: Optional[str], + address: str | None, audio_format: str, audio_file: str, ) -> None: @@ -448,7 +447,7 @@ class Player: return input_file.read(byte_count) # Obtain the codec capabilities from the stream - packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource] + packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource vendor_id = 0 codec_id = 0 if audio_format == "sbc": diff --git a/apps/rfcomm_bridge.py b/apps/rfcomm_bridge.py index 1c5aa18a..b0b27ca8 100644 --- a/apps/rfcomm_bridge.py +++ b/apps/rfcomm_bridge.py @@ -17,7 +17,6 @@ # ----------------------------------------------------------------------------- import asyncio import time -from typing import Optional import click @@ -82,14 +81,14 @@ class ServerBridge: def __init__( self, channel: int, uuid: str, trace: bool, tcp_host: str, tcp_port: int ) -> None: - self.device: Optional[Device] = None + self.device: Device | None = None self.channel = channel self.uuid = uuid self.tcp_host = tcp_host self.tcp_port = tcp_port - self.rfcomm_channel: Optional[rfcomm.DLC] = None - self.tcp_tracer: Optional[Tracer] - self.rfcomm_tracer: Optional[Tracer] + self.rfcomm_channel: rfcomm.DLC | None = None + self.tcp_tracer: Tracer | None + self.rfcomm_tracer: Tracer | None if trace: self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan")) @@ -242,14 +241,14 @@ class ClientBridge: self.tcp_port = tcp_port self.authenticate = authenticate self.encrypt = encrypt - self.device: Optional[Device] = None - self.connection: Optional[Connection] = None - self.rfcomm_client: Optional[rfcomm.Client] - self.rfcomm_mux: Optional[rfcomm.Multiplexer] + self.device: Device | None = None + self.connection: Connection | None = None + self.rfcomm_client: rfcomm.Client | None + self.rfcomm_mux: rfcomm.Multiplexer | None self.tcp_connected: bool = False - self.tcp_tracer: Optional[Tracer] - self.rfcomm_tracer: Optional[Tracer] + self.tcp_tracer: Tracer | None + self.rfcomm_tracer: Tracer | None if trace: self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan")) diff --git a/apps/speaker/speaker.py b/apps/speaker/speaker.py index 92f8ce0c..777025b6 100644 --- a/apps/speaker/speaker.py +++ b/apps/speaker/speaker.py @@ -26,7 +26,6 @@ import pathlib import subprocess import weakref from importlib import resources -from typing import Optional import aiohttp import click @@ -156,7 +155,7 @@ class QueuedOutput(Output): packets: asyncio.Queue extractor: AudioExtractor - packet_pump_task: Optional[asyncio.Task] + packet_pump_task: asyncio.Task | None started: bool def __init__(self, extractor): @@ -230,8 +229,8 @@ class WebSocketOutput(QueuedOutput): class FfplayOutput(QueuedOutput): MAX_QUEUE_SIZE = 32768 - subprocess: Optional[asyncio.subprocess.Process] - ffplay_task: Optional[asyncio.Task] + subprocess: asyncio.subprocess.Process | None + ffplay_task: asyncio.Task | None def __init__(self, codec: str) -> None: super().__init__(AudioExtractor.create(codec)) diff --git a/bumble/a2dp.py b/bumble/a2dp.py index 1ec2f7e0..4c9eb320 100644 --- a/bumble/a2dp.py +++ b/bumble/a2dp.py @@ -22,9 +22,9 @@ import enum import logging import struct from collections.abc import AsyncGenerator, Awaitable, Callable -from typing import Union +from typing import ClassVar -from typing_extensions import ClassVar, Self +from typing_extensions import Self from bumble import utils from bumble.codecs import AacAudioRtpPacket @@ -266,7 +266,7 @@ class MediaCodecInformation: @classmethod def create( cls, media_codec_type: int, data: bytes - ) -> Union[MediaCodecInformation, bytes]: + ) -> MediaCodecInformation | bytes: if media_codec_type == CodecType.SBC: return SbcMediaCodecInformation.from_bytes(data) elif media_codec_type == CodecType.MPEG_2_4_AAC: @@ -686,7 +686,7 @@ class SbcPacketSource: # Prepare for next packets sequence_number += 1 sequence_number &= 0xFFFF - sample_count += sum((frame.sample_count for frame in frames)) + sample_count += sum(frame.sample_count for frame in frames) frames = [frame] frames_size = len(frame.payload) else: diff --git a/bumble/at.py b/bumble/at.py index 30d19319..9fe85aec 100644 --- a/bumble/at.py +++ b/bumble/at.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union from bumble import core @@ -63,18 +62,18 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]: return [bytes(token) for token in tokens if len(token) > 0] -def parse_parameters(buffer: bytes) -> list[Union[bytes, list]]: +def parse_parameters(buffer: bytes) -> list[bytes | list]: """Parse the parameters using the comma and parenthesis separators. Raises AtParsingError in case of invalid input string.""" tokens = tokenize_parameters(buffer) accumulator: list[list] = [[]] - current: Union[bytes, list] = bytes() + current: bytes | list = b'' for token in tokens: if token == b',': accumulator[-1].append(current) - current = bytes() + current = b'' elif token == b'(': accumulator.append([]) elif token == b')': diff --git a/bumble/att.py b/bumble/att.py index 522986a0..89adaf9d 100644 --- a/bumble/att.py +++ b/bumble/att.py @@ -29,15 +29,12 @@ import enum import functools import inspect import struct +from collections.abc import Awaitable, Callable from typing import ( TYPE_CHECKING, - Awaitable, - Callable, ClassVar, Generic, - Optional, TypeVar, - Union, ) from bumble import hci, utils @@ -220,7 +217,7 @@ class ATT_PDU: fields: ClassVar[hci.Fields] = () op_code: int = dataclasses.field(init=False) name: str = dataclasses.field(init=False) - _payload: Optional[bytes] = dataclasses.field(default=None, init=False) + _payload: bytes | None = dataclasses.field(default=None, init=False) @classmethod def from_bytes(cls, pdu: bytes) -> ATT_PDU: @@ -760,26 +757,24 @@ class AttributeValue(Generic[_T]): def __init__( self, - read: Union[ - Callable[[Connection], _T], - Callable[[Connection], Awaitable[_T]], - None, - ] = None, - write: Union[ - Callable[[Connection, _T], None], - Callable[[Connection, _T], Awaitable[None]], - None, - ] = None, + read: ( + Callable[[Connection], _T] | Callable[[Connection], Awaitable[_T]] | None + ) = None, + write: ( + Callable[[Connection, _T], None] + | Callable[[Connection, _T], Awaitable[None]] + | None + ) = None, ): self._read = read self._write = write - def read(self, connection: Connection) -> Union[_T, Awaitable[_T]]: + def read(self, connection: Connection) -> _T | Awaitable[_T]: if self._read is None: raise InvalidOperationError('AttributeValue has no read function') return self._read(connection) - def write(self, connection: Connection, value: _T) -> Union[Awaitable[None], None]: + def write(self, connection: Connection, value: _T) -> Awaitable[None] | None: if self._write is None: raise InvalidOperationError('AttributeValue has no write function') return self._write(connection, value) @@ -828,13 +823,13 @@ class Attribute(utils.EventEmitter, Generic[_T]): EVENT_READ = "read" EVENT_WRITE = "write" - value: Union[AttributeValue[_T], _T, None] + value: AttributeValue[_T] | _T | None def __init__( self, - attribute_type: Union[str, bytes, UUID], - permissions: Union[str, Attribute.Permissions], - value: Union[AttributeValue[_T], _T, None] = None, + attribute_type: str | bytes | UUID, + permissions: str | Attribute.Permissions, + value: AttributeValue[_T] | _T | None = None, ) -> None: utils.EventEmitter.__init__(self) self.handle = 0 @@ -883,7 +878,7 @@ class Attribute(utils.EventEmitter, Generic[_T]): error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle ) - value: Union[_T, None] + value: _T | None if isinstance(self.value, AttributeValue): try: read_value = self.value.read(connection) diff --git a/bumble/audio/io.py b/bumble/audio/io.py index 98bc1a0f..a60cd331 100644 --- a/bumble/audio/io.py +++ b/bumble/audio/io.py @@ -26,7 +26,8 @@ import logging import pathlib import sys import wave -from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO +from collections.abc import AsyncGenerator +from typing import TYPE_CHECKING, BinaryIO from bumble.colors import color diff --git a/bumble/avc.py b/bumble/avc.py index 2424d3bb..535a4940 100644 --- a/bumble/avc.py +++ b/bumble/avc.py @@ -19,7 +19,6 @@ from __future__ import annotations import enum import struct -from typing import Union from bumble import core, utils @@ -166,7 +165,7 @@ class Frame: def to_bytes( self, - ctype_or_response: Union[CommandFrame.CommandType, ResponseFrame.ResponseCode], + ctype_or_response: CommandFrame.CommandType | ResponseFrame.ResponseCode, ) -> bytes: # TODO: support extended subunit types and ids. return ( diff --git a/bumble/avctp.py b/bumble/avctp.py index 252d4c79..157e5c5e 100644 --- a/bumble/avctp.py +++ b/bumble/avctp.py @@ -21,7 +21,6 @@ import logging import struct from collections.abc import Callable from enum import IntEnum -from typing import Optional from bumble import core, l2cap from bumble.colors import color @@ -147,7 +146,7 @@ class MessageAssembler: class Protocol: CommandHandler = Callable[[int, bytes], None] command_handlers: dict[int, CommandHandler] # Command handlers, by PID - ResponseHandler = Callable[[int, Optional[bytes]], None] + ResponseHandler = Callable[[int, bytes | None], None] response_handlers: dict[int, ResponseHandler] # Response handlers, by PID next_transaction_label: int message_assembler: MessageAssembler diff --git a/bumble/avdtp.py b/bumble/avdtp.py index 0f7bebe9..bcc9f7e6 100644 --- a/bumble/avdtp.py +++ b/bumble/avdtp.py @@ -22,16 +22,13 @@ import enum import logging import time import warnings -from collections.abc import AsyncGenerator, Awaitable, Iterable +from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable from dataclasses import dataclass, field from typing import ( Any, - Callable, ClassVar, - Optional, SupportsBytes, TypeVar, - Union, cast, ) @@ -184,7 +181,7 @@ class State(utils.OpenIntEnum): # ----------------------------------------------------------------------------- async def find_avdtp_service_with_sdp_client( sdp_client: sdp.Client, -) -> Optional[tuple[int, int]]: +) -> tuple[int, int] | None: ''' Find an AVDTP service, using a connected SDP client, and return its version, or None if none is found @@ -214,7 +211,7 @@ async def find_avdtp_service_with_sdp_client( # ----------------------------------------------------------------------------- async def find_avdtp_service_with_connection( connection: device.Connection, -) -> Optional[tuple[int, int]]: +) -> tuple[int, int] | None: ''' Find an AVDTP service, for a connection, and return its version, or None if none is found @@ -239,7 +236,7 @@ class RealtimeClock: # ----------------------------------------------------------------------------- class MediaPacketPump: - pump_task: Optional[asyncio.Task] + pump_task: asyncio.Task | None def __init__( self, packets: AsyncGenerator, clock: RealtimeClock = RealtimeClock() @@ -296,7 +293,7 @@ class MediaPacketPump: # ----------------------------------------------------------------------------- class MessageAssembler: - message: Optional[bytes] + message: bytes | None signal_identifier: SignalIdentifier def __init__(self, callback: Callable[[int, Message], Any]) -> None: @@ -470,15 +467,15 @@ class MediaCodecCapabilities(ServiceCapabilities): media_type: MediaType media_codec_type: a2dp.CodecType - media_codec_information: Union[bytes, SupportsBytes] + media_codec_information: bytes | SupportsBytes # Override init to allow passing service_capabilities_bytes. def __init__( self, media_type: MediaType, media_codec_type: a2dp.CodecType, - media_codec_information: Union[bytes, SupportsBytes], - service_capabilities_bytes: Optional[bytes] = None, + media_codec_information: bytes | SupportsBytes, + service_capabilities_bytes: bytes | None = None, ) -> None: self.media_type = media_type self.media_codec_type = media_codec_type @@ -553,7 +550,7 @@ class Message: message_type: MessageType signal_identifier: SignalIdentifier - _payload: Optional[bytes] = None + _payload: bytes | None = None fields: ClassVar[hci.Fields] = () @property @@ -607,7 +604,7 @@ class Message: instance.signal_identifier = signal_identifier return instance - def to_string(self, details: Union[str, Iterable[str]]) -> str: + def to_string(self, details: str | Iterable[str]) -> str: base = color( f'{self.signal_identifier.name}_{self.message_type.name}', 'yellow', @@ -1266,9 +1263,9 @@ class Protocol(utils.EventEmitter): local_endpoints: list[LocalStreamEndPoint] remote_endpoints: dict[int, DiscoveredStreamEndPoint] streams: dict[int, Stream] - transaction_results: list[Optional[asyncio.Future[Message]]] + transaction_results: list[asyncio.Future[Message] | None] channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]] - channel_acceptor: Optional[Stream] + channel_acceptor: Stream | None EVENT_OPEN = "open" EVENT_CLOSE = "close" @@ -1311,7 +1308,7 @@ class Protocol(utils.EventEmitter): l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open) l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close) - def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]: + def get_local_endpoint_by_seid(self, seid: int) -> LocalStreamEndPoint | None: if 0 < seid <= len(self.local_endpoints): return self.local_endpoints[seid - 1] @@ -1385,7 +1382,7 @@ class Protocol(utils.EventEmitter): def find_remote_sink_by_codec( self, media_type: int, codec_type: int, vendor_id: int = 0, codec_id: int = 0 - ) -> Optional[DiscoveredStreamEndPoint]: + ) -> DiscoveredStreamEndPoint | None: for endpoint in self.remote_endpoints.values(): if ( not endpoint.in_use @@ -1569,10 +1566,9 @@ class Protocol(utils.EventEmitter): assert False # Should never reach this - async def get_capabilities(self, seid: int) -> Union[ - Get_Capabilities_Response, - Get_All_Capabilities_Response, - ]: + async def get_capabilities( + self, seid: int + ) -> Get_Capabilities_Response | Get_All_Capabilities_Response: if self.version > (1, 2): return await self.send_command(Get_All_Capabilities_Command(seid)) @@ -1604,7 +1600,7 @@ class Protocol(utils.EventEmitter): async def abort(self, seid: int) -> Abort_Response: return await self.send_command(Abort_Command(seid)) - def on_discover_command(self, command: Discover_Command) -> Optional[Message]: + def on_discover_command(self, command: Discover_Command) -> Message | None: endpoint_infos = [ EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep) for endpoint in self.local_endpoints @@ -1613,7 +1609,7 @@ class Protocol(utils.EventEmitter): def on_get_capabilities_command( self, command: Get_Capabilities_Command - ) -> Optional[Message]: + ) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1622,7 +1618,7 @@ class Protocol(utils.EventEmitter): def on_get_all_capabilities_command( self, command: Get_All_Capabilities_Command - ) -> Optional[Message]: + ) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1631,7 +1627,7 @@ class Protocol(utils.EventEmitter): def on_set_configuration_command( self, command: Set_Configuration_Command - ) -> Optional[Message]: + ) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR) @@ -1649,7 +1645,7 @@ class Protocol(utils.EventEmitter): def on_get_configuration_command( self, command: Get_Configuration_Command - ) -> Optional[Message]: + ) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1658,7 +1654,7 @@ class Protocol(utils.EventEmitter): return endpoint.stream.on_get_configuration_command() - def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]: + def on_reconfigure_command(self, command: Reconfigure_Command) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR) @@ -1668,7 +1664,7 @@ class Protocol(utils.EventEmitter): result = endpoint.stream.on_reconfigure_command(command.capabilities) return result or Reconfigure_Response() - def on_open_command(self, command: Open_Command) -> Optional[Message]: + def on_open_command(self, command: Open_Command) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1678,7 +1674,7 @@ class Protocol(utils.EventEmitter): result = endpoint.stream.on_open_command() return result or Open_Response() - def on_start_command(self, command: Start_Command) -> Optional[Message]: + def on_start_command(self, command: Start_Command) -> Message | None: for seid in command.acp_seids: endpoint = self.get_local_endpoint_by_seid(seid) if endpoint is None: @@ -1697,7 +1693,7 @@ class Protocol(utils.EventEmitter): return Start_Response() - def on_suspend_command(self, command: Suspend_Command) -> Optional[Message]: + def on_suspend_command(self, command: Suspend_Command) -> Message | None: for seid in command.acp_seids: endpoint = self.get_local_endpoint_by_seid(seid) if endpoint is None: @@ -1716,7 +1712,7 @@ class Protocol(utils.EventEmitter): return Suspend_Response() - def on_close_command(self, command: Close_Command) -> Optional[Message]: + def on_close_command(self, command: Close_Command) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1726,7 +1722,7 @@ class Protocol(utils.EventEmitter): result = endpoint.stream.on_close_command() return result or Close_Response() - def on_abort_command(self, command: Abort_Command) -> Optional[Message]: + def on_abort_command(self, command: Abort_Command) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None or endpoint.stream is None: return Abort_Response() @@ -1736,7 +1732,7 @@ class Protocol(utils.EventEmitter): def on_security_control_command( self, command: Security_Control_Command - ) -> Optional[Message]: + ) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1744,7 +1740,7 @@ class Protocol(utils.EventEmitter): result = endpoint.on_security_control_command(command.data) return result or Security_Control_Response() - def on_delayreport_command(self, command: DelayReport_Command) -> Optional[Message]: + def on_delayreport_command(self, command: DelayReport_Command) -> Message | None: endpoint = self.get_local_endpoint_by_seid(command.acp_seid) if endpoint is None: return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR) @@ -1825,7 +1821,7 @@ class Stream: Pair of a local and a remote stream endpoint that can stream from one to the other ''' - rtp_channel: Optional[l2cap.ClassicChannel] + rtp_channel: l2cap.ClassicChannel | None def change_state(self, state: State) -> None: logger.debug(f'{self} state change -> {color(state.name, "cyan")}') @@ -1914,7 +1910,7 @@ class Stream: def on_set_configuration_command( self, configuration: Iterable[ServiceCapabilities] - ) -> Optional[Message]: + ) -> Message | None: if self.state != State.IDLE: return Set_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR) @@ -1925,7 +1921,7 @@ class Stream: self.change_state(State.CONFIGURED) return None - def on_get_configuration_command(self) -> Optional[Message]: + def on_get_configuration_command(self) -> Message | None: if self.state not in ( State.CONFIGURED, State.OPEN, @@ -1937,7 +1933,7 @@ class Stream: def on_reconfigure_command( self, configuration: Iterable[ServiceCapabilities] - ) -> Optional[Message]: + ) -> Message | None: if self.state != State.OPEN: return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR) @@ -1947,7 +1943,7 @@ class Stream: return None - def on_open_command(self) -> Optional[Message]: + def on_open_command(self) -> Message | None: if self.state != State.CONFIGURED: return Open_Reject(AVDTP_BAD_STATE_ERROR) @@ -1961,7 +1957,7 @@ class Stream: self.change_state(State.OPEN) return None - def on_start_command(self) -> Optional[Message]: + def on_start_command(self) -> Message | None: if self.state != State.OPEN: return Open_Reject(AVDTP_BAD_STATE_ERROR) @@ -1977,7 +1973,7 @@ class Stream: self.change_state(State.STREAMING) return None - def on_suspend_command(self) -> Optional[Message]: + def on_suspend_command(self) -> Message | None: if self.state != State.STREAMING: return Open_Reject(AVDTP_BAD_STATE_ERROR) @@ -1988,7 +1984,7 @@ class Stream: self.change_state(State.OPEN) return None - def on_close_command(self) -> Optional[Message]: + def on_close_command(self) -> Message | None: if self.state not in (State.OPEN, State.STREAMING): return Open_Reject(AVDTP_BAD_STATE_ERROR) @@ -2007,7 +2003,7 @@ class Stream: return None - def on_abort_command(self) -> Optional[Message]: + def on_abort_command(self) -> Message | None: if self.rtp_channel is None: # No need to wait self.change_state(State.IDLE) @@ -2120,7 +2116,7 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy): # ----------------------------------------------------------------------------- class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter): - stream: Optional[Stream] + stream: Stream | None EVENT_CONFIGURATION = "configuration" EVENT_OPEN = "open" @@ -2142,7 +2138,7 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter): media_type: MediaType, tsep: StreamEndPointType, capabilities: Iterable[ServiceCapabilities], - configuration: Optional[Iterable[ServiceCapabilities]] = None, + configuration: Iterable[ServiceCapabilities] | None = None, ): StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities) utils.EventEmitter.__init__(self) @@ -2161,13 +2157,13 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter): def on_reconfigure_command( self, command: Iterable[ServiceCapabilities] - ) -> Optional[Message]: + ) -> Message | None: del command # unused. return None def on_set_configuration_command( self, configuration: Iterable[ServiceCapabilities] - ) -> Optional[Message]: + ) -> Message | None: logger.debug( '<<< received configuration: ' f'{",".join([str(capability) for capability in configuration])}' @@ -2176,34 +2172,34 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter): self.emit(self.EVENT_CONFIGURATION) return None - def on_get_configuration_command(self) -> Optional[Message]: + def on_get_configuration_command(self) -> Message | None: return Get_Configuration_Response(self.configuration) - def on_open_command(self) -> Optional[Message]: + def on_open_command(self) -> Message | None: self.emit(self.EVENT_OPEN) return None - def on_start_command(self) -> Optional[Message]: + def on_start_command(self) -> Message | None: self.emit(self.EVENT_START) return None - def on_suspend_command(self) -> Optional[Message]: + def on_suspend_command(self) -> Message | None: self.emit(self.EVENT_SUSPEND) return None - def on_close_command(self) -> Optional[Message]: + def on_close_command(self) -> Message | None: self.emit(self.EVENT_CLOSE) return None - def on_abort_command(self) -> Optional[Message]: + def on_abort_command(self) -> Message | None: self.emit(self.EVENT_ABORT) return None - def on_delayreport_command(self, delay: int) -> Optional[Message]: + def on_delayreport_command(self, delay: int) -> Message | None: self.emit(self.EVENT_DELAY_REPORT, delay) return None - def on_security_control_command(self, data: bytes) -> Optional[Message]: + def on_security_control_command(self, data: bytes) -> Message | None: self.emit(self.EVENT_SECURITY_CONTROL, data) return None @@ -2255,12 +2251,12 @@ class LocalSource(LocalStreamEndPoint): self.emit(self.EVENT_STOP) @override - def on_start_command(self) -> Optional[Message]: + def on_start_command(self) -> Message | None: asyncio.create_task(self.start()) return None @override - def on_suspend_command(self) -> Optional[Message]: + def on_suspend_command(self) -> Message | None: asyncio.create_task(self.stop()) return None diff --git a/bumble/avrcp.py b/bumble/avrcp.py index de381827..7bfae725 100644 --- a/bumble/avrcp.py +++ b/bumble/avrcp.py @@ -24,7 +24,7 @@ import logging import struct from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence from dataclasses import dataclass, field -from typing import ClassVar, Optional, SupportsBytes, TypeVar, Union +from typing import ClassVar, SupportsBytes, TypeVar from bumble import avc, avctp, core, hci, l2cap, utils from bumble.colors import color @@ -196,7 +196,7 @@ def make_controller_service_sdp_records( service_record_handle: int, avctp_version: tuple[int, int] = (1, 4), avrcp_version: tuple[int, int] = (1, 6), - supported_features: Union[int, ControllerFeatures] = 1, + supported_features: int | ControllerFeatures = 1, ) -> list[ServiceAttribute]: avctp_version_int = avctp_version[0] << 8 | avctp_version[1] avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1] @@ -288,7 +288,7 @@ def make_target_service_sdp_records( service_record_handle: int, avctp_version: tuple[int, int] = (1, 4), avrcp_version: tuple[int, int] = (1, 6), - supported_features: Union[int, TargetFeatures] = 0x23, + supported_features: int | TargetFeatures = 0x23, ) -> list[ServiceAttribute]: # TODO: support a way to compute the supported features from a feature list avctp_version_int = avctp_version[0] << 8 | avctp_version[1] @@ -478,7 +478,7 @@ class BrowseableItem: MEDIA_ELEMENT = 0x03 item_type: ClassVar[Type] - _payload: Optional[bytes] = None + _payload: bytes | None = None subclasses: ClassVar[dict[Type, type[BrowseableItem]]] = {} fields: ClassVar[hci.Fields] = () @@ -672,7 +672,7 @@ class PduAssembler: 6.3.1 AVRCP specific AV//C commands """ - pdu_id: Optional[PduId] + pdu_id: PduId | None payload: bytes def __init__(self, callback: Callable[[PduId, bytes], None]) -> None: @@ -725,7 +725,7 @@ class PduAssembler: # ----------------------------------------------------------------------------- class Command: pdu_id: ClassVar[PduId] - _payload: Optional[bytes] = None + _payload: bytes | None = None _Command = TypeVar('_Command', bound='Command') subclasses: ClassVar[dict[int, type[Command]]] = {} @@ -1017,7 +1017,7 @@ class AddToNowPlayingCommand(Command): # ----------------------------------------------------------------------------- class Response: pdu_id: PduId - _payload: Optional[bytes] = None + _payload: bytes | None = None fields: ClassVar[hci.Fields] = () subclasses: ClassVar[dict[PduId, type[Response]]] = {} @@ -1079,7 +1079,7 @@ class NotImplementedResponse(Response): class GetCapabilitiesResponse(Response): pdu_id = PduId.GET_CAPABILITIES capability_id: GetCapabilitiesCommand.CapabilityId - capabilities: Sequence[Union[SupportsBytes, bytes]] + capabilities: Sequence[SupportsBytes | bytes] @classmethod def from_parameters(cls, parameters: bytes) -> Response: @@ -1092,7 +1092,7 @@ class GetCapabilitiesResponse(Response): capability_id = GetCapabilitiesCommand.CapabilityId(parameters[0]) capability_count = parameters[1] - capabilities: list[Union[SupportsBytes, bytes]] + capabilities: list[SupportsBytes | bytes] if capability_id == GetCapabilitiesCommand.CapabilityId.EVENTS_SUPPORTED: capabilities = [EventId(parameters[2 + x]) for x in range(capability_count)] else: @@ -1363,7 +1363,7 @@ class AddToNowPlayingResponse(Response): # ----------------------------------------------------------------------------- class Event: event_id: EventId - _pdu: Optional[bytes] = None + _pdu: bytes | None = None _Event = TypeVar('_Event', bound='Event') subclasses: ClassVar[dict[int, type[Event]]] = {} @@ -1436,13 +1436,13 @@ class PlayerApplicationSettingChangedEvent(Event): attribute_id: ApplicationSetting.AttributeId = field( metadata=ApplicationSetting.AttributeId.type_metadata(1) ) - value_id: Union[ - ApplicationSetting.EqualizerOnOffStatus, - ApplicationSetting.RepeatModeStatus, - ApplicationSetting.ShuffleOnOffStatus, - ApplicationSetting.ScanOnOffStatus, - ApplicationSetting.GenericValue, - ] = field(metadata=hci.metadata(1)) + value_id: ( + ApplicationSetting.EqualizerOnOffStatus + | ApplicationSetting.RepeatModeStatus + | ApplicationSetting.ShuffleOnOffStatus + | ApplicationSetting.ScanOnOffStatus + | ApplicationSetting.GenericValue + ) = field(metadata=hci.metadata(1)) def __post_init__(self) -> None: super().__post_init__() @@ -1628,17 +1628,17 @@ class Protocol(utils.EventEmitter): delegate: Delegate send_transaction_label: int command_pdu_assembler: PduAssembler - receive_command_state: Optional[ReceiveCommandState] + receive_command_state: ReceiveCommandState | None response_pdu_assembler: PduAssembler - receive_response_state: Optional[ReceiveResponseState] - avctp_protocol: Optional[avctp.Protocol] + receive_response_state: ReceiveResponseState | None + avctp_protocol: avctp.Protocol | None free_commands: asyncio.Queue pending_commands: dict[int, PendingCommand] # Pending commands, by label notification_listeners: dict[EventId, NotificationListener] @staticmethod def _check_vendor_dependent_frame( - frame: Union[avc.VendorDependentCommandFrame, avc.VendorDependentResponseFrame], + frame: avc.VendorDependentCommandFrame | avc.VendorDependentResponseFrame, ) -> bool: if frame.company_id != AVRCP_BLUETOOTH_SIG_COMPANY_ID: logger.debug("unsupported company id, ignoring") @@ -1650,7 +1650,7 @@ class Protocol(utils.EventEmitter): return True - def __init__(self, delegate: Optional[Delegate] = None) -> None: + def __init__(self, delegate: Delegate | None = None) -> None: super().__init__() self.delegate = delegate if delegate else Delegate() self.command_pdu_assembler = PduAssembler(self._on_command_pdu) @@ -2067,9 +2067,7 @@ class Protocol(utils.EventEmitter): # TODO handle other types self.send_not_implemented_response(transaction_label, command) - def _on_avctp_response( - self, transaction_label: int, payload: Optional[bytes] - ) -> None: + def _on_avctp_response(self, transaction_label: int, payload: bytes | None) -> None: response = avc.ResponseFrame.from_bytes(payload) if payload else None if not isinstance(response, avc.ResponseFrame): raise core.InvalidPacketError( @@ -2176,7 +2174,7 @@ class Protocol(utils.EventEmitter): # NOTE: with a small number of supported responses, a manual switch like this # is Ok, but if/when more responses are supported, a lookup mechanism would be # more appropriate. - response: Optional[Response] = None + response: Response | None = None if response_code == avc.ResponseFrame.ResponseCode.REJECTED: response = RejectedResponse(pdu_id=pdu_id, status_code=StatusCode(pdu[0])) elif response_code == avc.ResponseFrame.ResponseCode.NOT_IMPLEMENTED: diff --git a/bumble/colors.py b/bumble/colors.py index e9933ec1..f651f314 100644 --- a/bumble/colors.py +++ b/bumble/colors.py @@ -13,7 +13,6 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from functools import partial -from typing import Optional, Union class ColorError(ValueError): @@ -38,7 +37,7 @@ STYLES = ( ) -ColorSpec = Union[str, int] +ColorSpec = str | int def _join(*values: ColorSpec) -> str: @@ -56,14 +55,14 @@ def _color_code(spec: ColorSpec, base: int) -> str: elif isinstance(spec, int) and 0 <= spec <= 255: return _join(base + 8, 5, spec) else: - raise ColorError('Invalid color spec "%s"' % spec) + raise ColorError(f'Invalid color spec "{spec}"') def color( s: str, - fg: Optional[ColorSpec] = None, - bg: Optional[ColorSpec] = None, - style: Optional[str] = None, + fg: ColorSpec | None = None, + bg: ColorSpec | None = None, + style: str | None = None, ) -> str: codes: list[ColorSpec] = [] @@ -76,10 +75,10 @@ def color( if style_part in STYLES: codes.append(STYLES.index(style_part)) else: - raise ColorError('Invalid style "%s"' % style_part) + raise ColorError(f'Invalid style "{style_part}"') if codes: - return '\x1b[{0}m{1}\x1b[0m'.format(_join(*codes), s) + return f'\x1b[{_join(*codes)}m{s}\x1b[0m' else: return s diff --git a/bumble/controller.py b/bumble/controller.py index 79b67d4a..c3b84464 100644 --- a/bumble/controller.py +++ b/bumble/controller.py @@ -23,7 +23,7 @@ import itertools import logging import random import struct -from typing import TYPE_CHECKING, Any, Optional, Union, cast +from typing import TYPE_CHECKING, Any, cast from bumble import hci, link, ll, lmp from bumble import link as bumble_link @@ -52,7 +52,7 @@ class CisLink: handle: int cis_id: int cig_id: int - acl_connection: Optional[Connection] = None + acl_connection: Connection | None = None data_paths: set[int] = dataclasses.field(default_factory=set) @@ -73,7 +73,7 @@ class LegacyAdvertiser: scan_response_data: bytes = b'' enabled: bool = False - timer_handle: Optional[asyncio.Handle] = None + timer_handle: asyncio.Handle | None = None @property def address(self) -> hci.Address: @@ -124,12 +124,12 @@ class LegacyAdvertiser: class AdvertisingSet: controller: Controller handle: int - parameters: Optional[hci.HCI_LE_Set_Extended_Advertising_Parameters_Command] = None + parameters: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command | None = None data: bytearray = dataclasses.field(default_factory=bytearray) scan_response_data: bytearray = dataclasses.field(default_factory=bytearray) enabled: bool = False - timer_handle: Optional[asyncio.Handle] = None - random_address: Optional[hci.Address] = None + timer_handle: asyncio.Handle | None = None + random_address: hci.Address | None = None @property def address(self) -> hci.Address | None: @@ -223,7 +223,7 @@ class Connection: # ----------------------------------------------------------------------------- class Controller: - hci_sink: Optional[TransportSink] = None + hci_sink: TransportSink | None = None le_connections: dict[hci.Address, Connection] # LE Connections classic_connections: dict[hci.Address, Connection] # Connections in BR/EDR @@ -292,8 +292,8 @@ class Controller: sync_flow_control: bool = False local_name: str = 'Bumble' advertising_interval: int = 2000 - advertising_data: Optional[bytes] = None - advertising_timer_handle: Optional[asyncio.Handle] = None + advertising_data: bytes | None = None + advertising_timer_handle: asyncio.Handle | None = None classic_scan_enable: int = 0 classic_allow_role_switch: bool = True pending_le_connection: ( @@ -308,9 +308,9 @@ class Controller: self, name: str, host_source=None, - host_sink: Optional[TransportSink] = None, - link: Optional[link.LocalLink] = None, - public_address: Optional[Union[bytes, str, hci.Address]] = None, + host_sink: TransportSink | None = None, + link: link.LocalLink | None = None, + public_address: bytes | str | hci.Address | None = None, ) -> None: self.name = name self.link = link or bumble_link.LocalLink() @@ -351,18 +351,18 @@ class Controller: ) @property - def host(self) -> Optional[TransportSink]: + def host(self) -> TransportSink | None: return self.hci_sink @host.setter - def host(self, host: Optional[TransportSink]) -> None: + def host(self, host: TransportSink | None) -> None: ''' Sets the host (sink) for this controller, and set this controller as the controller (sink) for the host ''' self.set_packet_sink(host) - def set_packet_sink(self, sink: Optional[TransportSink]) -> None: + def set_packet_sink(self, sink: TransportSink | None) -> None: ''' Method from the Packet Source interface ''' @@ -373,7 +373,7 @@ class Controller: return self._public_address @public_address.setter - def public_address(self, address: Union[hci.Address, str]) -> None: + def public_address(self, address: hci.Address | str) -> None: if isinstance(address, str): address = hci.Address(address) self._public_address = address @@ -383,7 +383,7 @@ class Controller: return self._random_address @random_address.setter - def random_address(self, address: Union[hci.Address, str]) -> None: + def random_address(self, address: hci.Address | str) -> None: if isinstance(address, str): address = hci.Address(address) self._random_address = address @@ -415,7 +415,7 @@ class Controller: def on_hci_command_packet(self, command: hci.HCI_Command) -> None: handler_name = f'on_{command.name.lower()}' handler = getattr(self, handler_name, self.on_hci_command) - result: Optional[bytes] = handler(command) + result: bytes | None = handler(command) if isinstance(result, bytes): self.send_hci_packet( hci.HCI_Command_Complete_Event( @@ -472,7 +472,7 @@ class Controller: if handle not in current_handles ) - def find_connection_by_handle(self, handle: int) -> Optional[Connection]: + def find_connection_by_handle(self, handle: int) -> Connection | None: for connection in itertools.chain( self.le_connections.values(), self.classic_connections.values(), @@ -481,13 +481,13 @@ class Controller: return connection return None - def find_classic_sco_link_by_handle(self, handle: int) -> Optional[ScoLink]: + def find_classic_sco_link_by_handle(self, handle: int) -> ScoLink | None: for connection in self.sco_links.values(): if connection.handle == handle: return connection return None - def find_iso_link_by_handle(self, handle: int) -> Optional[CisLink]: + def find_iso_link_by_handle(self, handle: int) -> CisLink | None: return self.central_cis_links.get(handle) or self.peripheral_cis_links.get( handle ) @@ -1130,13 +1130,13 @@ class Controller: ############################################################ # HCI handlers ############################################################ - def on_hci_command(self, command: hci.HCI_Command) -> Optional[bytes]: + def on_hci_command(self, command: hci.HCI_Command) -> bytes | None: logger.warning(color(f'--- Unsupported command {command}', 'red')) return bytes([hci.HCI_UNKNOWN_HCI_COMMAND_ERROR]) def on_hci_create_connection_command( self, command: hci.HCI_Create_Connection_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.5 Create Connection command ''' @@ -1186,7 +1186,7 @@ class Controller: def on_hci_disconnect_command( self, command: hci.HCI_Disconnect_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.6 Disconnect Command ''' @@ -1256,7 +1256,7 @@ class Controller: def on_hci_accept_connection_request_command( self, command: hci.HCI_Accept_Connection_Request_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.8 Accept Connection Request command ''' @@ -1314,7 +1314,7 @@ class Controller: def on_hci_remote_name_request_command( self, command: hci.HCI_Remote_Name_Request_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.19 Remote Name Request command ''' @@ -1332,7 +1332,7 @@ class Controller: def on_hci_enhanced_setup_synchronous_connection_command( self, command: hci.HCI_Enhanced_Setup_Synchronous_Connection_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.45 Enhanced Setup Synchronous Connection command ''' @@ -1389,7 +1389,7 @@ class Controller: def on_hci_enhanced_accept_synchronous_connection_request_command( self, command: hci.HCI_Enhanced_Accept_Synchronous_Connection_Request_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.1.46 Enhanced Accept Synchronous Connection Request command ''' @@ -1427,7 +1427,7 @@ class Controller: def on_hci_sniff_mode_command( self, command: hci.HCI_Sniff_Mode_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.2.2 Sniff Mode command ''' @@ -1460,7 +1460,7 @@ class Controller: def on_hci_exit_sniff_mode_command( self, command: hci.HCI_Exit_Sniff_Mode_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.2.3 Exit Sniff Mode command ''' @@ -1494,7 +1494,7 @@ class Controller: def on_hci_switch_role_command( self, command: hci.HCI_Switch_Role_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.2.8 Switch hci.Role command ''' @@ -1551,7 +1551,7 @@ class Controller: def on_hci_set_event_mask_command( self, command: hci.HCI_Set_Event_Mask_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.1 Set Event Mask Command ''' @@ -1560,7 +1560,7 @@ class Controller: ) return bytes([hci.HCI_SUCCESS]) - def on_hci_reset_command(self, _command: hci.HCI_Reset_Command) -> Optional[bytes]: + def on_hci_reset_command(self, _command: hci.HCI_Reset_Command) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.2 Reset Command ''' @@ -1569,7 +1569,7 @@ class Controller: def on_hci_write_local_name_command( self, command: hci.HCI_Write_Local_Name_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.11 Write Local Name Command ''' @@ -1586,7 +1586,7 @@ class Controller: def on_hci_read_local_name_command( self, _command: hci.HCI_Read_Local_Name_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.12 Read Local Name Command ''' @@ -1598,7 +1598,7 @@ class Controller: def on_hci_read_class_of_device_command( self, _command: hci.HCI_Read_Class_Of_Device_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.25 Read Class of Device Command ''' @@ -1606,7 +1606,7 @@ class Controller: def on_hci_write_class_of_device_command( self, _command: hci.HCI_Write_Class_Of_Device_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.26 Write Class of Device Command ''' @@ -1614,7 +1614,7 @@ class Controller: def on_hci_read_synchronous_flow_control_enable_command( self, _command: hci.HCI_Read_Synchronous_Flow_Control_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.36 Read Synchronous Flow Control Enable Command @@ -1627,7 +1627,7 @@ class Controller: def on_hci_write_synchronous_flow_control_enable_command( self, command: hci.HCI_Write_Synchronous_Flow_Control_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.37 Write Synchronous Flow Control Enable Command @@ -1643,7 +1643,7 @@ class Controller: def on_hci_set_controller_to_host_flow_control_command( self, _command: hci.HCI_Set_Controller_To_Host_Flow_Control_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.38 Set Controller To Host Flow Control Command @@ -1654,7 +1654,7 @@ class Controller: def on_hci_host_buffer_size_command( self, _command: hci.HCI_Host_Buffer_Size_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.39 Host Buffer Size Command ''' @@ -1664,7 +1664,7 @@ class Controller: def on_hci_write_extended_inquiry_response_command( self, _command: hci.HCI_Write_Extended_Inquiry_Response_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.56 Write Extended Inquiry Response Command @@ -1673,7 +1673,7 @@ class Controller: def on_hci_write_simple_pairing_mode_command( self, _command: hci.HCI_Write_Simple_Pairing_Mode_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.59 Write Simple Pairing Mode Command ''' @@ -1681,7 +1681,7 @@ class Controller: def on_hci_set_event_mask_page_2_command( self, command: hci.HCI_Set_Event_Mask_Page_2_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.69 Set Event Mask Page 2 Command ''' @@ -1692,7 +1692,7 @@ class Controller: def on_hci_read_le_host_support_command( self, _command: hci.HCI_Read_LE_Host_Support_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.78 Write LE Host Support Command ''' @@ -1700,7 +1700,7 @@ class Controller: def on_hci_write_le_host_support_command( self, _command: hci.HCI_Write_LE_Host_Support_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.79 Write LE Host Support Command ''' @@ -1709,7 +1709,7 @@ class Controller: def on_hci_write_authenticated_payload_timeout_command( self, command: hci.HCI_Write_Authenticated_Payload_Timeout_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.94 Write Authenticated Payload Timeout Command @@ -1719,7 +1719,7 @@ class Controller: def on_hci_read_local_version_information_command( self, _command: hci.HCI_Read_Local_Version_Information_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.1 Read Local Version Information Command ''' @@ -1735,7 +1735,7 @@ class Controller: def on_hci_read_local_supported_commands_command( self, _command: hci.HCI_Read_Local_Supported_Commands_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.2 Read Local Supported Commands Command ''' @@ -1743,7 +1743,7 @@ class Controller: def on_hci_read_local_supported_features_command( self, _command: hci.HCI_Read_Local_Supported_Features_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.3 Read Local Supported Features Command ''' @@ -1751,7 +1751,7 @@ class Controller: def on_hci_read_local_extended_features_command( self, command: hci.HCI_Read_Local_Extended_Features_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.4 Read Local Extended Features Command ''' @@ -1774,7 +1774,7 @@ class Controller: def on_hci_read_buffer_size_command( self, _command: hci.HCI_Read_Buffer_Size_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.5 Read Buffer Size Command ''' @@ -1789,7 +1789,7 @@ class Controller: def on_hci_read_bd_addr_command( self, _command: hci.HCI_Read_BD_ADDR_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.4.6 Read BD_ADDR Command ''' @@ -1802,7 +1802,7 @@ class Controller: def on_hci_le_set_default_subrate_command( self, command: hci.HCI_LE_Set_Default_Subrate_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 6, Part E - 7.8.123 LE Set Event Mask Command ''' @@ -1818,7 +1818,7 @@ class Controller: def on_hci_le_subrate_request_command( self, command: hci.HCI_LE_Subrate_Request_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 6, Part E - 7.8.124 LE Subrate Request command ''' @@ -1852,7 +1852,7 @@ class Controller: def on_hci_le_set_event_mask_command( self, command: hci.HCI_LE_Set_Event_Mask_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.1 LE Set Event Mask Command ''' @@ -1863,7 +1863,7 @@ class Controller: def on_hci_le_read_buffer_size_command( self, _command: hci.HCI_LE_Read_Buffer_Size_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command ''' @@ -1876,7 +1876,7 @@ class Controller: def on_hci_le_read_buffer_size_v2_command( self, _command: hci.HCI_LE_Read_Buffer_Size_V2_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command ''' @@ -1891,7 +1891,7 @@ class Controller: def on_hci_le_read_local_supported_features_command( self, _command: hci.HCI_LE_Read_Local_Supported_Features_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.3 LE Read Local Supported Features Command @@ -1900,7 +1900,7 @@ class Controller: def on_hci_le_set_random_address_command( self, command: hci.HCI_LE_Set_Random_Address_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.4 LE Set Random hci.Address Command ''' @@ -1909,7 +1909,7 @@ class Controller: def on_hci_le_set_advertising_parameters_command( self, command: hci.HCI_LE_Set_Advertising_Parameters_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.5 LE Set Advertising Parameters Command ''' @@ -1933,7 +1933,7 @@ class Controller: def on_hci_le_read_advertising_physical_channel_tx_power_command( self, _command: hci.HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.6 LE Read Advertising Physical Channel Tx Power Command @@ -1942,7 +1942,7 @@ class Controller: def on_hci_le_set_advertising_data_command( self, command: hci.HCI_LE_Set_Advertising_Data_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.7 LE Set Advertising Data Command ''' @@ -1952,7 +1952,7 @@ class Controller: def on_hci_le_set_scan_response_data_command( self, command: hci.HCI_LE_Set_Scan_Response_Data_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.8 LE Set Scan Response Data Command ''' @@ -1961,7 +1961,7 @@ class Controller: def on_hci_le_set_advertising_enable_command( self, command: hci.HCI_LE_Set_Advertising_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.9 LE Set Advertising Enable Command ''' @@ -1974,7 +1974,7 @@ class Controller: def on_hci_le_set_scan_parameters_command( self, command: hci.HCI_LE_Set_Scan_Parameters_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.10 LE Set Scan Parameters Command ''' @@ -1990,7 +1990,7 @@ class Controller: def on_hci_le_set_scan_enable_command( self, command: hci.HCI_LE_Set_Scan_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.11 LE Set Scan Enable Command ''' @@ -2000,7 +2000,7 @@ class Controller: def on_hci_le_create_connection_command( self, command: hci.HCI_LE_Create_Connection_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.12 LE Create Connection Command ''' @@ -2035,7 +2035,7 @@ class Controller: def on_hci_le_create_connection_cancel_command( self, _command: hci.HCI_LE_Create_Connection_Cancel_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.13 LE Create Connection Cancel Command ''' @@ -2043,7 +2043,7 @@ class Controller: def on_hci_le_extended_create_connection_command( self, command: hci.HCI_LE_Extended_Create_Connection_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.66 LE Extended Create Connection Command ''' @@ -2074,7 +2074,7 @@ class Controller: def on_hci_le_read_filter_accept_list_size_command( self, _command: hci.HCI_LE_Read_Filter_Accept_List_Size_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.14 LE Read Filter Accept List Size Command @@ -2083,7 +2083,7 @@ class Controller: def on_hci_le_clear_filter_accept_list_command( self, _command: hci.HCI_LE_Clear_Filter_Accept_List_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.15 LE Clear Filter Accept List Command ''' @@ -2091,7 +2091,7 @@ class Controller: def on_hci_le_add_device_to_filter_accept_list_command( self, _command: hci.HCI_LE_Add_Device_To_Filter_Accept_List_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.16 LE Add Device To Filter Accept List Command @@ -2100,7 +2100,7 @@ class Controller: def on_hci_le_remove_device_from_filter_accept_list_command( self, _command: hci.HCI_LE_Remove_Device_From_Filter_Accept_List_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.17 LE Remove Device From Filter Accept List Command @@ -2109,7 +2109,7 @@ class Controller: def on_hci_write_scan_enable_command( self, command: hci.HCI_Write_Scan_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.3.18 Write Scan Enable Command ''' @@ -2118,7 +2118,7 @@ class Controller: def on_hci_le_read_remote_features_command( self, command: hci.HCI_LE_Read_Remote_Features_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.21 LE Read Remote Features Command ''' @@ -2154,9 +2154,7 @@ class Controller: ) return None - def on_hci_le_rand_command( - self, _command: hci.HCI_LE_Rand_Command - ) -> Optional[bytes]: + def on_hci_le_rand_command(self, _command: hci.HCI_LE_Rand_Command) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.23 LE Rand Command ''' @@ -2164,7 +2162,7 @@ class Controller: def on_hci_le_enable_encryption_command( self, command: hci.HCI_LE_Enable_Encryption_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.24 LE Enable Encryption Command ''' @@ -2204,7 +2202,7 @@ class Controller: def on_hci_le_read_supported_states_command( self, _command: hci.HCI_LE_Read_Supported_States_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.27 LE Read Supported States Command ''' @@ -2212,7 +2210,7 @@ class Controller: def on_hci_le_read_suggested_default_data_length_command( self, _command: hci.HCI_LE_Read_Suggested_Default_Data_Length_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.34 LE Read Suggested Default Data Length Command @@ -2226,7 +2224,7 @@ class Controller: def on_hci_le_write_suggested_default_data_length_command( self, command: hci.HCI_LE_Write_Suggested_Default_Data_Length_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.35 LE Write Suggested Default Data Length Command @@ -2238,7 +2236,7 @@ class Controller: def on_hci_le_read_local_p_256_public_key_command( self, _command: hci.HCI_LE_Read_Local_P_256_Public_Key_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.36 LE Read P-256 Public Key Command ''' @@ -2247,7 +2245,7 @@ class Controller: def on_hci_le_add_device_to_resolving_list_command( self, _command: hci.HCI_LE_Add_Device_To_Resolving_List_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.38 LE Add Device To Resolving List Command @@ -2256,7 +2254,7 @@ class Controller: def on_hci_le_clear_resolving_list_command( self, _command: hci.HCI_LE_Clear_Resolving_List_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.40 LE Clear Resolving List Command ''' @@ -2264,7 +2262,7 @@ class Controller: def on_hci_le_read_resolving_list_size_command( self, _command: hci.HCI_LE_Read_Resolving_List_Size_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.41 LE Read Resolving List Size Command ''' @@ -2272,7 +2270,7 @@ class Controller: def on_hci_le_set_address_resolution_enable_command( self, command: hci.HCI_LE_Set_Address_Resolution_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.44 LE Set hci.Address Resolution Enable Command @@ -2288,7 +2286,7 @@ class Controller: def on_hci_le_set_resolvable_private_address_timeout_command( self, command: hci.HCI_LE_Set_Resolvable_Private_Address_Timeout_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.45 LE Set Resolvable Private hci.Address Timeout Command @@ -2298,7 +2296,7 @@ class Controller: def on_hci_le_read_maximum_data_length_command( self, _command: hci.HCI_LE_Read_Maximum_Data_Length_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.46 LE Read Maximum Data Length Command ''' @@ -2313,7 +2311,7 @@ class Controller: def on_hci_le_read_phy_command( self, command: hci.HCI_LE_Read_PHY_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.47 LE Read PHY Command ''' @@ -2327,7 +2325,7 @@ class Controller: def on_hci_le_set_default_phy_command( self, command: hci.HCI_LE_Set_Default_PHY_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.48 LE Set Default PHY Command ''' @@ -2338,7 +2336,7 @@ class Controller: def on_hci_le_set_advertising_set_random_address_command( self, command: hci.HCI_LE_Set_Advertising_Set_Random_Address_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.52 LE Set Advertising Set Random hci.Address Command @@ -2353,7 +2351,7 @@ class Controller: def on_hci_le_set_extended_advertising_parameters_command( self, command: hci.HCI_LE_Set_Extended_Advertising_Parameters_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.53 LE Set Extended Advertising Parameters Command @@ -2369,7 +2367,7 @@ class Controller: def on_hci_le_set_extended_advertising_data_command( self, command: hci.HCI_LE_Set_Extended_Advertising_Data_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.54 LE Set Extended Advertising Data Command @@ -2393,7 +2391,7 @@ class Controller: def on_hci_le_set_extended_scan_response_data_command( self, command: hci.HCI_LE_Set_Extended_Scan_Response_Data_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.55 LE Set Extended Scan Response Data Command @@ -2417,7 +2415,7 @@ class Controller: def on_hci_le_set_extended_advertising_enable_command( self, command: hci.HCI_LE_Set_Extended_Advertising_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.56 LE Set Extended Advertising Enable Command @@ -2438,7 +2436,7 @@ class Controller: def on_hci_le_remove_advertising_set_command( self, command: hci.HCI_LE_Remove_Advertising_Set_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.59 LE Remove Advertising Set Command ''' @@ -2449,7 +2447,7 @@ class Controller: def on_hci_le_clear_advertising_sets_command( self, _command: hci.HCI_LE_Clear_Advertising_Sets_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.60 LE Clear Advertising Sets Command ''' @@ -2460,7 +2458,7 @@ class Controller: def on_hci_le_read_maximum_advertising_data_length_command( self, _command: hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.57 LE Read Maximum Advertising Data Length Command @@ -2469,7 +2467,7 @@ class Controller: def on_hci_le_read_number_of_supported_advertising_sets_command( self, _command: hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.58 LE Read Number of Supported Advertising Set Command @@ -2478,7 +2476,7 @@ class Controller: def on_hci_le_set_periodic_advertising_parameters_command( self, _command: hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.61 LE Set Periodic Advertising Parameters Command @@ -2487,7 +2485,7 @@ class Controller: def on_hci_le_set_periodic_advertising_data_command( self, _command: hci.HCI_LE_Set_Periodic_Advertising_Data_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.62 LE Set Periodic Advertising Data Command @@ -2496,7 +2494,7 @@ class Controller: def on_hci_le_set_periodic_advertising_enable_command( self, _command: hci.HCI_LE_Set_Periodic_Advertising_Enable_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.63 LE Set Periodic Advertising Enable Command @@ -2505,7 +2503,7 @@ class Controller: def on_hci_le_read_transmit_power_command( self, _command: hci.HCI_LE_Read_Transmit_Power_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.74 LE Read Transmit Power Command ''' @@ -2513,7 +2511,7 @@ class Controller: def on_hci_le_set_cig_parameters_command( self, command: hci.HCI_LE_Set_CIG_Parameters_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.97 LE Set CIG Parameter Command ''' @@ -2539,7 +2537,7 @@ class Controller: def on_hci_le_create_cis_command( self, command: hci.HCI_LE_Create_CIS_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.99 LE Create CIS Command ''' @@ -2574,7 +2572,7 @@ class Controller: def on_hci_le_remove_cig_command( self, command: hci.HCI_LE_Remove_CIG_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.100 LE Remove CIG Command ''' @@ -2591,7 +2589,7 @@ class Controller: def on_hci_le_accept_cis_request_command( self, command: hci.HCI_LE_Accept_CIS_Request_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.101 LE Accept CIS Request Command ''' @@ -2620,7 +2618,7 @@ class Controller: def on_hci_le_setup_iso_data_path_command( self, command: hci.HCI_LE_Setup_ISO_Data_Path_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command ''' @@ -2641,7 +2639,7 @@ class Controller: def on_hci_le_remove_iso_data_path_command( self, command: hci.HCI_LE_Remove_ISO_Data_Path_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command ''' @@ -2667,7 +2665,7 @@ class Controller: def on_hci_le_set_host_feature_command( self, _command: hci.HCI_LE_Set_Host_Feature_Command - ) -> Optional[bytes]: + ) -> bytes | None: ''' See Bluetooth spec Vol 4, Part E - 7.8.115 LE Set Host Feature command ''' diff --git a/bumble/core.py b/bumble/core.py index 1d1b98a7..d47ec6a7 100644 --- a/bumble/core.py +++ b/bumble/core.py @@ -20,14 +20,11 @@ from __future__ import annotations import dataclasses import enum import struct +from collections.abc import Iterable from typing import ( Any, ClassVar, - Iterable, Literal, - Optional, - Type, - Union, cast, overload, ) @@ -102,7 +99,7 @@ class BaseError(BaseBumbleError): def __init__( self, - error_code: Optional[int], + error_code: int | None, error_namespace: str = '', error_name: str = '', details: str = '', @@ -215,11 +212,9 @@ class UUID: UUIDS: list[UUID] = [] # Registry of all instances created uuid_bytes: bytes - name: Optional[str] + name: str | None - def __init__( - self, uuid_str_or_int: Union[str, int], name: Optional[str] = None - ) -> None: + def __init__(self, uuid_str_or_int: str | int, name: str | None = None) -> None: if isinstance(uuid_str_or_int, int): self.uuid_bytes = struct.pack(' UUID: + def from_bytes(cls, uuid_bytes: bytes, name: str | None = None) -> UUID: if len(uuid_bytes) in (2, 4, 16): self = cls.__new__(cls) self.uuid_bytes = uuid_bytes @@ -263,11 +258,11 @@ class UUID: raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed') @classmethod - def from_16_bits(cls, uuid_16: int, name: Optional[str] = None) -> UUID: + def from_16_bits(cls, uuid_16: int, name: str | None = None) -> UUID: return cls.from_bytes(struct.pack(' UUID: + def from_32_bits(cls, uuid_32: int, name: str | None = None) -> UUID: return cls.from_bytes(struct.pack(' Self: @@ -1547,7 +1542,7 @@ class DataType: return f"{self.__class__.__name__}({self.value_string()})" @classmethod - def from_advertising_data(cls, advertising_data: AdvertisingData) -> Optional[Self]: + def from_advertising_data(cls, advertising_data: AdvertisingData) -> Self | None: if (data := advertising_data.get(cls.ad_type, raw=True)) is None: return None @@ -1575,16 +1570,16 @@ class DataType: # ----------------------------------------------------------------------------- # Advertising Data # ----------------------------------------------------------------------------- -AdvertisingDataObject = Union[ - list[UUID], - tuple[UUID, bytes], - bytes, - str, - int, - tuple[int, int], - tuple[int, bytes], - Appearance, -] +AdvertisingDataObject = ( + list[UUID] + | tuple[UUID, bytes] + | bytes + | str + | int + | tuple[int, int] + | tuple[int, bytes] + | Appearance +) class AdvertisingData: @@ -1721,7 +1716,7 @@ class AdvertisingData: def __init__( self, - ad_structures: Optional[Iterable[Union[tuple[int, bytes], DataType]]] = None, + ad_structures: Iterable[tuple[int, bytes] | DataType] | None = None, ) -> None: if ad_structures is None: ad_structures = [] @@ -2019,7 +2014,7 @@ class AdvertisingData: AdvertisingData.Type.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS, ], raw: Literal[False] = False, - ) -> Optional[list[UUID]]: ... + ) -> list[UUID] | None: ... @overload def get( @@ -2030,7 +2025,7 @@ class AdvertisingData: AdvertisingData.Type.SERVICE_DATA_128_BIT_UUID, ], raw: Literal[False] = False, - ) -> Optional[tuple[UUID, bytes]]: ... + ) -> tuple[UUID, bytes] | None: ... @overload def get( @@ -2042,7 +2037,7 @@ class AdvertisingData: AdvertisingData.Type.BROADCAST_NAME, ], raw: Literal[False] = False, - ) -> Optional[Optional[str]]: ... + ) -> str | None: ... @overload def get( @@ -2054,38 +2049,36 @@ class AdvertisingData: AdvertisingData.Type.CLASS_OF_DEVICE, ], raw: Literal[False] = False, - ) -> Optional[int]: ... + ) -> int | None: ... @overload def get( self, type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,], raw: Literal[False] = False, - ) -> Optional[tuple[int, int]]: ... + ) -> tuple[int, int] | None: ... @overload def get( self, type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,], raw: Literal[False] = False, - ) -> Optional[tuple[int, bytes]]: ... + ) -> tuple[int, bytes] | None: ... @overload def get( self, type_id: Literal[AdvertisingData.Type.APPEARANCE,], raw: Literal[False] = False, - ) -> Optional[Appearance]: ... + ) -> Appearance | None: ... @overload - def get(self, type_id: int, raw: Literal[True]) -> Optional[bytes]: ... + def get(self, type_id: int, raw: Literal[True]) -> bytes | None: ... @overload - def get( - self, type_id: int, raw: bool = False - ) -> Optional[AdvertisingDataObject]: ... + def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None: ... - def get(self, type_id: int, raw: bool = False) -> Optional[AdvertisingDataObject]: + def get(self, type_id: int, raw: bool = False) -> AdvertisingDataObject | None: ''' Get advertising data as a simple AdvertisingDataObject object. diff --git a/bumble/crypto/builtin.py b/bumble/crypto/builtin.py index 55c15805..03d391e8 100644 --- a/bumble/crypto/builtin.py +++ b/bumble/crypto/builtin.py @@ -29,7 +29,6 @@ import dataclasses import functools import secrets import struct -from typing import Optional from bumble import core @@ -309,7 +308,7 @@ class _CMAC: self.digest_size = mac_len self._key = key self._block_size = bs = 16 - self._mac_tag: Optional[bytes] = None + self._mac_tag: bytes | None = None self._update_after_digest = update_after_digest # Section 5.3 of NIST SP 800 38B and Appendix B @@ -348,7 +347,7 @@ class _CMAC: self._last_ct = zero_block # Last block that was encrypted with AES - self._last_pt: Optional[bytes] = None + self._last_pt: bytes | None = None # Counter for total message size self._data_size = 0 diff --git a/bumble/data_types.py b/bumble/data_types.py index 59c43794..8d4ec9ce 100644 --- a/bumble/data_types.py +++ b/bumble/data_types.py @@ -25,7 +25,8 @@ from __future__ import annotations import dataclasses import math import struct -from typing import Any, ClassVar, Sequence +from collections.abc import Sequence +from typing import Any, ClassVar from typing_extensions import Self diff --git a/bumble/decoder.py b/bumble/decoder.py index 83a23b16..86d7bf2b 100644 --- a/bumble/decoder.py +++ b/bumble/decoder.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union # ----------------------------------------------------------------------------- # Constants @@ -167,12 +166,12 @@ class G722Decoder: # The initial value in BLOCK 3H self._band[1].det = 8 - def decode_frame(self, encoded_data: Union[bytes, bytearray]) -> bytearray: + def decode_frame(self, encoded_data: bytes | bytearray) -> bytearray: result_array = bytearray(len(encoded_data) * 4) self.g722_decode(result_array, encoded_data) return result_array - def g722_decode(self, result_array, encoded_data: Union[bytes, bytearray]) -> int: + def g722_decode(self, result_array, encoded_data: bytes | bytearray) -> int: """Decode the data frame using g722 decoder.""" result_length = 0 diff --git a/bumble/device.py b/bumble/device.py index 73140451..9c4c59ab 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -25,19 +25,15 @@ import itertools import json import logging import secrets -from collections.abc import Iterable, Sequence +from collections.abc import Awaitable, Callable, Iterable, Sequence from contextlib import AsyncExitStack, asynccontextmanager, closing from dataclasses import dataclass, field from enum import Enum, IntEnum from typing import ( TYPE_CHECKING, Any, - Awaitable, - Callable, ClassVar, - Optional, TypeVar, - Union, cast, overload, ) @@ -188,7 +184,7 @@ class Advertisement: self.data = AdvertisingData.from_bytes(self.data_bytes) @classmethod - def from_advertising_report(cls, report) -> Optional[Advertisement]: + def from_advertising_report(cls, report) -> Advertisement | None: if isinstance(report, hci.HCI_LE_Advertising_Report_Event.Report): return LegacyAdvertisement.from_advertising_report(report) @@ -604,11 +600,11 @@ class AdvertisingSet(utils.EventEmitter): device: Device advertising_handle: int auto_restart: bool - random_address: Optional[hci.Address] + random_address: hci.Address | None advertising_parameters: AdvertisingParameters advertising_data: bytes scan_response_data: bytes - periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters] + periodic_advertising_parameters: PeriodicAdvertisingParameters | None periodic_advertising_data: bytes selected_tx_power: int = 0 enabled: bool = False @@ -855,7 +851,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter): TERMINATED = 6 _state: State - sync_handle: Optional[int] + sync_handle: int | None advertiser_address: hci.Address sid: int skip: int @@ -1282,7 +1278,7 @@ class Peer: return mtu async def discover_service( - self, uuid: Union[core.UUID, str] + self, uuid: core.UUID | str ) -> list[gatt_client.ServiceProxy]: return await self.gatt_client.discover_service(uuid) @@ -1298,8 +1294,8 @@ class Peer: async def discover_characteristics( self, - uuids: Iterable[Union[core.UUID, str]] = (), - service: Optional[gatt_client.ServiceProxy] = None, + uuids: Iterable[core.UUID | str] = (), + service: gatt_client.ServiceProxy | None = None, ) -> list[gatt_client.CharacteristicProxy[bytes]]: return await self.gatt_client.discover_characteristics( uuids=uuids, service=service @@ -1307,9 +1303,9 @@ class Peer: async def discover_descriptors( self, - characteristic: Optional[gatt_client.CharacteristicProxy] = None, - start_handle: Optional[int] = None, - end_handle: Optional[int] = None, + characteristic: gatt_client.CharacteristicProxy | None = None, + start_handle: int | None = None, + end_handle: int | None = None, ): return await self.gatt_client.discover_descriptors( characteristic, start_handle, end_handle @@ -1330,7 +1326,7 @@ class Peer: async def subscribe( self, characteristic: gatt_client.CharacteristicProxy, - subscriber: Optional[Callable[[bytes], Any]] = None, + subscriber: Callable[[bytes], Any] | None = None, prefer_notify: bool = True, ) -> None: return await self.gatt_client.subscribe( @@ -1340,25 +1336,23 @@ class Peer: async def unsubscribe( self, characteristic: gatt_client.CharacteristicProxy, - subscriber: Optional[Callable[[bytes], Any]] = None, + subscriber: Callable[[bytes], Any] | None = None, ) -> None: return await self.gatt_client.unsubscribe(characteristic, subscriber) - async def read_value( - self, attribute: Union[int, gatt_client.AttributeProxy] - ) -> bytes: + async def read_value(self, attribute: int | gatt_client.AttributeProxy) -> bytes: return await self.gatt_client.read_value(attribute) async def write_value( self, - attribute: Union[int, gatt_client.AttributeProxy], + attribute: int | gatt_client.AttributeProxy, value: bytes, with_response: bool = False, ) -> None: return await self.gatt_client.write_value(attribute, value, with_response) async def read_characteristics_by_uuid( - self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None + self, uuid: core.UUID, service: gatt_client.ServiceProxy | None = None ) -> list[bytes]: return await self.gatt_client.read_characteristics_by_uuid(uuid, service) @@ -1368,7 +1362,7 @@ class Peer: def get_characteristics_by_uuid( self, uuid: core.UUID, - service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None, + service: gatt_client.ServiceProxy | core.UUID | None = None, ) -> list[gatt_client.CharacteristicProxy[bytes]]: if isinstance(service, core.UUID): return list( @@ -1384,7 +1378,7 @@ class Peer: def create_service_proxy( self, proxy_class: type[_PROXY_CLASS] - ) -> Optional[_PROXY_CLASS]: + ) -> _PROXY_CLASS | None: if proxy := proxy_class.from_client(self.gatt_client): return cast(_PROXY_CLASS, proxy) @@ -1392,7 +1386,7 @@ class Peer: async def discover_service_and_create_proxy( self, proxy_class: type[_PROXY_CLASS] - ) -> Optional[_PROXY_CLASS]: + ) -> _PROXY_CLASS | None: # Discover the first matching service and its characteristics services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID) if services: @@ -1401,7 +1395,7 @@ class Peer: return self.create_service_proxy(proxy_class) return None - async def sustain(self, timeout: Optional[float] = None) -> None: + async def sustain(self, timeout: float | None = None) -> None: await self.connection.sustain(timeout) # [Classic only] @@ -1444,7 +1438,7 @@ class ScoLink(utils.CompositeEventEmitter): acl_connection: Connection handle: int link_type: int - sink: Optional[Callable[[hci.HCI_SynchronousDataPacket], Any]] = None + sink: Callable[[hci.HCI_SynchronousDataPacket], Any] | None = None EVENT_DISCONNECTION: ClassVar[str] = "disconnection" EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure" @@ -1627,8 +1621,8 @@ class CisLink(utils.EventEmitter, _IsoLink): cis_sync_delay: int = 0 # CIS sync delay, in microseconds transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds - phy_c_to_p: Optional[hci.Phy] = None - phy_p_to_c: Optional[hci.Phy] = None + phy_c_to_p: hci.Phy | None = None + phy_p_to_c: hci.Phy | None = None nse: int = 0 bn_c_to_p: int = 0 bn_p_to_c: int = 0 @@ -1716,11 +1710,11 @@ class Connection(utils.CompositeEventEmitter): handle: int transport: core.PhysicalTransport self_address: hci.Address - self_resolvable_address: Optional[hci.Address] + self_resolvable_address: hci.Address | None peer_address: hci.Address - peer_name: Optional[str] - peer_resolvable_address: Optional[hci.Address] - peer_le_features: Optional[hci.LeFeatureMask] + peer_name: str | None + peer_resolvable_address: hci.Address | None + peer_le_features: hci.LeFeatureMask | None role: hci.Role parameters: Parameters encryption: int @@ -1728,8 +1722,8 @@ class Connection(utils.CompositeEventEmitter): authenticated: bool sc: bool gatt_client: gatt_client.Client - pairing_peer_io_capability: Optional[int] - pairing_peer_authentication_requirements: Optional[int] + pairing_peer_io_capability: int | None + pairing_peer_authentication_requirements: int | None cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE @@ -1831,9 +1825,9 @@ class Connection(utils.CompositeEventEmitter): handle: int, transport: core.PhysicalTransport, self_address: hci.Address, - self_resolvable_address: Optional[hci.Address], + self_resolvable_address: hci.Address | None, peer_address: hci.Address, - peer_resolvable_address: Optional[hci.Address], + peer_resolvable_address: hci.Address | None, role: hci.Role, parameters: Parameters, ): @@ -1896,8 +1890,8 @@ class Connection(utils.CompositeEventEmitter): ) -> l2cap.LeCreditBasedChannel: ... async def create_l2cap_channel( - self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec] - ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]: + self, spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec + ) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel: return await self.device.create_l2cap_channel(connection=self, spec=spec) async def disconnect( @@ -1921,7 +1915,7 @@ class Connection(utils.CompositeEventEmitter): async def switch_role(self, role: hci.Role) -> None: return await self.device.switch_role(self, role) - async def sustain(self, timeout: Optional[float] = None) -> None: + async def sustain(self, timeout: float | None = None) -> None: """Idles the current task waiting for a disconnect or timeout""" abort = asyncio.get_running_loop().create_future() @@ -1965,8 +1959,8 @@ class Connection(utils.CompositeEventEmitter): async def set_phy( self, - tx_phys: Optional[Iterable[hci.Phy]] = None, - rx_phys: Optional[Iterable[hci.Phy]] = None, + tx_phys: Iterable[hci.Phy] | None = None, + rx_phys: Iterable[hci.Phy] | None = None, phy_options: int = 0, ): return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options) @@ -2070,12 +2064,12 @@ class DeviceConfiguration: AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)]) ) irk: bytes = bytes(16) # This really must be changed for any level of security - keystore: Optional[str] = None + keystore: str | None = None address_resolution_offload: bool = False address_generation_offload: bool = False cis_enabled: bool = False channel_sounding_enabled: bool = False - identity_address_type: Optional[int] = None + identity_address_type: int | None = None io_capability: int = pairing.PairingDelegate.IoCapability.NO_OUTPUT_NO_INPUT gap_service_enabled: bool = True gatt_service_enabled: bool = True @@ -2143,7 +2137,7 @@ class DeviceConfiguration: setattr(self, key, value) def load_from_file(self, filename: str) -> None: - with open(filename, 'r', encoding='utf-8') as file: + with open(filename, encoding='utf-8') as file: self.load_from_dict(json.load(file)) @classmethod @@ -2254,12 +2248,12 @@ class Device(utils.CompositeEventEmitter): pending_connections: dict[hci.Address, Connection] classic_pending_accepts: dict[ hci.Address, - list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]], + list[asyncio.Future[Connection | tuple[hci.Address, int, int]]], ] advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator] periodic_advertising_syncs: list[PeriodicAdvertisingSync] config: DeviceConfiguration - legacy_advertiser: Optional[LegacyAdvertiser] + legacy_advertiser: LegacyAdvertiser | None sco_links: dict[int, ScoLink] cis_links: dict[int, CisLink] bigs: dict[int, Big] @@ -2347,10 +2341,10 @@ class Device(utils.CompositeEventEmitter): def __init__( self, - name: Optional[str] = None, - address: Optional[hci.Address] = None, - config: Optional[DeviceConfiguration] = None, - host: Optional[Host] = None, + name: str | None = None, + address: hci.Address | None = None, + config: DeviceConfiguration | None = None, + host: Host | None = None, ) -> None: super().__init__() @@ -2407,7 +2401,7 @@ class Device(utils.CompositeEventEmitter): self.le_simultaneous_enabled = config.le_simultaneous_enabled self.le_privacy_enabled = config.le_privacy_enabled self.le_rpa_timeout = config.le_rpa_timeout - self.le_rpa_periodic_update_task: Optional[asyncio.Task] = None + self.le_rpa_periodic_update_task: asyncio.Task | None = None self.le_subrate_enabled = config.le_subrate_enabled self.classic_enabled = config.classic_enabled self.cis_enabled = config.cis_enabled @@ -2431,8 +2425,8 @@ class Device(utils.CompositeEventEmitter): # can be initialized from a config object, and for backward compatibility for # client code that may set those values directly before calling # start_advertising(). - self.legacy_advertising_set: Optional[AdvertisingSet] = None - self.legacy_advertiser: Optional[LegacyAdvertiser] = None + self.legacy_advertising_set: AdvertisingSet | None = None + self.legacy_advertiser: LegacyAdvertiser | None = None self.advertising_data = config.advertising_data self.scan_response_data = config.scan_response_data self.advertising_interval_min = config.advertising_interval_min @@ -2550,7 +2544,7 @@ class Device(utils.CompositeEventEmitter): def sdp_service_records(self, service_records): self.sdp_server.service_records = service_records - def lookup_connection(self, connection_handle: int) -> Optional[Connection]: + def lookup_connection(self, connection_handle: int) -> Connection | None: if connection := self.connections.get(connection_handle): return connection @@ -2559,9 +2553,9 @@ class Device(utils.CompositeEventEmitter): def find_connection_by_bd_addr( self, bd_addr: hci.Address, - transport: Optional[int] = None, + transport: int | None = None, check_address_type: bool = False, - ) -> Optional[Connection]: + ) -> Connection | None: for connection in self.connections.values(): if bytes(connection.peer_address) == bytes(bd_addr): if ( @@ -2576,7 +2570,7 @@ class Device(utils.CompositeEventEmitter): def lookup_periodic_advertising_sync( self, sync_handle: int - ) -> Optional[PeriodicAdvertisingSync]: + ) -> PeriodicAdvertisingSync | None: return next( ( sync @@ -2614,8 +2608,8 @@ class Device(utils.CompositeEventEmitter): async def create_l2cap_channel( self, connection: Connection, - spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec], - ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]: + spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec, + ) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel: if isinstance(spec, l2cap.ClassicChannelSpec): return await self.l2cap_channel_manager.create_classic_channel( connection=connection, spec=spec @@ -2629,25 +2623,25 @@ class Device(utils.CompositeEventEmitter): def create_l2cap_server( self, spec: l2cap.ClassicChannelSpec, - handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None, + handler: Callable[[l2cap.ClassicChannel], Any] | None = None, ) -> l2cap.ClassicChannelServer: ... @overload def create_l2cap_server( self, spec: l2cap.LeCreditBasedChannelSpec, - handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None, + handler: Callable[[l2cap.LeCreditBasedChannel], Any] | None = None, ) -> l2cap.LeCreditBasedChannelServer: ... def create_l2cap_server( self, - spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec], - handler: Union[ - Callable[[l2cap.ClassicChannel], Any], - Callable[[l2cap.LeCreditBasedChannel], Any], - None, - ] = None, - ) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]: + spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec, + handler: ( + Callable[[l2cap.ClassicChannel], Any] + | Callable[[l2cap.LeCreditBasedChannel], Any] + | None + ) = None, + ) -> l2cap.ClassicChannelServer | l2cap.LeCreditBasedChannelServer: if isinstance(spec, l2cap.ClassicChannelSpec): return self.l2cap_channel_manager.create_classic_server( spec=spec, @@ -2949,13 +2943,13 @@ class Device(utils.CompositeEventEmitter): async def start_advertising( self, advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE, - target: Optional[hci.Address] = None, + target: hci.Address | None = None, own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM, auto_restart: bool = False, - advertising_data: Optional[bytes] = None, - scan_response_data: Optional[bytes] = None, - advertising_interval_min: Optional[float] = None, - advertising_interval_max: Optional[float] = None, + advertising_data: bytes | None = None, + scan_response_data: bytes | None = None, + advertising_interval_min: float | None = None, + advertising_interval_max: float | None = None, ) -> None: """Start legacy advertising. @@ -3059,11 +3053,11 @@ class Device(utils.CompositeEventEmitter): async def create_advertising_set( self, - advertising_parameters: Optional[AdvertisingParameters] = None, - random_address: Optional[hci.Address] = None, + advertising_parameters: AdvertisingParameters | None = None, + random_address: hci.Address | None = None, advertising_data: bytes = b'', scan_response_data: bytes = b'', - periodic_advertising_parameters: Optional[PeriodicAdvertisingParameters] = None, + periodic_advertising_parameters: PeriodicAdvertisingParameters | None = None, periodic_advertising_data: bytes = b'', auto_start: bool = True, auto_restart: bool = False, @@ -3599,13 +3593,13 @@ class Device(utils.CompositeEventEmitter): async def connect( self, - peer_address: Union[hci.Address, str], + peer_address: hci.Address | str, transport: core.PhysicalTransport = PhysicalTransport.LE, - connection_parameters_preferences: Optional[ - dict[hci.Phy, ConnectionParametersPreferences] - ] = None, + connection_parameters_preferences: ( + dict[hci.Phy, ConnectionParametersPreferences] | None + ) = None, own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM, - timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT, + timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT, always_resolve: bool = False, ) -> Connection: ''' @@ -3915,9 +3909,9 @@ class Device(utils.CompositeEventEmitter): async def accept( self, - peer_address: Union[hci.Address, str] = hci.Address.ANY, + peer_address: hci.Address | str = hci.Address.ANY, role: hci.Role = hci.Role.PERIPHERAL, - timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT, + timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT, ) -> Connection: ''' Wait and accept any incoming connection or a connection from `peer_address` when @@ -4041,7 +4035,7 @@ class Device(utils.CompositeEventEmitter): self.pending_connections.pop(peer_address, None) @asynccontextmanager - async def connect_as_gatt(self, peer_address: Union[hci.Address, str]): + async def connect_as_gatt(self, peer_address: hci.Address | str): async with AsyncExitStack() as stack: connection = await stack.enter_async_context( await self.connect(peer_address) @@ -4088,7 +4082,7 @@ class Device(utils.CompositeEventEmitter): ) async def disconnect( - self, connection: Union[Connection, ScoLink, CisLink], reason: int + self, connection: Connection | ScoLink | CisLink, reason: int ) -> None: # Create a future so that we can wait for the disconnection's result pending_disconnection = asyncio.get_running_loop().create_future() @@ -4227,8 +4221,8 @@ class Device(utils.CompositeEventEmitter): async def set_connection_phy( self, connection: Connection, - tx_phys: Optional[Iterable[hci.Phy]] = None, - rx_phys: Optional[Iterable[hci.Phy]] = None, + tx_phys: Iterable[hci.Phy] | None = None, + rx_phys: Iterable[hci.Phy] | None = None, phy_options: int = 0, ): if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND): @@ -4252,8 +4246,8 @@ class Device(utils.CompositeEventEmitter): async def set_default_phy( self, - tx_phys: Optional[Iterable[hci.Phy]] = None, - rx_phys: Optional[Iterable[hci.Phy]] = None, + tx_phys: Iterable[hci.Phy] | None = None, + rx_phys: Iterable[hci.Phy] | None = None, ): all_phys_bits = (1 if tx_phys is None else 0) | ( (1 if rx_phys is None else 0) << 1 @@ -4307,7 +4301,7 @@ class Device(utils.CompositeEventEmitter): if local_name == name: peer_address.set_result(address) - listener: Optional[Callable[..., None]] = None + listener: Callable[..., None] | None = None was_scanning = self.scanning was_discovering = self.discovering try: @@ -4421,7 +4415,7 @@ class Device(utils.CompositeEventEmitter): async def get_long_term_key( self, connection_handle: int, rand: bytes, ediv: int - ) -> Optional[bytes]: + ) -> bytes | None: if (connection := self.lookup_connection(connection_handle)) is None: return None @@ -4445,7 +4439,7 @@ class Device(utils.CompositeEventEmitter): return keys.ltk_peripheral.value return None - async def get_link_key(self, address: hci.Address) -> Optional[bytes]: + async def get_link_key(self, address: hci.Address) -> bytes | None: if self.keystore is None: return None @@ -4583,7 +4577,7 @@ class Device(utils.CompositeEventEmitter): await connection.cancel_on_disconnection(pending_role_change) # [Classic only] - async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str: + async def request_remote_name(self, remote: hci.Address | Connection) -> str: # Set up event handlers pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future() @@ -5153,7 +5147,7 @@ class Device(utils.CompositeEventEmitter): self, connection: Connection, attribute: Attribute, - value: Optional[Any] = None, + value: Any | None = None, force: bool = False, ) -> None: """ @@ -5172,7 +5166,7 @@ class Device(utils.CompositeEventEmitter): await self.gatt_server.notify_subscriber(connection, attribute, value, force) async def notify_subscribers( - self, attribute: Attribute, value: Optional[Any] = None, force: bool = False + self, attribute: Attribute, value: Any | None = None, force: bool = False ) -> None: """ Send a notification to all the subscribers of an attribute. @@ -5192,7 +5186,7 @@ class Device(utils.CompositeEventEmitter): self, connection: Connection, attribute: Attribute, - value: Optional[Any] = None, + value: Any | None = None, force: bool = False, ): """ @@ -5213,7 +5207,7 @@ class Device(utils.CompositeEventEmitter): await self.gatt_server.indicate_subscriber(connection, attribute, value, force) async def indicate_subscribers( - self, attribute: Attribute, value: Optional[Any] = None, force: bool = False + self, attribute: Attribute, value: Any | None = None, force: bool = False ): """ Send an indication to all the subscribers of an attribute. @@ -5437,8 +5431,8 @@ class Device(utils.CompositeEventEmitter): self, connection_handle: int, peer_address: hci.Address, - self_resolvable_address: Optional[hci.Address], - peer_resolvable_address: Optional[hci.Address], + self_resolvable_address: hci.Address | None, + peer_resolvable_address: hci.Address | None, role: hci.Role, connection_interval: int, peripheral_latency: int, @@ -5473,7 +5467,7 @@ class Device(utils.CompositeEventEmitter): peer_address = resolved_address self_address = None - own_address_type: Optional[hci.OwnAddressType] = None + own_address_type: hci.OwnAddressType | None = None if role == hci.Role.CENTRAL: own_address_type = self.connect_own_address_type assert own_address_type is not None @@ -5938,7 +5932,7 @@ class Device(utils.CompositeEventEmitter): @host_event_handler @try_with_connection_from_address def on_remote_name( - self, connection: Optional[Connection], address: hci.Address, remote_name: bytes + self, connection: Connection | None, address: hci.Address, remote_name: bytes ): # Try to decode the name try: @@ -5957,7 +5951,7 @@ class Device(utils.CompositeEventEmitter): @host_event_handler @try_with_connection_from_address def on_remote_name_failure( - self, connection: Optional[Connection], address: hci.Address, error: int + self, connection: Connection | None, address: hci.Address, error: int ): if connection: connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error) @@ -6402,7 +6396,7 @@ class Device(utils.CompositeEventEmitter): @host_event_handler @try_with_connection_from_address def on_role_change_failure( - self, connection: Optional[Connection], address: hci.Address, error: int + self, connection: Connection | None, address: hci.Address, error: int ): if connection: connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error) @@ -6426,7 +6420,7 @@ class Device(utils.CompositeEventEmitter): def on_pairing( self, connection: Connection, - identity_address: Optional[hci.Address], + identity_address: hci.Address | None, keys: PairingKeys, sc: bool, ) -> None: diff --git a/bumble/drivers/__init__.py b/bumble/drivers/__init__.py index f755a273..377146b7 100644 --- a/bumble/drivers/__init__.py +++ b/bumble/drivers/__init__.py @@ -24,7 +24,8 @@ from __future__ import annotations import logging import pathlib import platform -from typing import TYPE_CHECKING, Iterable, Optional +from collections.abc import Iterable +from typing import TYPE_CHECKING from bumble.drivers import intel, rtk from bumble.drivers.common import Driver @@ -41,7 +42,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- # Functions # ----------------------------------------------------------------------------- -async def get_driver_for_host(host: Host) -> Optional[Driver]: +async def get_driver_for_host(host: Host) -> Driver | None: """Probe diver classes until one returns a valid instance for a host, or none is found. If a "driver" HCI metadata entry is present, only that driver class will be probed. diff --git a/bumble/drivers/intel.py b/bumble/drivers/intel.py index 90f52a4f..486951bd 100644 --- a/bumble/drivers/intel.py +++ b/bumble/drivers/intel.py @@ -29,7 +29,7 @@ import os import pathlib import platform import struct -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from bumble import core, hci, utils from bumble.drivers import common @@ -353,8 +353,8 @@ class Driver(common.Driver): self.reset_complete = asyncio.Event() # Parse configuration options from the driver name. - self.ddc_addon: Optional[bytes] = None - self.ddc_override: Optional[bytes] = None + self.ddc_addon: bytes | None = None + self.ddc_override: bytes | None = None driver = host.hci_metadata.get("driver") if driver is not None and driver.startswith("intel/"): for key, value in [ @@ -602,7 +602,7 @@ class Driver(common.Driver): await self.load_ddc_if_any(firmware_base_name) - async def load_ddc_if_any(self, firmware_base_name: Optional[str] = None) -> None: + async def load_ddc_if_any(self, firmware_base_name: str | None = None) -> None: """ Check for and load any Device Data Configuration (DDC) blobs. diff --git a/bumble/gatt.py b/bumble/gatt.py index 19e9e087..836d6c87 100644 --- a/bumble/gatt.py +++ b/bumble/gatt.py @@ -28,7 +28,8 @@ import enum import functools import logging import struct -from typing import Iterable, Optional, Sequence, TypeVar, Union +from collections.abc import Iterable, Sequence +from typing import TypeVar from bumble.att import Attribute, AttributeValue from bumble.colors import color @@ -355,7 +356,7 @@ class Service(Attribute): def __init__( self, - uuid: Union[str, UUID], + uuid: str | UUID, characteristics: Iterable[Characteristic], primary=True, included_services: Iterable[Service] = (), @@ -378,7 +379,7 @@ class Service(Attribute): self.characteristics = list(characteristics) self.primary = primary - def get_advertising_data(self) -> Optional[bytes]: + def get_advertising_data(self) -> bytes | None: """ Get Service specific advertising data Defined by each Service, default value is empty @@ -502,10 +503,10 @@ class Characteristic(Attribute[_T]): def __init__( self, - uuid: Union[str, bytes, UUID], + uuid: str | bytes | UUID, properties: Characteristic.Properties, - permissions: Union[str, Attribute.Permissions], - value: Union[AttributeValue[_T], _T, None] = None, + permissions: str | Attribute.Permissions, + value: AttributeValue[_T] | _T | None = None, descriptors: Sequence[Descriptor] = (), ): super().__init__(uuid, permissions, value) diff --git a/bumble/gatt_adapters.py b/bumble/gatt_adapters.py index cdaaef3f..1990db66 100644 --- a/bumble/gatt_adapters.py +++ b/bumble/gatt_adapters.py @@ -22,7 +22,8 @@ from __future__ import annotations import struct -from typing import Any, Callable, Generic, Iterable, Literal, Optional, TypeVar +from collections.abc import Callable, Iterable +from typing import Any, Generic, Literal, TypeVar from bumble import utils from bumble.core import InvalidOperationError @@ -74,8 +75,8 @@ class DelegatedCharacteristicAdapter(CharacteristicAdapter[_T]): def __init__( self, characteristic: Characteristic, - encode: Optional[Callable[[_T], bytes]] = None, - decode: Optional[Callable[[bytes], _T]] = None, + encode: Callable[[_T], bytes] | None = None, + decode: Callable[[bytes], _T] | None = None, ): super().__init__(characteristic) self.encode = encode @@ -101,8 +102,8 @@ class DelegatedCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T]): def __init__( self, characteristic_proxy: CharacteristicProxy, - encode: Optional[Callable[[_T], bytes]] = None, - decode: Optional[Callable[[bytes], _T]] = None, + encode: Callable[[_T], bytes] | None = None, + decode: Callable[[bytes], _T] | None = None, ): super().__init__(characteristic_proxy) self.encode = encode diff --git a/bumble/gatt_client.py b/bumble/gatt_client.py index 2be2528e..487961ce 100644 --- a/bumble/gatt_client.py +++ b/bumble/gatt_client.py @@ -28,16 +28,13 @@ from __future__ import annotations import asyncio import logging import struct +from collections.abc import Callable, Iterable from datetime import datetime from typing import ( TYPE_CHECKING, Any, - Callable, Generic, - Iterable, - Optional, TypeVar, - Union, ) from bumble import att, core, utils @@ -192,7 +189,7 @@ class CharacteristicProxy(AttributeProxy[_T]): self.descriptors_discovered = False self.subscribers = {} # Map from subscriber to proxy subscriber - def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]: + def get_descriptor(self, descriptor_type: UUID) -> DescriptorProxy | None: for descriptor in self.descriptors: if descriptor.type == descriptor_type: return descriptor @@ -204,7 +201,7 @@ class CharacteristicProxy(AttributeProxy[_T]): async def subscribe( self, - subscriber: Optional[Callable[[_T], Any]] = None, + subscriber: Callable[[_T], Any] | None = None, prefer_notify: bool = True, ) -> None: if subscriber is not None: @@ -253,7 +250,7 @@ class ProfileServiceProxy: SERVICE_CLASS: type[TemplateService] @classmethod - def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]: + def from_client(cls, client: Client) -> ProfileServiceProxy | None: return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID) @@ -264,13 +261,11 @@ class Client: services: list[ServiceProxy] cached_values: dict[int, tuple[datetime, bytes]] notification_subscribers: dict[ - int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]] + int, set[CharacteristicProxy | Callable[[bytes], Any]] ] - indication_subscribers: dict[ - int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]] - ] - pending_response: Optional[asyncio.futures.Future[att.ATT_PDU]] - pending_request: Optional[att.ATT_PDU] + indication_subscribers: dict[int, set[CharacteristicProxy | Callable[[bytes], Any]]] + pending_response: asyncio.futures.Future[att.ATT_PDU] | None + pending_request: att.ATT_PDU | None def __init__(self, connection: Connection) -> None: self.connection = connection @@ -360,7 +355,7 @@ class Client: return [service for service in self.services if service.uuid == uuid] def get_characteristics_by_uuid( - self, uuid: UUID, service: Optional[ServiceProxy] = None + self, uuid: UUID, service: ServiceProxy | None = None ) -> list[CharacteristicProxy[bytes]]: services = [service] if service else self.services return [ @@ -369,13 +364,14 @@ class Client: if c.uuid == uuid ] - def get_attribute_grouping(self, attribute_handle: int) -> Optional[ - Union[ - ServiceProxy, - tuple[ServiceProxy, CharacteristicProxy], - tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy], - ] - ]: + def get_attribute_grouping( + self, attribute_handle: int + ) -> ( + ServiceProxy + | tuple[ServiceProxy, CharacteristicProxy] + | tuple[ServiceProxy, CharacteristicProxy, DescriptorProxy] + | None + ): """ Get the attribute(s) associated with an attribute handle """ @@ -478,7 +474,7 @@ class Client: return services - async def discover_service(self, uuid: Union[str, UUID]) -> list[ServiceProxy]: + async def discover_service(self, uuid: str | UUID) -> list[ServiceProxy]: ''' See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID ''' @@ -612,7 +608,7 @@ class Client: return included_services async def discover_characteristics( - self, uuids, service: Optional[ServiceProxy] + self, uuids, service: ServiceProxy | None ) -> list[CharacteristicProxy[bytes]]: ''' See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2 @@ -699,9 +695,9 @@ class Client: async def discover_descriptors( self, - characteristic: Optional[CharacteristicProxy] = None, - start_handle: Optional[int] = None, - end_handle: Optional[int] = None, + characteristic: CharacteristicProxy | None = None, + start_handle: int | None = None, + end_handle: int | None = None, ) -> list[DescriptorProxy]: ''' See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors @@ -810,7 +806,7 @@ class Client: async def subscribe( self, characteristic: CharacteristicProxy, - subscriber: Optional[Callable[[Any], Any]] = None, + subscriber: Callable[[Any], Any] | None = None, prefer_notify: bool = True, ) -> None: # If we haven't already discovered the descriptors for this characteristic, @@ -860,7 +856,7 @@ class Client: async def unsubscribe( self, characteristic: CharacteristicProxy, - subscriber: Optional[Callable[[Any], Any]] = None, + subscriber: Callable[[Any], Any] | None = None, force: bool = False, ) -> None: ''' @@ -925,7 +921,7 @@ class Client: await self.write_value(cccd, b'\x00\x00', with_response=True) async def read_value( - self, attribute: Union[int, AttributeProxy], no_long_read: bool = False + self, attribute: int | AttributeProxy, no_long_read: bool = False ) -> bytes: ''' See Vol 3, Part G - 4.8.1 Read Characteristic Value @@ -980,7 +976,7 @@ class Client: return attribute_value async def read_characteristics_by_uuid( - self, uuid: UUID, service: Optional[ServiceProxy] + self, uuid: UUID, service: ServiceProxy | None ) -> list[bytes]: ''' See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID @@ -1038,7 +1034,7 @@ class Client: async def write_value( self, - attribute: Union[int, AttributeProxy], + attribute: int | AttributeProxy, value: bytes, with_response: bool = False, ) -> None: diff --git a/bumble/gatt_server.py b/bumble/gatt_server.py index 71e2a6ef..bfa20990 100644 --- a/bumble/gatt_server.py +++ b/bumble/gatt_server.py @@ -29,7 +29,8 @@ import asyncio import logging import struct from collections import defaultdict -from typing import TYPE_CHECKING, Iterable, Optional, TypeVar +from collections.abc import Iterable +from typing import TYPE_CHECKING, TypeVar from bumble import att, utils from bumble.colors import color @@ -73,7 +74,7 @@ class Server(utils.EventEmitter): attributes_by_handle: dict[int, att.Attribute] subscribers: dict[int, dict[int, bytes]] indication_semaphores: defaultdict[int, asyncio.Semaphore] - pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]] + pending_confirmations: defaultdict[int, asyncio.futures.Future | None] EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription" @@ -109,7 +110,7 @@ class Server(utils.EventEmitter): and (data := attribute.get_advertising_data()) } - def get_attribute(self, handle: int) -> Optional[att.Attribute]: + def get_attribute(self, handle: int) -> att.Attribute | None: attribute = self.attributes_by_handle.get(handle) if attribute: return attribute @@ -126,7 +127,7 @@ class Server(utils.EventEmitter): def get_attribute_group( self, handle: int, group_type: type[AttributeGroupType] - ) -> Optional[AttributeGroupType]: + ) -> AttributeGroupType | None: return next( ( attribute @@ -137,7 +138,7 @@ class Server(utils.EventEmitter): None, ) - def get_service_attribute(self, service_uuid: UUID) -> Optional[Service]: + def get_service_attribute(self, service_uuid: UUID) -> Service | None: return next( ( attribute @@ -151,7 +152,7 @@ class Server(utils.EventEmitter): def get_characteristic_attributes( self, service_uuid: UUID, characteristic_uuid: UUID - ) -> Optional[tuple[CharacteristicDeclaration, Characteristic]]: + ) -> tuple[CharacteristicDeclaration, Characteristic] | None: service_handle = self.get_service_attribute(service_uuid) if not service_handle: return None @@ -176,7 +177,7 @@ class Server(utils.EventEmitter): def get_descriptor_attribute( self, service_uuid: UUID, characteristic_uuid: UUID, descriptor_uuid: UUID - ) -> Optional[Descriptor]: + ) -> Descriptor | None: characteristics = self.get_characteristic_attributes( service_uuid, characteristic_uuid ) @@ -334,7 +335,7 @@ class Server(utils.EventEmitter): self, connection: Connection, attribute: att.Attribute, - value: Optional[bytes] = None, + value: bytes | None = None, force: bool = False, ) -> None: # Check if there's a subscriber @@ -377,7 +378,7 @@ class Server(utils.EventEmitter): self, connection: Connection, attribute: att.Attribute, - value: Optional[bytes] = None, + value: bytes | None = None, force: bool = False, ) -> None: # Check if there's a subscriber @@ -437,7 +438,7 @@ class Server(utils.EventEmitter): self, indicate: bool, attribute: att.Attribute, - value: Optional[bytes] = None, + value: bytes | None = None, force: bool = False, ) -> None: # Get all the connections for which there's at least one subscription @@ -464,7 +465,7 @@ class Server(utils.EventEmitter): async def notify_subscribers( self, attribute: att.Attribute, - value: Optional[bytes] = None, + value: bytes | None = None, force: bool = False, ): return await self._notify_or_indicate_subscribers( @@ -474,7 +475,7 @@ class Server(utils.EventEmitter): async def indicate_subscribers( self, attribute: att.Attribute, - value: Optional[bytes] = None, + value: bytes | None = None, force: bool = False, ): return await self._notify_or_indicate_subscribers(True, attribute, value, force) diff --git a/bumble/hci.py b/bumble/hci.py index 02c746d1..fab2c343 100644 --- a/bumble/hci.py +++ b/bumble/hci.py @@ -24,17 +24,13 @@ import functools import logging import secrets import struct -from collections.abc import Sequence +from collections.abc import Callable, Iterable, Sequence from dataclasses import field from typing import ( Any, - Callable, ClassVar, - Iterable, Literal, - Optional, TypeVar, - Union, cast, ) @@ -106,7 +102,7 @@ def map_class_of_device(class_of_device): ) -def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int: +def phy_list_to_bits(phys: Iterable[Phy] | None) -> int: if phys is None: return 0 @@ -184,8 +180,8 @@ class SpecableFlag(enum.IntFlag): # - "v" for variable length bytes with a leading length byte # - an integer [1, 4] for 1-byte, 2-byte or 4-byte unsigned little-endian integers # - an integer [-2, -1] for 1-byte, 2-byte signed little-endian integers -FieldSpec = Union[dict[str, Any], Callable[[bytes, int], tuple[int, Any]], str, int] -Fields = Sequence[Union[tuple[str, FieldSpec], 'Fields']] +FieldSpec = dict[str, Any] | Callable[[bytes, int], tuple[int, Any]] | str | int +Fields = Sequence['tuple[str, FieldSpec] | Fields'] @dataclasses.dataclass @@ -2156,7 +2152,7 @@ class Address: def __init__( self, - address: Union[bytes, str], + address: bytes | str, address_type: AddressType = RANDOM_DEVICE_ADDRESS, ) -> None: ''' @@ -2421,9 +2417,9 @@ class HCI_Command(HCI_Packet): def __init__( self, - parameters: Optional[bytes] = None, + parameters: bytes | None = None, *, - op_code: Optional[int] = None, + op_code: int | None = None, **kwargs, ) -> None: # op_code should be set in cls. @@ -5714,7 +5710,7 @@ class HCI_Event(HCI_Packet): hci_packet_type = HCI_EVENT_PACKET event_names: dict[int, str] = {} event_classes: dict[int, type[HCI_Event]] = {} - vendor_factories: list[Callable[[bytes], Optional[HCI_Event]]] = [] + vendor_factories: list[Callable[[bytes], HCI_Event | None]] = [] event_code: int fields: Fields = () _parameters: bytes = b'' @@ -5775,14 +5771,12 @@ class HCI_Event(HCI_Packet): return event_class @classmethod - def add_vendor_factory( - cls, factory: Callable[[bytes], Optional[HCI_Event]] - ) -> None: + def add_vendor_factory(cls, factory: Callable[[bytes], HCI_Event | None]) -> None: cls.vendor_factories.append(factory) @classmethod def remove_vendor_factory( - cls, factory: Callable[[bytes], Optional[HCI_Event]] + cls, factory: Callable[[bytes], HCI_Event | None] ) -> None: if factory in cls.vendor_factories: cls.vendor_factories.remove(factory) @@ -5795,7 +5789,7 @@ class HCI_Event(HCI_Packet): if len(parameters) != length: raise InvalidPacketError('invalid packet length') - subclass: Optional[type[HCI_Event]] + subclass: type[HCI_Event] | None if event_code == HCI_LE_META_EVENT: # We do this dispatch here and not in the subclass in order to avoid call # loops @@ -5833,9 +5827,9 @@ class HCI_Event(HCI_Packet): def __init__( self, - parameters: Optional[bytes] = None, + parameters: bytes | None = None, *, - event_code: Optional[int] = None, + event_code: int | None = None, **kwargs, ): if event_code is not None: @@ -5944,9 +5938,7 @@ class HCI_Extended_Event(HCI_Event): cls.subevent_names.update(cls.subevent_map(symbols)) @classmethod - def subclass_from_parameters( - cls, parameters: bytes - ) -> Optional[HCI_Extended_Event]: + def subclass_from_parameters(cls, parameters: bytes) -> HCI_Extended_Event | None: """ Factory method that parses the subevent code, finds a registered subclass, and creates an instance if found. @@ -5966,9 +5958,9 @@ class HCI_Extended_Event(HCI_Event): def __init__( self, - parameters: Optional[bytes] = None, + parameters: bytes | None = None, *, - subevent_code: Optional[int] = None, + subevent_code: int | None = None, **kwargs, ) -> None: if subevent_code is not None: @@ -6964,7 +6956,7 @@ class HCI_Command_Complete_Event(HCI_Event): command_opcode: int = field( metadata=metadata({'size': 2, 'mapper': HCI_Command.command_name}) ) - return_parameters: Union[bytes, HCI_Object, int] = field(metadata=metadata("*")) + return_parameters: bytes | HCI_Object | int = field(metadata=metadata("*")) def map_return_parameters(self, return_parameters): '''Map simple 'status' return parameters to their named constant form''' @@ -7548,20 +7540,20 @@ class HCI_IsoDataPacket(HCI_Packet): iso_sdu_fragment: bytes pb_flag: int ts_flag: int = 0 - time_stamp: Optional[int] = None - packet_sequence_number: Optional[int] = None - iso_sdu_length: Optional[int] = None - packet_status_flag: Optional[int] = None + time_stamp: int | None = None + packet_sequence_number: int | None = None + iso_sdu_length: int | None = None + packet_status_flag: int | None = None def __post_init__(self) -> None: self.ts_flag = self.time_stamp is not None @staticmethod def from_bytes(packet: bytes) -> HCI_IsoDataPacket: - time_stamp: Optional[int] = None - packet_sequence_number: Optional[int] = None - iso_sdu_length: Optional[int] = None - packet_status_flag: Optional[int] = None + time_stamp: int | None = None + packet_sequence_number: int | None = None + iso_sdu_length: int | None = None + packet_status_flag: int | None = None pos = 1 pdu_info, data_total_length = struct.unpack_from(' None: self.callback = callback diff --git a/bumble/helpers.py b/bumble/helpers.py index e67fb57e..4e361933 100644 --- a/bumble/helpers.py +++ b/bumble/helpers.py @@ -20,7 +20,7 @@ from __future__ import annotations import datetime import logging from collections.abc import Callable, MutableMapping -from typing import Any, Optional, cast +from typing import Any, cast from bumble import avc, avctp, avdtp, avrcp, crypto, rfcomm, sdp from bumble.att import ATT_CID, ATT_PDU @@ -70,7 +70,7 @@ AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'} class PacketTracer: class AclStream: psms: MutableMapping[int, int] - peer: Optional[PacketTracer.AclStream] + peer: PacketTracer.AclStream | None avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler] avctp_assemblers: MutableMapping[int, avctp.MessageAssembler] @@ -201,7 +201,7 @@ class PacketTracer: self.label = label self.emit_message = emit_message self.acl_streams = {} # ACL streams, by connection handle - self.packet_timestamp: Optional[datetime.datetime] = None + self.packet_timestamp: datetime.datetime | None = None def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream: logger.info( @@ -230,7 +230,7 @@ class PacketTracer: self.peer.end_acl_stream(connection_handle) def on_packet( - self, timestamp: Optional[datetime.datetime], packet: HCI_Packet + self, timestamp: datetime.datetime | None, packet: HCI_Packet ) -> None: self.packet_timestamp = timestamp self.emit(packet) @@ -262,7 +262,7 @@ class PacketTracer: self, packet: HCI_Packet, direction: int = 0, - timestamp: Optional[datetime.datetime] = None, + timestamp: datetime.datetime | None = None, ) -> None: if direction == 0: self.host_to_controller_analyzer.on_packet(timestamp, packet) diff --git a/bumble/hfp.py b/bumble/hfp.py index c5eecf81..3c623a93 100644 --- a/bumble/hfp.py +++ b/bumble/hfp.py @@ -25,7 +25,8 @@ import enum import logging import re import traceback -from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Optional, Union +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, ClassVar from typing_extensions import Self @@ -80,7 +81,7 @@ class HfpProtocol: dlc.sink = self.feed - def feed(self, data: Union[bytes, str]) -> None: + def feed(self, data: bytes | str) -> None: # Convert the data to a string if needed if isinstance(data, bytes): data = data.decode('utf-8') @@ -324,8 +325,8 @@ class CallInfo: status: CallInfoStatus mode: CallInfoMode multi_party: CallInfoMultiParty - number: Optional[str] = None - type: Optional[int] = None + number: str | None = None + type: int | None = None @dataclasses.dataclass @@ -353,10 +354,10 @@ class CallLineIdentification: number: str type: int - subaddr: Optional[str] = None - satype: Optional[int] = None - alpha: Optional[str] = None - cli_validity: Optional[int] = None + subaddr: str | None = None + satype: int | None = None + alpha: str | None = None + cli_validity: int | None = None @classmethod def parse_from(cls, parameters: list[bytes]) -> Self: @@ -584,7 +585,7 @@ class AgIndicatorState: indicator: AgIndicator supported_values: set[int] current_status: int - index: Optional[int] = None + index: int | None = None enabled: bool = True @property @@ -728,7 +729,7 @@ class HfProtocol(utils.EventEmitter): command_lock: asyncio.Lock if TYPE_CHECKING: response_queue: asyncio.Queue[AtResponse] - unsolicited_queue: asyncio.Queue[Optional[AtResponse]] + unsolicited_queue: asyncio.Queue[AtResponse | None] else: response_queue: asyncio.Queue unsolicited_queue: asyncio.Queue @@ -820,7 +821,7 @@ class HfProtocol(utils.EventEmitter): cmd: str, timeout: float = 1.0, response_type: AtResponseType = AtResponseType.NONE, - ) -> Union[None, AtResponse, list[AtResponse]]: + ) -> None | AtResponse | list[AtResponse]: """ Sends an AT command and wait for the peer response. Wait for the AT responses sent by the peer, to the status code. @@ -1411,7 +1412,7 @@ class AgProtocol(utils.EventEmitter): self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec))) def _on_chld(self, operation_code: bytes) -> None: - call_index: Optional[int] = None + call_index: int | None = None if len(operation_code) > 1: call_index = int(operation_code[1:]) operation_code = operation_code[:1] + b'x' @@ -1481,8 +1482,8 @@ class AgProtocol(utils.EventEmitter): def _on_cmer( self, mode: bytes, - keypad: Optional[bytes] = None, - display: Optional[bytes] = None, + keypad: bytes | None = None, + display: bytes | None = None, indicator: bytes = b'', ) -> None: if ( @@ -1844,7 +1845,7 @@ def make_ag_sdp_records( async def find_hf_sdp_record( connection: device.Connection, -) -> Optional[tuple[int, ProfileVersion, HfSdpFeature]]: +) -> tuple[int, ProfileVersion, HfSdpFeature] | None: """Searches a Hands-Free SDP record from remote device. Args: @@ -1864,9 +1865,9 @@ async def find_hf_sdp_record( ], ) for attribute_lists in search_result: - channel: Optional[int] = None - version: Optional[ProfileVersion] = None - features: Optional[HfSdpFeature] = None + channel: int | None = None + version: ProfileVersion | None = None + features: HfSdpFeature | None = None for attribute in attribute_lists: # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]]. if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID: @@ -1896,7 +1897,7 @@ async def find_hf_sdp_record( async def find_ag_sdp_record( connection: device.Connection, -) -> Optional[tuple[int, ProfileVersion, AgSdpFeature]]: +) -> tuple[int, ProfileVersion, AgSdpFeature] | None: """Searches an Audio-Gateway SDP record from remote device. Args: @@ -1915,9 +1916,9 @@ async def find_ag_sdp_record( ], ) for attribute_lists in search_result: - channel: Optional[int] = None - version: Optional[ProfileVersion] = None - features: Optional[AgSdpFeature] = None + channel: int | None = None + version: ProfileVersion | None = None + features: AgSdpFeature | None = None for attribute in attribute_lists: # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]]. if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID: diff --git a/bumble/hid.py b/bumble/hid.py index 252c54f5..232640ce 100644 --- a/bumble/hid.py +++ b/bumble/hid.py @@ -21,8 +21,8 @@ import enum import logging import struct from abc import ABC, abstractmethod +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, Optional from typing_extensions import override @@ -195,9 +195,9 @@ class SendHandshakeMessage(Message): # ----------------------------------------------------------------------------- class HID(ABC, utils.EventEmitter): - l2cap_ctrl_channel: Optional[l2cap.ClassicChannel] = None - l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None - connection: Optional[device.Connection] = None + l2cap_ctrl_channel: l2cap.ClassicChannel | None = None + l2cap_intr_channel: l2cap.ClassicChannel | None = None + connection: device.Connection | None = None EVENT_INTERRUPT_DATA = "interrupt_data" EVENT_CONTROL_DATA = "control_data" @@ -212,7 +212,7 @@ class HID(ABC, utils.EventEmitter): def __init__(self, device: device.Device, role: Role) -> None: super().__init__() - self.remote_device_bd_address: Optional[Address] = None + self.remote_device_bd_address: Address | None = None self.device = device self.role = role @@ -353,10 +353,10 @@ class Device(HID): data: bytes = b'' status: int = 0 - get_report_cb: Optional[Callable[[int, int, int], GetSetStatus]] = None - set_report_cb: Optional[Callable[[int, int, int, bytes], GetSetStatus]] = None - get_protocol_cb: Optional[Callable[[], GetSetStatus]] = None - set_protocol_cb: Optional[Callable[[int], GetSetStatus]] = None + get_report_cb: Callable[[int, int, int], GetSetStatus] | None = None + set_report_cb: Callable[[int, int, int, bytes], GetSetStatus] | None = None + get_protocol_cb: Callable[[], GetSetStatus] | None = None + set_protocol_cb: Callable[[int], GetSetStatus] | None = None def __init__(self, device: device.Device) -> None: super().__init__(device, HID.Role.DEVICE) diff --git a/bumble/host.py b/bumble/host.py index 06a049c6..929480b1 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -22,7 +22,8 @@ import collections import dataclasses import logging import struct -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Union, cast +from collections.abc import Awaitable, Callable +from typing import TYPE_CHECKING, Any, cast from bumble import drivers, hci, utils from bumble.colors import color @@ -198,7 +199,7 @@ class Connection: self.peer_address = peer_address self.assembler = hci.HCI_AclDataPacketAssembler(self.on_acl_pdu) self.transport = transport - acl_packet_queue: Optional[DataPacketQueue] = ( + acl_packet_queue: DataPacketQueue | None = ( host.le_acl_packet_queue if transport == PhysicalTransport.LE else host.acl_packet_queue @@ -241,20 +242,18 @@ class Host(utils.EventEmitter): bis_links: dict[int, IsoLink] sco_links: dict[int, ScoLink] bigs: dict[int, set[int]] - acl_packet_queue: Optional[DataPacketQueue] = None - le_acl_packet_queue: Optional[DataPacketQueue] = None - iso_packet_queue: Optional[DataPacketQueue] = None - hci_sink: Optional[TransportSink] = None + acl_packet_queue: DataPacketQueue | None = None + le_acl_packet_queue: DataPacketQueue | None = None + iso_packet_queue: DataPacketQueue | None = None + hci_sink: TransportSink | None = None hci_metadata: dict[str, Any] - long_term_key_provider: Optional[ - Callable[[int, bytes, int], Awaitable[Optional[bytes]]] - ] - link_key_provider: Optional[Callable[[hci.Address], Awaitable[Optional[bytes]]]] + long_term_key_provider: Callable[[int, bytes, int], Awaitable[bytes | None]] | None + link_key_provider: Callable[[hci.Address], Awaitable[bytes | None]] | None def __init__( self, - controller_source: Optional[TransportSource] = None, - controller_sink: Optional[TransportSink] = None, + controller_source: TransportSource | None = None, + controller_sink: TransportSink | None = None, ) -> None: super().__init__() @@ -266,7 +265,7 @@ class Host(utils.EventEmitter): self.sco_links = {} # SCO links, by connection handle self.bigs = {} # BIG Handle to BIS Handles self.pending_command = None - self.pending_response: Optional[asyncio.Future[Any]] = None + self.pending_response: asyncio.Future[Any] | None = None self.number_of_supported_advertising_sets = 0 self.maximum_advertising_data_length = 31 self.local_version = None @@ -279,7 +278,7 @@ class Host(utils.EventEmitter): self.long_term_key_provider = None self.link_key_provider = None self.pairing_io_capability_provider = None # Classic only - self.snooper: Optional[Snooper] = None + self.snooper: Snooper | None = None # Connect to the source and sink if specified if controller_source: @@ -290,9 +289,9 @@ class Host(utils.EventEmitter): def find_connection_by_bd_addr( self, bd_addr: hci.Address, - transport: Optional[int] = None, + transport: int | None = None, check_address_type: bool = False, - ) -> Optional[Connection]: + ) -> Connection | None: for connection in self.connections.values(): if bytes(connection.peer_address) == bytes(bd_addr): if ( @@ -632,7 +631,7 @@ class Host(utils.EventEmitter): ) @property - def controller(self) -> Optional[TransportSink]: + def controller(self) -> TransportSink | None: return self.hci_sink @controller.setter @@ -641,7 +640,7 @@ class Host(utils.EventEmitter): if controller: self.set_packet_source(controller) - def set_packet_sink(self, sink: Optional[TransportSink]) -> None: + def set_packet_sink(self, sink: TransportSink | None) -> None: self.hci_sink = sink def set_packet_source(self, source: TransportSource) -> None: @@ -656,7 +655,7 @@ class Host(utils.EventEmitter): self.hci_sink.on_packet(bytes(packet)) async def send_command( - self, command, check_result=False, response_timeout: Optional[int] = None + self, command, check_result=False, response_timeout: int | None = None ): # Wait until we can send (only one pending command at a time) async with self.command_semaphore: @@ -899,7 +898,7 @@ class Host(utils.EventEmitter): self.emit('l2cap_pdu', connection.handle, cid, pdu) def on_command_processed( - self, event: Union[hci.HCI_Command_Complete_Event, hci.HCI_Command_Status_Event] + self, event: hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event ): if self.pending_response: # Check that it is what we were expecting @@ -962,11 +961,11 @@ class Host(utils.EventEmitter): def on_hci_le_connection_complete_event( self, - event: Union[ - hci.HCI_LE_Connection_Complete_Event, - hci.HCI_LE_Enhanced_Connection_Complete_Event, - hci.HCI_LE_Enhanced_Connection_Complete_V2_Event, - ], + event: ( + hci.HCI_LE_Connection_Complete_Event + | hci.HCI_LE_Enhanced_Connection_Complete_Event + | hci.HCI_LE_Enhanced_Connection_Complete_V2_Event + ), ): # Check if this is a cancellation if event.status == hci.HCI_SUCCESS: @@ -1011,10 +1010,10 @@ class Host(utils.EventEmitter): def on_hci_le_enhanced_connection_complete_event( self, - event: Union[ - hci.HCI_LE_Enhanced_Connection_Complete_Event, - hci.HCI_LE_Enhanced_Connection_Complete_V2_Event, - ], + event: ( + hci.HCI_LE_Enhanced_Connection_Complete_Event + | hci.HCI_LE_Enhanced_Connection_Complete_V2_Event + ), ): # Just use the same implementation as for the non-enhanced event for now self.on_hci_le_connection_complete_event(event) diff --git a/bumble/keys.py b/bumble/keys.py index 55c21479..3dc739d8 100644 --- a/bumble/keys.py +++ b/bumble/keys.py @@ -27,7 +27,7 @@ import dataclasses import json import logging import os -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from typing_extensions import Self @@ -51,8 +51,8 @@ class PairingKeys: class Key: value: bytes authenticated: bool = False - ediv: Optional[int] = None - rand: Optional[bytes] = None + ediv: int | None = None + rand: bytes | None = None @classmethod def from_dict(cls, key_dict: dict[str, Any]) -> PairingKeys.Key: @@ -74,17 +74,17 @@ class PairingKeys: return key_dict - address_type: Optional[hci.AddressType] = None - ltk: Optional[Key] = None - ltk_central: Optional[Key] = None - ltk_peripheral: Optional[Key] = None - irk: Optional[Key] = None - csrk: Optional[Key] = None - link_key: Optional[Key] = None # Classic - link_key_type: Optional[int] = None # Classic + address_type: hci.AddressType | None = None + ltk: Key | None = None + ltk_central: Key | None = None + ltk_peripheral: Key | None = None + irk: Key | None = None + csrk: Key | None = None + link_key: Key | None = None # Classic + link_key_type: int | None = None # Classic @classmethod - def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Optional[Key]: + def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Key | None: key_dict = keys_dict.get(key_name) if key_dict is None: return None @@ -156,7 +156,7 @@ class KeyStore: async def update(self, name: str, keys: PairingKeys) -> None: pass - async def get(self, _name: str) -> Optional[PairingKeys]: + async def get(self, _name: str) -> PairingKeys | None: return None async def get_all(self) -> list[tuple[str, PairingKeys]]: @@ -274,7 +274,7 @@ class JsonKeyStore(KeyStore): @classmethod def from_device( - cls: type[Self], device: Device, filename: Optional[str] = None + cls: type[Self], device: Device, filename: str | None = None ) -> Self: if not filename: # Extract the filename from the config if there is one @@ -297,7 +297,7 @@ class JsonKeyStore(KeyStore): # Try to open the file, without failing. If the file does not exist, it # will be created upon saving. try: - with open(self.filename, 'r', encoding='utf-8') as json_file: + with open(self.filename, encoding='utf-8') as json_file: db = json.load(json_file) except FileNotFoundError: db = {} @@ -348,7 +348,7 @@ class JsonKeyStore(KeyStore): key_map.clear() await self.save(db) - async def get(self, name: str) -> Optional[PairingKeys]: + async def get(self, name: str) -> PairingKeys | None: _, key_map = await self.load() if name not in key_map: return None @@ -370,7 +370,7 @@ class MemoryKeyStore(KeyStore): async def update(self, name: str, keys: PairingKeys) -> None: self.all_keys[name] = keys - async def get(self, name: str) -> Optional[PairingKeys]: + async def get(self, name: str) -> PairingKeys | None: return self.all_keys.get(name) async def get_all(self) -> list[tuple[str, PairingKeys]]: diff --git a/bumble/l2cap.py b/bumble/l2cap.py index d74e2c9b..e79c3204 100644 --- a/bumble/l2cap.py +++ b/bumble/l2cap.py @@ -24,7 +24,7 @@ import logging import struct from collections import deque from collections.abc import Callable, Iterable, Sequence -from typing import TYPE_CHECKING, Any, ClassVar, Optional, SupportsBytes, TypeVar, Union +from typing import TYPE_CHECKING, Any, ClassVar, SupportsBytes, TypeVar from typing_extensions import override @@ -175,7 +175,7 @@ class ClassicChannelSpec: fcs_enabled: Whether to enable FCS (Frame Check Sequence). ''' - psm: Optional[int] = None + psm: int | None = None mtu: int = L2CAP_DEFAULT_MTU mps: int = L2CAP_DEFAULT_MPS tx_window_size: int = DEFAULT_TX_WINDOW_SIZE @@ -188,7 +188,7 @@ class ClassicChannelSpec: @dataclasses.dataclass class LeCreditBasedChannelSpec: - psm: Optional[int] = None + psm: int | None = None mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS @@ -372,7 +372,7 @@ class L2CAP_Control_Frame: fields: ClassVar[hci.Fields] = () code: int = dataclasses.field(default=0, init=False) name: str = dataclasses.field(default='', init=False) - _payload: Optional[bytes] = dataclasses.field(default=None, init=False) + _payload: bytes | None = dataclasses.field(default=None, init=False) identifier: int @@ -910,8 +910,8 @@ class EnhancedRetransmissionProcessor(Processor): _num_receiver_ready_polls_sent: int = 0 _pending_pdus: list[_PendingPdu] - _monitor_handle: Optional[asyncio.TimerHandle] = None - _receiver_ready_poll_handle: Optional[asyncio.TimerHandle] = None + _monitor_handle: asyncio.TimerHandle | None = None + _receiver_ready_poll_handle: asyncio.TimerHandle | None = None # Timeout, in seconds. monitor_timeout: float @@ -1109,10 +1109,10 @@ class ClassicChannel(utils.EventEmitter): EVENT_OPEN = "open" EVENT_CLOSE = "close" - connection_result: Optional[asyncio.Future[None]] - disconnection_result: Optional[asyncio.Future[None]] - response: Optional[asyncio.Future[bytes]] - sink: Optional[Callable[[bytes], Any]] + connection_result: asyncio.Future[None] | None + disconnection_result: asyncio.Future[None] | None + response: asyncio.Future[bytes] | None + sink: Callable[[bytes], Any] | None state: State connection: Connection mtu: int @@ -1159,7 +1159,7 @@ class ClassicChannel(utils.EventEmitter): def write(self, sdu: bytes) -> None: self.processor.send_sdu(sdu) - def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None: + def send_pdu(self, pdu: SupportsBytes | bytes) -> None: if self.state != self.State.OPEN: raise InvalidStateError('channel not open') self.manager.send_pdu( @@ -1542,13 +1542,13 @@ class LeCreditBasedChannel(utils.EventEmitter): CONNECTION_ERROR = 5 out_queue: deque[bytes] - connection_result: Optional[asyncio.Future[LeCreditBasedChannel]] - disconnection_result: Optional[asyncio.Future[None]] - in_sdu: Optional[bytes] - out_sdu: Optional[bytes] + connection_result: asyncio.Future[LeCreditBasedChannel] | None + disconnection_result: asyncio.Future[None] | None + in_sdu: bytes | None + out_sdu: bytes | None state: State connection: Connection - sink: Optional[Callable[[bytes], Any]] + sink: Callable[[bytes], Any] | None EVENT_OPEN = "open" EVENT_CLOSE = "close" @@ -1608,7 +1608,7 @@ class LeCreditBasedChannel(utils.EventEmitter): elif new_state == self.State.DISCONNECTED: self.emit(self.EVENT_CLOSE) - def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None: + def send_pdu(self, pdu: SupportsBytes | bytes) -> None: self.manager.send_pdu(self.connection, self.destination_cid, pdu) def send_control_frame(self, frame: L2CAP_Control_Frame) -> None: @@ -1913,7 +1913,7 @@ class ClassicChannelServer(utils.EventEmitter): self, manager: ChannelManager, psm: int, - handler: Optional[Callable[[ClassicChannel], Any]], + handler: Callable[[ClassicChannel], Any] | None, spec: ClassicChannelSpec, ) -> None: super().__init__() @@ -1940,7 +1940,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter): self, manager: ChannelManager, psm: int, - handler: Optional[Callable[[LeCreditBasedChannel], Any]], + handler: Callable[[LeCreditBasedChannel], Any] | None, max_credits: int, mtu: int, mps: int, @@ -1966,12 +1966,12 @@ class LeCreditBasedChannelServer(utils.EventEmitter): # ----------------------------------------------------------------------------- class ChannelManager: identifiers: dict[int, int] - channels: dict[int, dict[int, Union[ClassicChannel, LeCreditBasedChannel]]] + channels: dict[int, dict[int, ClassicChannel | LeCreditBasedChannel]] servers: dict[int, ClassicChannelServer] le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]] le_coc_servers: dict[int, LeCreditBasedChannelServer] le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request] - fixed_channels: dict[int, Optional[Callable[[int, bytes], Any]]] + fixed_channels: dict[int, Callable[[int, bytes], Any] | None] pending_credit_based_connections: dict[ int, dict[ @@ -1982,8 +1982,8 @@ class ChannelManager: ], ], ] - _host: Optional[Host] - connection_parameters_update_response: Optional[asyncio.Future[int]] + _host: Host | None + connection_parameters_update_response: asyncio.Future[int] | None def __init__( self, @@ -2089,7 +2089,7 @@ class ChannelManager: def create_classic_server( self, spec: ClassicChannelSpec, - handler: Optional[Callable[[ClassicChannel], Any]] = None, + handler: Callable[[ClassicChannel], Any] | None = None, ) -> ClassicChannelServer: if not spec.psm: # Find a free PSM @@ -2125,7 +2125,7 @@ class ChannelManager: def create_le_credit_based_server( self, spec: LeCreditBasedChannelSpec, - handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None, + handler: Callable[[LeCreditBasedChannel], Any] | None = None, ) -> LeCreditBasedChannelServer: if not spec.psm: # Find a free PSM @@ -2175,7 +2175,7 @@ class ChannelManager: self, connection: Connection, cid: int, - pdu: Union[SupportsBytes, bytes], + pdu: SupportsBytes | bytes, with_fcs: bool = False, ) -> None: pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu) diff --git a/bumble/link.py b/bumble/link.py index eef5a21a..e6ea2d1b 100644 --- a/bumble/link.py +++ b/bumble/link.py @@ -19,7 +19,7 @@ import asyncio # Imports # ----------------------------------------------------------------------------- import logging -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from bumble import core, hci, ll, lmp @@ -67,7 +67,7 @@ class LocalLink: def find_classic_controller( self, address: hci.Address - ) -> Optional[controller.Controller]: + ) -> controller.Controller | None: for controller in self.controllers: if controller.public_address == address: return controller diff --git a/bumble/pairing.py b/bumble/pairing.py index 26542bf2..b6509a6c 100644 --- a/bumble/pairing.py +++ b/bumble/pairing.py @@ -20,7 +20,6 @@ from __future__ import annotations import enum import secrets from dataclasses import dataclass -from typing import Optional from bumble import hci from bumble.core import AdvertisingData, LeRole @@ -45,16 +44,16 @@ from bumble.smp import ( class OobData: """OOB data that can be sent from one device to another.""" - address: Optional[hci.Address] = None - role: Optional[LeRole] = None - shared_data: Optional[OobSharedData] = None - legacy_context: Optional[OobLegacyContext] = None + address: hci.Address | None = None + role: LeRole | None = None + shared_data: OobSharedData | None = None + legacy_context: OobLegacyContext | None = None @classmethod def from_ad(cls, ad: AdvertisingData) -> OobData: instance = cls() - shared_data_c: Optional[bytes] = None - shared_data_r: Optional[bytes] = None + shared_data_c: bytes | None = None + shared_data_r: bytes | None = None for ad_type, ad_data in ad.ad_structures: if ad_type == AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS: instance.address = hci.Address(ad_data) @@ -181,14 +180,14 @@ class PairingDelegate: """Compare two numbers.""" return True - async def get_number(self) -> Optional[int]: + async def get_number(self) -> int | None: """ Return an optional number as an answer to a passkey request. Returning `None` will result in a negative reply. """ return 0 - async def get_string(self, max_length: int) -> Optional[str]: + async def get_string(self, max_length: int) -> str | None: """ Return a string whose utf-8 encoding is up to max_length bytes. """ @@ -239,18 +238,18 @@ class PairingConfig: class OobConfig: """Config for OOB pairing.""" - our_context: Optional[OobContext] - peer_data: Optional[OobSharedData] - legacy_context: Optional[OobLegacyContext] + our_context: OobContext | None + peer_data: OobSharedData | None + legacy_context: OobLegacyContext | None def __init__( self, sc: bool = True, mitm: bool = True, bonding: bool = True, - delegate: Optional[PairingDelegate] = None, - identity_address_type: Optional[AddressType] = None, - oob: Optional[OobConfig] = None, + delegate: PairingDelegate | None = None, + identity_address_type: AddressType | None = None, + oob: OobConfig | None = None, ) -> None: self.sc = sc self.mitm = mitm diff --git a/bumble/pandora/__init__.py b/bumble/pandora/__init__.py index 51ff248c..29f382d5 100644 --- a/bumble/pandora/__init__.py +++ b/bumble/pandora/__init__.py @@ -19,7 +19,7 @@ This module implement the Pandora Bluetooth test APIs for the Bumble stack. __version__ = "0.0.1" -from typing import Callable, Optional +from collections.abc import Callable import grpc import grpc.aio @@ -58,7 +58,7 @@ def register_servicer_hook( async def serve( bumble: PandoraDevice, config: Config = Config(), - grpc_server: Optional[grpc.aio.Server] = None, + grpc_server: grpc.aio.Server | None = None, port: int = 0, ) -> None: # initialize a gRPC server if not provided. diff --git a/bumble/pandora/device.py b/bumble/pandora/device.py index 584703c1..571b6494 100644 --- a/bumble/pandora/device.py +++ b/bumble/pandora/device.py @@ -16,7 +16,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any from bumble import transport from bumble.core import ( @@ -54,7 +54,7 @@ class PandoraDevice: # HCI transport name & instance. _hci_name: str - _hci: Optional[transport.Transport] # type: ignore[name-defined] + _hci: transport.Transport | None # type: ignore[name-defined] def __init__(self, config: dict[str, Any]) -> None: self.config = config @@ -98,7 +98,7 @@ class PandoraDevice: await self.close() await self.open() - def info(self) -> Optional[dict[str, str]]: + def info(self) -> dict[str, str] | None: return { 'public_bd_address': str(self.device.public_address), 'random_address': str(self.device.random_address), diff --git a/bumble/pandora/host.py b/bumble/pandora/host.py index 2f646335..ca686a22 100644 --- a/bumble/pandora/host.py +++ b/bumble/pandora/host.py @@ -17,7 +17,8 @@ from __future__ import annotations import asyncio import logging import struct -from typing import AsyncGenerator, Optional, cast +from collections.abc import AsyncGenerator +from typing import cast import grpc import grpc.aio @@ -623,7 +624,7 @@ class HostService(HostServicer): self.log.debug('Inquiry') inquiry_queue: asyncio.Queue[ - Optional[tuple[Address, int, AdvertisingData, int]] + tuple[Address, int, AdvertisingData, int] | None ] = asyncio.Queue() complete_handler = self.device.on( self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None) diff --git a/bumble/pandora/l2cap.py b/bumble/pandora/l2cap.py index 4b192b82..45559eac 100644 --- a/bumble/pandora/l2cap.py +++ b/bumble/pandora/l2cap.py @@ -18,8 +18,8 @@ import json import logging from asyncio import Future from asyncio import Queue as AsyncQueue +from collections.abc import AsyncGenerator from dataclasses import dataclass -from typing import AsyncGenerator, Optional, Union import grpc from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error @@ -56,7 +56,7 @@ from bumble.l2cap import ( from bumble.pandora import utils from bumble.pandora.config import Config -L2capChannel = Union[ClassicChannel, LeCreditBasedChannel] +L2capChannel = ClassicChannel | LeCreditBasedChannel @dataclass @@ -107,10 +107,8 @@ class L2CAPService(L2CAPServicer): oneof = request.WhichOneof('type') self.log.debug(f'WaitConnection channel request type: {oneof}.') channel_type = getattr(request, oneof) - spec: Optional[Union[ClassicChannelSpec, LeCreditBasedChannelSpec]] = None - l2cap_server: Optional[ - Union[ClassicChannelServer, LeCreditBasedChannelServer] - ] = None + spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None + l2cap_server: ClassicChannelServer | LeCreditBasedChannelServer | None = None if isinstance(channel_type, CreditBasedChannelRequest): spec = LeCreditBasedChannelSpec( psm=channel_type.spsm, @@ -217,7 +215,7 @@ class L2CAPService(L2CAPServicer): oneof = request.WhichOneof('type') self.log.debug(f'Channel request type: {oneof}.') channel_type = getattr(request, oneof) - spec: Optional[Union[ClassicChannelSpec, LeCreditBasedChannelSpec]] = None + spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None if isinstance(channel_type, CreditBasedChannelRequest): spec = LeCreditBasedChannelSpec( psm=channel_type.spsm, diff --git a/bumble/pandora/security.py b/bumble/pandora/security.py index 65c72ea7..523f06eb 100644 --- a/bumble/pandora/security.py +++ b/bumble/pandora/security.py @@ -17,8 +17,8 @@ from __future__ import annotations import asyncio import contextlib import logging -from collections.abc import Awaitable -from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union +from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable +from typing import Any import grpc from google.protobuf import ( @@ -66,7 +66,7 @@ class PairingDelegate(BasePairingDelegate): def __init__( self, connection: BumbleConnection, - service: "SecurityService", + service: SecurityService, io_capability: BasePairingDelegate.IoCapability = BasePairingDelegate.NO_OUTPUT_NO_INPUT, local_initiator_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION, local_responder_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION, @@ -132,7 +132,7 @@ class PairingDelegate(BasePairingDelegate): assert answer.answer_variant() == 'confirm' and answer.confirm is not None return answer.confirm - async def get_number(self) -> Optional[int]: + async def get_number(self) -> int | None: self.log.debug( f"Pairing event: `passkey_entry_request` (io_capability: {self.io_capability})" ) @@ -149,7 +149,7 @@ class PairingDelegate(BasePairingDelegate): assert answer.answer_variant() == 'passkey' return answer.passkey - async def get_string(self, max_length: int) -> Optional[str]: + async def get_string(self, max_length: int) -> str | None: self.log.debug( f"Pairing event: `pin_code_request` (io_capability: {self.io_capability})" ) @@ -197,8 +197,8 @@ class SecurityService(SecurityServicer): self.log = utils.BumbleServerLoggerAdapter( logging.getLogger(), {'service_name': 'Security', 'device': device} ) - self.event_queue: Optional[asyncio.Queue[PairingEvent]] = None - self.event_answer: Optional[AsyncIterator[PairingEventAnswer]] = None + self.event_queue: asyncio.Queue[PairingEvent] | None = None + self.event_answer: AsyncIterator[PairingEventAnswer] | None = None self.device = device self.config = config @@ -233,7 +233,7 @@ class SecurityService(SecurityServicer): if level == LEVEL2: return connection.encryption != 0 and connection.authenticated - link_key_type: Optional[int] = None + link_key_type: int | None = None if (keystore := connection.device.keystore) and ( keys := await keystore.get(str(connection.peer_address)) ): @@ -412,8 +412,8 @@ class SecurityService(SecurityServicer): wait_for_security: asyncio.Future[str] = ( asyncio.get_running_loop().create_future() ) - authenticate_task: Optional[asyncio.Future[None]] = None - pair_task: Optional[asyncio.Future[None]] = None + authenticate_task: asyncio.Future[None] | None = None + pair_task: asyncio.Future[None] | None = None async def authenticate() -> None: if (encryption := connection.encryption) != 0: @@ -459,7 +459,7 @@ class SecurityService(SecurityServicer): if self.need_pairing(connection, level): bumble.utils.AsyncRunner.spawn(connection.pair()) - listeners: dict[str, Callable[..., Union[None, Awaitable[None]]]] = { + listeners: dict[str, Callable[..., None | Awaitable[None]]] = { 'disconnection': set_failure('connection_died'), 'pairing_failure': set_failure('pairing_failure'), 'connection_authentication_failure': set_failure('authentication_failure'), @@ -502,7 +502,7 @@ class SecurityService(SecurityServicer): return WaitSecurityResponse(**kwargs) async def reached_security_level( - self, connection: BumbleConnection, level: Union[SecurityLevel, LESecurityLevel] + self, connection: BumbleConnection, level: SecurityLevel | LESecurityLevel ) -> bool: self.log.debug( str( diff --git a/bumble/pandora/utils.py b/bumble/pandora/utils.py index 047a06ab..3faaae01 100644 --- a/bumble/pandora/utils.py +++ b/bumble/pandora/utils.py @@ -18,7 +18,8 @@ import contextlib import functools import inspect import logging -from typing import Any, Generator, MutableMapping, Optional +from collections.abc import Generator, MutableMapping +from typing import Any import grpc from google.protobuf.message import Message # pytype: disable=pyi-error @@ -34,7 +35,7 @@ ADDRESS_TYPES: dict[str, AddressType] = { } -def address_from_request(request: Message, field: Optional[str]) -> Address: +def address_from_request(request: Message, field: str | None) -> Address: if field is None: return Address.ANY return Address(bytes(reversed(getattr(request, field))), ADDRESS_TYPES[field]) @@ -95,8 +96,7 @@ def rpc(func: Any) -> Any: @functools.wraps(func) def gen_wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any: with exception_to_rpc_error(context): - for v in func(self, request, context): - yield v + yield from func(self, request, context) @functools.wraps(func) def wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any: diff --git a/bumble/profiles/aics.py b/bumble/profiles/aics.py index 984ee102..da3764c9 100644 --- a/bumble/profiles/aics.py +++ b/bumble/profiles/aics.py @@ -22,7 +22,6 @@ from __future__ import annotations import logging import struct from dataclasses import dataclass -from typing import Optional from bumble import utils from bumble.att import ATT_Error @@ -129,7 +128,7 @@ class AudioInputState: mute: Mute = Mute.NOT_MUTED gain_mode: GainMode = GainMode.MANUAL change_counter: int = 0 - attribute: Optional[Attribute] = None + attribute: Attribute | None = None def __bytes__(self) -> bytes: return bytes( @@ -316,7 +315,7 @@ class AudioInputDescription: ''' audio_input_description: str = "Bluetooth" - attribute: Optional[Attribute] = None + attribute: Attribute | None = None def on_read(self, _connection: Connection) -> str: return self.audio_input_description @@ -339,11 +338,11 @@ class AICSService(TemplateService): def __init__( self, - audio_input_state: Optional[AudioInputState] = None, - gain_settings_properties: Optional[GainSettingsProperties] = None, + audio_input_state: AudioInputState | None = None, + gain_settings_properties: GainSettingsProperties | None = None, audio_input_type: str = "local", - audio_input_status: Optional[AudioInputStatus] = None, - audio_input_description: Optional[AudioInputDescription] = None, + audio_input_status: AudioInputStatus | None = None, + audio_input_description: AudioInputDescription | None = None, ): self.audio_input_state = ( AudioInputState() if audio_input_state is None else audio_input_state diff --git a/bumble/profiles/ams.py b/bumble/profiles/ams.py index 2af90840..0724ea8b 100644 --- a/bumble/profiles/ams.py +++ b/bumble/profiles/ams.py @@ -25,7 +25,7 @@ import asyncio import dataclasses import enum import logging -from typing import Iterable, Optional, Union +from collections.abc import Iterable from bumble import utils from bumble.device import Peer @@ -230,7 +230,7 @@ class AmsClient(utils.EventEmitter): self.supported_commands = set() @classmethod - async def for_peer(cls, peer: Peer) -> Optional[AmsClient]: + async def for_peer(cls, peer: Peer) -> AmsClient | None: ams_proxy = await peer.discover_service_and_create_proxy(AmsProxy) if ams_proxy is None: return None @@ -263,9 +263,7 @@ class AmsClient(utils.EventEmitter): async def observe( self, entity: EntityId, - attributes: Iterable[ - Union[PlayerAttributeId, QueueAttributeId, TrackAttributeId] - ], + attributes: Iterable[PlayerAttributeId | QueueAttributeId | TrackAttributeId], ) -> None: await self._ams_proxy.entity_update.write_value( bytes([entity] + list(attributes)), with_response=True diff --git a/bumble/profiles/ancs.py b/bumble/profiles/ancs.py index 8a165fb5..bbaf014a 100644 --- a/bumble/profiles/ancs.py +++ b/bumble/profiles/ancs.py @@ -27,7 +27,7 @@ import datetime import enum import logging import struct -from typing import Optional, Sequence, Union +from collections.abc import Sequence from bumble import utils from bumble.att import ATT_Error @@ -116,7 +116,7 @@ class NotificationAttributeId(utils.OpenIntEnum): @dataclasses.dataclass class NotificationAttribute: attribute_id: NotificationAttributeId - value: Union[str, int, datetime.datetime] + value: str | int | datetime.datetime @dataclasses.dataclass @@ -242,10 +242,10 @@ class AncsProxy(ProfileServiceProxy): class AncsClient(utils.EventEmitter): - _expected_response_command_id: Optional[CommandId] - _expected_response_notification_uid: Optional[int] - _expected_response_app_identifier: Optional[str] - _expected_app_identifier: Optional[str] + _expected_response_command_id: CommandId | None + _expected_response_notification_uid: int | None + _expected_response_app_identifier: str | None + _expected_app_identifier: str | None _expected_response_tuples: int _response_accumulator: bytes @@ -255,12 +255,12 @@ class AncsClient(utils.EventEmitter): super().__init__() self._ancs_proxy = ancs_proxy self._command_semaphore = asyncio.Semaphore() - self._response: Optional[asyncio.Future] = None + self._response: asyncio.Future | None = None self._reset_response() self._started = False @classmethod - async def for_peer(cls, peer: Peer) -> Optional[AncsClient]: + async def for_peer(cls, peer: Peer) -> AncsClient | None: ancs_proxy = await peer.discover_service_and_create_proxy(AncsProxy) if ancs_proxy is None: return None @@ -316,7 +316,7 @@ class AncsClient(utils.EventEmitter): # Not enough data yet. return - attributes: list[Union[NotificationAttribute, AppAttribute]] = [] + attributes: list[NotificationAttribute | AppAttribute] = [] if command_id == CommandId.GET_NOTIFICATION_ATTRIBUTES: (notification_uid,) = struct.unpack_from( @@ -342,7 +342,7 @@ class AncsClient(utils.EventEmitter): str_value = attribute_data[3 : 3 + attribute_data_length].decode( "utf-8" ) - value: Union[str, int, datetime.datetime] + value: str | int | datetime.datetime if attribute_id == NotificationAttributeId.MESSAGE_SIZE: value = int(str_value) elif attribute_id == NotificationAttributeId.DATE: @@ -415,7 +415,7 @@ class AncsClient(utils.EventEmitter): self, notification_uid: int, attributes: Sequence[ - Union[NotificationAttributeId, tuple[NotificationAttributeId, int]] + NotificationAttributeId | tuple[NotificationAttributeId, int] ], ) -> list[NotificationAttribute]: if not self._started: diff --git a/bumble/profiles/ascs.py b/bumble/profiles/ascs.py index 51cc0e00..1e9d5910 100644 --- a/bumble/profiles/ascs.py +++ b/bumble/profiles/ascs.py @@ -24,7 +24,7 @@ import logging import struct from collections.abc import Sequence from dataclasses import dataclass, field -from typing import Any, Optional, TypeVar, Union +from typing import Any, TypeVar from bumble import colors, device, gatt, gatt_client, hci, utils from bumble.profiles import le_audio @@ -49,7 +49,7 @@ class ASE_Operation: classes: dict[int, type[ASE_Operation]] = {} op_code: Opcode name: str - fields: Optional[Sequence[Any]] = None + fields: Sequence[Any] | None = None ase_id: Sequence[int] class Opcode(enum.IntEnum): @@ -278,7 +278,7 @@ class AseStateMachine(gatt.Characteristic): EVENT_STATE_CHANGE = "state_change" - cis_link: Optional[device.CisLink] = None + cis_link: device.CisLink | None = None # Additional parameters in CODEC_CONFIGURED State preferred_framing = 0 # Unframed PDU supported @@ -290,7 +290,7 @@ class AseStateMachine(gatt.Characteristic): preferred_presentation_delay_min = 0 preferred_presentation_delay_max = 0 codec_id = hci.CodingFormat(hci.CodecID.LC3) - codec_specific_configuration: Union[CodecSpecificConfiguration, bytes] = b'' + codec_specific_configuration: CodecSpecificConfiguration | bytes = b'' # Additional parameters in QOS_CONFIGURED State cig_id = 0 @@ -610,7 +610,7 @@ class AudioStreamControlService(gatt.TemplateService): ase_state_machines: dict[int, AseStateMachine] ase_control_point: gatt.Characteristic[bytes] - _active_client: Optional[device.Connection] = None + _active_client: device.Connection | None = None def __init__( self, diff --git a/bumble/profiles/asha.py b/bumble/profiles/asha.py index 43304466..bb8e4171 100644 --- a/bumble/profiles/asha.py +++ b/bumble/profiles/asha.py @@ -19,7 +19,8 @@ import enum import logging import struct -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any from bumble import data_types, gatt, gatt_client, l2cap, utils from bumble.core import AdvertisingData @@ -90,20 +91,20 @@ class AshaService(gatt.TemplateService): EVENT_DISCONNECTED = "disconnected" EVENT_VOLUME_CHANGED = "volume_changed" - audio_sink: Optional[Callable[[bytes], Any]] - active_codec: Optional[Codec] = None - audio_type: Optional[AudioType] = None - volume: Optional[int] = None - other_state: Optional[int] = None - connection: Optional[Connection] = None + audio_sink: Callable[[bytes], Any] | None + active_codec: Codec | None = None + audio_type: AudioType | None = None + volume: int | None = None + other_state: int | None = None + connection: Connection | None = None def __init__( self, capability: int, - hisyncid: Union[list[int], bytes], + hisyncid: list[int] | bytes, device: Device, psm: int = 0, - audio_sink: Optional[Callable[[bytes], Any]] = None, + audio_sink: Callable[[bytes], Any] | None = None, feature_map: int = FeatureMap.LE_COC_AUDIO_OUTPUT_STREAMING_SUPPORTED, protocol_version: int = 0x01, render_delay_milliseconds: int = 0, diff --git a/bumble/profiles/bass.py b/bumble/profiles/bass.py index 64f85c68..86577b90 100644 --- a/bumble/profiles/bass.py +++ b/bumble/profiles/bass.py @@ -21,7 +21,8 @@ from __future__ import annotations import dataclasses import logging import struct -from typing import ClassVar, Optional, Sequence +from collections.abc import Sequence +from typing import ClassVar from bumble import core, device, gatt, gatt_adapters, gatt_client, hci, utils @@ -351,7 +352,7 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy): broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy[bytes] broadcast_receive_states: list[ - gatt_client.CharacteristicProxy[Optional[BroadcastReceiveState]] + gatt_client.CharacteristicProxy[BroadcastReceiveState | None] ] def __init__(self, service_proxy: gatt_client.ServiceProxy): diff --git a/bumble/profiles/battery_service.py b/bumble/profiles/battery_service.py index 0fdee94e..f0318d6c 100644 --- a/bumble/profiles/battery_service.py +++ b/bumble/profiles/battery_service.py @@ -16,7 +16,6 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- -from typing import Optional from bumble.gatt import ( GATT_BATTERY_LEVEL_CHARACTERISTIC, @@ -56,7 +55,7 @@ class BatteryService(TemplateService): class BatteryServiceProxy(ProfileServiceProxy): SERVICE_CLASS = BatteryService - battery_level: Optional[CharacteristicProxy[int]] + battery_level: CharacteristicProxy[int] | None def __init__(self, service_proxy): self.service_proxy = service_proxy diff --git a/bumble/profiles/csip.py b/bumble/profiles/csip.py index 025bc7d4..6cfe9a62 100644 --- a/bumble/profiles/csip.py +++ b/bumble/profiles/csip.py @@ -20,7 +20,6 @@ from __future__ import annotations import enum import struct -from typing import Optional from bumble import core, crypto, device, gatt, gatt_client @@ -96,17 +95,17 @@ class CoordinatedSetIdentificationService(gatt.TemplateService): set_identity_resolving_key: bytes set_identity_resolving_key_characteristic: gatt.Characteristic[bytes] - coordinated_set_size_characteristic: Optional[gatt.Characteristic[bytes]] = None - set_member_lock_characteristic: Optional[gatt.Characteristic[bytes]] = None - set_member_rank_characteristic: Optional[gatt.Characteristic[bytes]] = None + coordinated_set_size_characteristic: gatt.Characteristic[bytes] | None = None + set_member_lock_characteristic: gatt.Characteristic[bytes] | None = None + set_member_rank_characteristic: gatt.Characteristic[bytes] | None = None def __init__( self, set_identity_resolving_key: bytes, set_identity_resolving_key_type: SirkType, - coordinated_set_size: Optional[int] = None, - set_member_lock: Optional[MemberLock] = None, - set_member_rank: Optional[int] = None, + coordinated_set_size: int | None = None, + set_member_lock: MemberLock | None = None, + set_member_rank: int | None = None, ) -> None: if len(set_identity_resolving_key) != SET_IDENTITY_RESOLVING_KEY_LENGTH: raise core.InvalidArgumentError( @@ -198,9 +197,9 @@ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy): SERVICE_CLASS = CoordinatedSetIdentificationService set_identity_resolving_key: gatt_client.CharacteristicProxy[bytes] - coordinated_set_size: Optional[gatt_client.CharacteristicProxy[bytes]] = None - set_member_lock: Optional[gatt_client.CharacteristicProxy[bytes]] = None - set_member_rank: Optional[gatt_client.CharacteristicProxy[bytes]] = None + coordinated_set_size: gatt_client.CharacteristicProxy[bytes] | None = None + set_member_lock: gatt_client.CharacteristicProxy[bytes] | None = None + set_member_rank: gatt_client.CharacteristicProxy[bytes] | None = None def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None: self.service_proxy = service_proxy diff --git a/bumble/profiles/device_information_service.py b/bumble/profiles/device_information_service.py index f08d3fa1..ce537121 100644 --- a/bumble/profiles/device_information_service.py +++ b/bumble/profiles/device_information_service.py @@ -17,7 +17,6 @@ # Imports # ----------------------------------------------------------------------------- import struct -from typing import Optional from bumble.gatt import ( GATT_DEVICE_INFORMATION_SERVICE, @@ -54,14 +53,14 @@ class DeviceInformationService(TemplateService): def __init__( self, - manufacturer_name: Optional[str] = None, - model_number: Optional[str] = None, - serial_number: Optional[str] = None, - hardware_revision: Optional[str] = None, - firmware_revision: Optional[str] = None, - software_revision: Optional[str] = None, - system_id: Optional[tuple[int, int]] = None, # (OUI, Manufacturer ID) - ieee_regulatory_certification_data_list: Optional[bytes] = None, + manufacturer_name: str | None = None, + model_number: str | None = None, + serial_number: str | None = None, + hardware_revision: str | None = None, + firmware_revision: str | None = None, + software_revision: str | None = None, + system_id: tuple[int, int] | None = None, # (OUI, Manufacturer ID) + ieee_regulatory_certification_data_list: bytes | None = None, # TODO: pnp_id ): characteristics: list[Characteristic[bytes]] = [ @@ -109,14 +108,14 @@ class DeviceInformationService(TemplateService): class DeviceInformationServiceProxy(ProfileServiceProxy): SERVICE_CLASS = DeviceInformationService - manufacturer_name: Optional[CharacteristicProxy[str]] - model_number: Optional[CharacteristicProxy[str]] - serial_number: Optional[CharacteristicProxy[str]] - hardware_revision: Optional[CharacteristicProxy[str]] - firmware_revision: Optional[CharacteristicProxy[str]] - software_revision: Optional[CharacteristicProxy[str]] - system_id: Optional[CharacteristicProxy[tuple[int, int]]] - ieee_regulatory_certification_data_list: Optional[CharacteristicProxy[bytes]] + manufacturer_name: CharacteristicProxy[str] | None + model_number: CharacteristicProxy[str] | None + serial_number: CharacteristicProxy[str] | None + hardware_revision: CharacteristicProxy[str] | None + firmware_revision: CharacteristicProxy[str] | None + software_revision: CharacteristicProxy[str] | None + system_id: CharacteristicProxy[tuple[int, int]] | None + ieee_regulatory_certification_data_list: CharacteristicProxy[bytes] | None def __init__(self, service_proxy: ServiceProxy): self.service_proxy = service_proxy diff --git a/bumble/profiles/gap.py b/bumble/profiles/gap.py index 198b419c..3b818af6 100644 --- a/bumble/profiles/gap.py +++ b/bumble/profiles/gap.py @@ -19,7 +19,6 @@ # ----------------------------------------------------------------------------- import logging import struct -from typing import Optional, Union from bumble.core import Appearance from bumble.gatt import ( @@ -54,7 +53,7 @@ class GenericAccessService(TemplateService): appearance_characteristic: Characteristic[bytes] def __init__( - self, device_name: str, appearance: Union[Appearance, tuple[int, int], int] = 0 + self, device_name: str, appearance: Appearance | tuple[int, int] | int = 0 ): if isinstance(appearance, int): appearance_int = appearance @@ -88,8 +87,8 @@ class GenericAccessService(TemplateService): class GenericAccessServiceProxy(ProfileServiceProxy): SERVICE_CLASS = GenericAccessService - device_name: Optional[CharacteristicProxy[str]] - appearance: Optional[CharacteristicProxy[Appearance]] + device_name: CharacteristicProxy[str] | None + appearance: CharacteristicProxy[Appearance] | None def __init__(self, service_proxy: ServiceProxy): self.service_proxy = service_proxy diff --git a/bumble/profiles/gmap.py b/bumble/profiles/gmap.py index 9784d5df..d0bd08b1 100644 --- a/bumble/profiles/gmap.py +++ b/bumble/profiles/gmap.py @@ -19,7 +19,6 @@ # ----------------------------------------------------------------------------- import struct from enum import IntFlag -from typing import Optional from bumble.gatt import ( GATT_BGR_FEATURES_CHARACTERISTIC, @@ -77,18 +76,18 @@ class GamingAudioService(TemplateService): UUID = GATT_GAMING_AUDIO_SERVICE gmap_role: Characteristic - ugg_features: Optional[Characteristic] = None - ugt_features: Optional[Characteristic] = None - bgs_features: Optional[Characteristic] = None - bgr_features: Optional[Characteristic] = None + ugg_features: Characteristic | None = None + ugt_features: Characteristic | None = None + bgs_features: Characteristic | None = None + bgr_features: Characteristic | None = None def __init__( self, gmap_role: GmapRole, - ugg_features: Optional[UggFeatures] = None, - ugt_features: Optional[UgtFeatures] = None, - bgs_features: Optional[BgsFeatures] = None, - bgr_features: Optional[BgrFeatures] = None, + ugg_features: UggFeatures | None = None, + ugt_features: UgtFeatures | None = None, + bgs_features: BgsFeatures | None = None, + bgr_features: BgrFeatures | None = None, ) -> None: characteristics = [] @@ -150,10 +149,10 @@ class GamingAudioService(TemplateService): class GamingAudioServiceProxy(ProfileServiceProxy): SERVICE_CLASS = GamingAudioService - ugg_features: Optional[CharacteristicProxy[UggFeatures]] = None - ugt_features: Optional[CharacteristicProxy[UgtFeatures]] = None - bgs_features: Optional[CharacteristicProxy[BgsFeatures]] = None - bgr_features: Optional[CharacteristicProxy[BgrFeatures]] = None + ugg_features: CharacteristicProxy[UggFeatures] | None = None + ugt_features: CharacteristicProxy[UgtFeatures] | None = None + bgs_features: CharacteristicProxy[BgsFeatures] | None = None + bgr_features: CharacteristicProxy[BgrFeatures] | None = None def __init__(self, service_proxy: ServiceProxy) -> None: self.service_proxy = service_proxy diff --git a/bumble/profiles/hap.py b/bumble/profiles/hap.py index 15194d9d..d2a0b9c8 100644 --- a/bumble/profiles/hap.py +++ b/bumble/profiles/hap.py @@ -20,7 +20,7 @@ from __future__ import annotations import asyncio import logging from dataclasses import dataclass, field -from typing import Any, Optional, Union +from typing import Any from bumble import att, gatt, gatt_adapters, gatt_client, utils from bumble.core import InvalidArgumentError, InvalidStateError @@ -145,7 +145,7 @@ class PresetChangedOperation: return bytes([self.prev_index]) + bytes(self.preset_record) change_id: ChangeId - additional_parameters: Union[Generic, int] + additional_parameters: Generic | int def to_bytes(self, is_last: bool) -> bytes: if isinstance(self.additional_parameters, PresetChangedOperation.Generic): @@ -235,7 +235,7 @@ class HearingAccessService(gatt.TemplateService): preset_records: dict[int, PresetRecord] # key is the preset index read_presets_request_in_progress: bool - other_server_in_binaural_set: Optional[HearingAccessService] = None + other_server_in_binaural_set: HearingAccessService | None = None preset_changed_operations_history_per_device: dict[ Address, list[PresetChangedOperation] diff --git a/bumble/profiles/heart_rate_service.py b/bumble/profiles/heart_rate_service.py index 5797979f..b31e7262 100644 --- a/bumble/profiles/heart_rate_service.py +++ b/bumble/profiles/heart_rate_service.py @@ -20,7 +20,6 @@ from __future__ import annotations import struct from enum import IntEnum -from typing import Optional from bumble import core from bumble.att import ATT_Error @@ -207,13 +206,13 @@ class HeartRateService(TemplateService): class HeartRateServiceProxy(ProfileServiceProxy): SERVICE_CLASS = HeartRateService - heart_rate_measurement: Optional[ - CharacteristicProxy[HeartRateService.HeartRateMeasurement] - ] - body_sensor_location: Optional[ - CharacteristicProxy[HeartRateService.BodySensorLocation] - ] - heart_rate_control_point: Optional[CharacteristicProxy[int]] + heart_rate_measurement: ( + CharacteristicProxy[HeartRateService.HeartRateMeasurement] | None + ) + body_sensor_location: ( + CharacteristicProxy[HeartRateService.BodySensorLocation] | None + ) + heart_rate_control_point: CharacteristicProxy[int] | None def __init__(self, service_proxy): self.service_proxy = service_proxy diff --git a/bumble/profiles/mcp.py b/bumble/profiles/mcp.py index ea34174a..c6d38486 100644 --- a/bumble/profiles/mcp.py +++ b/bumble/profiles/mcp.py @@ -22,7 +22,7 @@ import asyncio import dataclasses import enum import struct -from typing import TYPE_CHECKING, ClassVar, Optional +from typing import TYPE_CHECKING, ClassVar from typing_extensions import Self @@ -196,7 +196,7 @@ class MediaControlService(gatt.TemplateService): UUID = gatt.GATT_MEDIA_CONTROL_SERVICE - def __init__(self, media_player_name: Optional[str] = None) -> None: + def __init__(self, media_player_name: str | None = None) -> None: self.track_position = 0 self.media_player_name_characteristic = gatt.Characteristic( @@ -337,32 +337,32 @@ class MediaControlServiceProxy( EVENT_TRACK_DURATION = "track_duration" EVENT_TRACK_POSITION = "track_position" - media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None - media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None - track_changed: Optional[gatt_client.CharacteristicProxy[bytes]] = None - track_title: Optional[gatt_client.CharacteristicProxy[bytes]] = None - track_duration: Optional[gatt_client.CharacteristicProxy[bytes]] = None - track_position: Optional[gatt_client.CharacteristicProxy[bytes]] = None - playback_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None - seeking_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None - current_track_segments_object_id: Optional[ - gatt_client.CharacteristicProxy[bytes] - ] = None - current_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - next_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - parent_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - current_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - playing_order: Optional[gatt_client.CharacteristicProxy[bytes]] = None - playing_orders_supported: Optional[gatt_client.CharacteristicProxy[bytes]] = None - media_state: Optional[gatt_client.CharacteristicProxy[bytes]] = None - media_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None - media_control_point_opcodes_supported: Optional[ - gatt_client.CharacteristicProxy[bytes] - ] = None - search_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None - search_results_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None - content_control_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None + media_player_name: gatt_client.CharacteristicProxy[bytes] | None = None + media_player_icon_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + media_player_icon_url: gatt_client.CharacteristicProxy[bytes] | None = None + track_changed: gatt_client.CharacteristicProxy[bytes] | None = None + track_title: gatt_client.CharacteristicProxy[bytes] | None = None + track_duration: gatt_client.CharacteristicProxy[bytes] | None = None + track_position: gatt_client.CharacteristicProxy[bytes] | None = None + playback_speed: gatt_client.CharacteristicProxy[bytes] | None = None + seeking_speed: gatt_client.CharacteristicProxy[bytes] | None = None + current_track_segments_object_id: gatt_client.CharacteristicProxy[bytes] | None = ( + None + ) + current_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + next_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + parent_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + current_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + playing_order: gatt_client.CharacteristicProxy[bytes] | None = None + playing_orders_supported: gatt_client.CharacteristicProxy[bytes] | None = None + media_state: gatt_client.CharacteristicProxy[bytes] | None = None + media_control_point: gatt_client.CharacteristicProxy[bytes] | None = None + media_control_point_opcodes_supported: ( + gatt_client.CharacteristicProxy[bytes] | None + ) = None + search_control_point: gatt_client.CharacteristicProxy[bytes] | None = None + search_results_object_id: gatt_client.CharacteristicProxy[bytes] | None = None + content_control_id: gatt_client.CharacteristicProxy[bytes] | None = None if TYPE_CHECKING: media_control_point_notifications: asyncio.Queue[bytes] diff --git a/bumble/profiles/pacs.py b/bumble/profiles/pacs.py index 1c9a090f..c03c61a8 100644 --- a/bumble/profiles/pacs.py +++ b/bumble/profiles/pacs.py @@ -21,7 +21,7 @@ from __future__ import annotations import dataclasses import logging import struct -from typing import Optional, Sequence, Union +from collections.abc import Sequence from bumble import gatt, gatt_adapters, gatt_client, hci from bumble.profiles import le_audio @@ -39,7 +39,7 @@ class PacRecord: '''Published Audio Capabilities Service, Table 3.2/3.4.''' coding_format: hci.CodingFormat - codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes] + codec_specific_capabilities: CodecSpecificCapabilities | bytes metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata) @classmethod @@ -56,7 +56,7 @@ class PacRecord: offset += 1 metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size]) - codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes] + codec_specific_capabilities: CodecSpecificCapabilities | bytes if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC: codec_specific_capabilities = codec_specific_capabilities_bytes else: @@ -101,10 +101,10 @@ class PacRecord: class PublishedAudioCapabilitiesService(gatt.TemplateService): UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE - sink_pac: Optional[gatt.Characteristic[bytes]] - sink_audio_locations: Optional[gatt.Characteristic[bytes]] - source_pac: Optional[gatt.Characteristic[bytes]] - source_audio_locations: Optional[gatt.Characteristic[bytes]] + sink_pac: gatt.Characteristic[bytes] | None + sink_audio_locations: gatt.Characteristic[bytes] | None + source_pac: gatt.Characteristic[bytes] | None + source_audio_locations: gatt.Characteristic[bytes] | None available_audio_contexts: gatt.Characteristic[bytes] supported_audio_contexts: gatt.Characteristic[bytes] @@ -115,9 +115,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService): available_source_context: ContextType, available_sink_context: ContextType, sink_pac: Sequence[PacRecord] = (), - sink_audio_locations: Optional[AudioLocation] = None, + sink_audio_locations: AudioLocation | None = None, source_pac: Sequence[PacRecord] = (), - source_audio_locations: Optional[AudioLocation] = None, + source_audio_locations: AudioLocation | None = None, ) -> None: characteristics = [] @@ -183,14 +183,10 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService): class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy): SERVICE_CLASS = PublishedAudioCapabilitiesService - sink_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None - sink_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = ( - None - ) - source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None - source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = ( - None - ) + sink_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None + sink_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None + source_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None + source_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]] supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]] diff --git a/bumble/profiles/vcs.py b/bumble/profiles/vcs.py index 8acd141f..84154c22 100644 --- a/bumble/profiles/vcs.py +++ b/bumble/profiles/vcs.py @@ -20,7 +20,7 @@ from __future__ import annotations import dataclasses import enum -from typing import Sequence +from collections.abc import Sequence from bumble import att, device, gatt, gatt_adapters, gatt_client diff --git a/bumble/profiles/vocs.py b/bumble/profiles/vocs.py index 13b12502..390a4c73 100644 --- a/bumble/profiles/vocs.py +++ b/bumble/profiles/vocs.py @@ -18,7 +18,6 @@ # ----------------------------------------------------------------------------- import struct from dataclasses import dataclass -from typing import Optional from bumble import utils from bumble.att import ATT_Error @@ -69,7 +68,7 @@ class ErrorCode(utils.OpenIntEnum): class VolumeOffsetState: volume_offset: int = 0 change_counter: int = 0 - attribute: Optional[Characteristic] = None + attribute: Characteristic | None = None def __bytes__(self) -> bytes: return struct.pack(' bytes: return struct.pack(' None: self.volume_offset_state = ( VolumeOffsetState() if volume_offset_state is None else volume_offset_state diff --git a/bumble/rfcomm.py b/bumble/rfcomm.py index fba2e0f8..aa5236f7 100644 --- a/bumble/rfcomm.py +++ b/bumble/rfcomm.py @@ -22,7 +22,8 @@ import collections import dataclasses import enum import logging -from typing import TYPE_CHECKING, Callable, Optional, Union +from collections.abc import Callable +from typing import TYPE_CHECKING from typing_extensions import Self @@ -119,7 +120,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30 # ----------------------------------------------------------------------------- def make_service_sdp_records( - service_record_handle: int, channel: int, uuid: Optional[UUID] = None + service_record_handle: int, channel: int, uuid: UUID | None = None ) -> list[sdp.ServiceAttribute]: """ Create SDP records for an RFComm service given a channel number and an @@ -186,7 +187,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]: ) for attribute_lists in search_result: service_classes: list[UUID] = [] - channel: Optional[int] = None + channel: int | None = None for attribute in attribute_lists: # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]]. if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID: @@ -207,7 +208,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]: # ----------------------------------------------------------------------------- async def find_rfcomm_channel_with_uuid( connection: Connection, uuid: str | UUID -) -> Optional[int]: +) -> int | None: """Searches an RFCOMM channel associated with given UUID from service records. Args: @@ -473,15 +474,15 @@ class DLC(utils.EventEmitter): self.state = DLC.State.INIT self.role = multiplexer.role self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0 - self.connection_result: Optional[asyncio.Future] = None - self.disconnection_result: Optional[asyncio.Future] = None + self.connection_result: asyncio.Future | None = None + self.disconnection_result: asyncio.Future | None = None self.drained = asyncio.Event() self.drained.set() # Queued packets when sink is not set. self._enqueued_rx_packets: collections.deque[bytes] = collections.deque( maxlen=DEFAULT_RX_QUEUE_SIZE ) - self._sink: Optional[Callable[[bytes], None]] = None + self._sink: Callable[[bytes], None] | None = None # Compute the MTU max_overhead = 4 + 1 # header with 2-byte length + fcs @@ -490,11 +491,11 @@ class DLC(utils.EventEmitter): ) @property - def sink(self) -> Optional[Callable[[bytes], None]]: + def sink(self) -> Callable[[bytes], None] | None: return self._sink @sink.setter - def sink(self, sink: Optional[Callable[[bytes], None]]) -> None: + def sink(self, sink: Callable[[bytes], None] | None) -> None: self._sink = sink # Dump queued packets to sink if sink: @@ -712,7 +713,7 @@ class DLC(utils.EventEmitter): self.drained.set() # Stream protocol - def write(self, data: Union[bytes, str]) -> None: + def write(self, data: bytes | str) -> None: # We can only send bytes if not isinstance(data, bytes): if isinstance(data, str): @@ -769,10 +770,10 @@ class Multiplexer(utils.EventEmitter): EVENT_DLC = "dlc" - connection_result: Optional[asyncio.Future] - disconnection_result: Optional[asyncio.Future] - open_result: Optional[asyncio.Future] - acceptor: Optional[Callable[[int], Optional[tuple[int, int]]]] + connection_result: asyncio.Future | None + disconnection_result: asyncio.Future | None + open_result: asyncio.Future | None + acceptor: Callable[[int], tuple[int, int] | None] | None dlcs: dict[int, DLC] def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None: @@ -784,7 +785,7 @@ class Multiplexer(utils.EventEmitter): self.connection_result = None self.disconnection_result = None self.open_result = None - self.open_pn: Optional[RFCOMM_MCC_PN] = None + self.open_pn: RFCOMM_MCC_PN | None = None self.open_rx_max_credits = 0 self.acceptor = None @@ -1031,8 +1032,8 @@ class Multiplexer(utils.EventEmitter): # ----------------------------------------------------------------------------- class Client: - multiplexer: Optional[Multiplexer] - l2cap_channel: Optional[l2cap.ClassicChannel] + multiplexer: Multiplexer | None + l2cap_channel: l2cap.ClassicChannel | None def __init__( self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU @@ -1145,7 +1146,7 @@ class Server(utils.EventEmitter): # Notify self.emit(self.EVENT_START, multiplexer) - def accept_dlc(self, channel_number: int) -> Optional[tuple[int, int]]: + def accept_dlc(self, channel_number: int) -> tuple[int, int] | None: return self.dlc_configs.get(channel_number) def on_dlc(self, dlc: DLC) -> None: diff --git a/bumble/sdp.py b/bumble/sdp.py index 90e04a78..912afeb5 100644 --- a/bumble/sdp.py +++ b/bumble/sdp.py @@ -20,7 +20,8 @@ from __future__ import annotations import asyncio import logging import struct -from typing import TYPE_CHECKING, Iterable, NewType, Optional, Sequence, Union +from collections.abc import Iterable, Sequence +from typing import TYPE_CHECKING, NewType from typing_extensions import Self @@ -497,7 +498,7 @@ class ServiceAttribute: @staticmethod def find_attribute_in_list( attribute_list: Iterable[ServiceAttribute], attribute_id: int - ) -> Optional[DataElement]: + ) -> DataElement | None: return next( ( attribute.value @@ -778,11 +779,11 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU): class Client: def __init__(self, connection: Connection, mtu: int = 0) -> None: self.connection = connection - self.channel: Optional[l2cap.ClassicChannel] = None + self.channel: l2cap.ClassicChannel | None = None self.mtu = mtu self.request_semaphore = asyncio.Semaphore(1) - self.pending_request: Optional[SDP_PDU] = None - self.pending_response: Optional[asyncio.futures.Future[SDP_PDU]] = None + self.pending_request: SDP_PDU | None = None + self.pending_response: asyncio.futures.Future[SDP_PDU] | None = None self.next_transaction_id = 0 async def connect(self) -> None: @@ -898,7 +899,7 @@ class Client: async def search_attributes( self, uuids: Iterable[core.UUID], - attribute_ids: Iterable[Union[int, tuple[int, int]]], + attribute_ids: Iterable[int | tuple[int, int]], ) -> list[list[ServiceAttribute]]: """ Search for attributes by UUID and attribute IDs. @@ -970,7 +971,7 @@ class Client: async def get_attributes( self, service_record_handle: int, - attribute_ids: Iterable[Union[int, tuple[int, int]]], + attribute_ids: Iterable[int | tuple[int, int]], ) -> list[ServiceAttribute]: """ Get attributes for a service. @@ -1042,10 +1043,10 @@ class Client: # ----------------------------------------------------------------------------- class Server: CONTINUATION_STATE = bytes([0x01, 0x00]) - channel: Optional[l2cap.ClassicChannel] + channel: l2cap.ClassicChannel | None Service = NewType('Service', list[ServiceAttribute]) service_records: dict[int, Service] - current_response: Union[None, bytes, tuple[int, list[int]]] + current_response: None | bytes | tuple[int, list[int]] def __init__(self, device: Device) -> None: self.device = device @@ -1123,7 +1124,7 @@ class Server: self, continuation_state: bytes, transaction_id: int, - ) -> Optional[bool]: + ) -> bool | None: # Check if this is a valid continuation if len(continuation_state) > 1: if ( diff --git a/bumble/smp.py b/bumble/smp.py index e91c0618..c27e9e7d 100644 --- a/bumble/smp.py +++ b/bumble/smp.py @@ -27,8 +27,9 @@ from __future__ import annotations import asyncio import enum import logging +from collections.abc import Awaitable, Callable from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Awaitable, Callable, ClassVar, Optional, TypeVar, cast +from typing import TYPE_CHECKING, ClassVar, TypeVar, cast from bumble import crypto, utils from bumble.colors import color @@ -204,10 +205,10 @@ class SMP_Command: fields: ClassVar[Fields] code: int = field(default=0, init=False) name: str = field(default='', init=False) - _payload: Optional[bytes] = field(default=None, init=False) + _payload: bytes | None = field(default=None, init=False) @classmethod - def from_bytes(cls, pdu: bytes) -> "SMP_Command": + def from_bytes(cls, pdu: bytes) -> SMP_Command: code = pdu[0] subclass = SMP_Command.smp_classes.get(code) @@ -545,7 +546,7 @@ class OobContext: r: bytes def __init__( - self, ecc_key: Optional[crypto.EccKey] = None, r: Optional[bytes] = None + self, ecc_key: crypto.EccKey | None = None, r: bytes | None = None ) -> None: self.ecc_key = crypto.EccKey.generate() if ecc_key is None else ecc_key self.r = crypto.r() if r is None else r @@ -561,7 +562,7 @@ class OobLegacyContext: tk: bytes - def __init__(self, tk: Optional[bytes] = None) -> None: + def __init__(self, tk: bytes | None = None) -> None: self.tk = crypto.r() if tk is None else tk @@ -668,31 +669,31 @@ class Session: self.stk = None self.ltk_ediv = 0 self.ltk_rand = bytes(8) - self.link_key: Optional[bytes] = None + self.link_key: bytes | None = None self.maximum_encryption_key_size: int = 0 self.initiator_key_distribution: int = 0 self.responder_key_distribution: int = 0 - self.peer_random_value: Optional[bytes] = None + self.peer_random_value: bytes | None = None self.peer_public_key_x: bytes = bytes(32) self.peer_public_key_y = bytes(32) self.peer_ltk = None self.peer_ediv = None - self.peer_rand: Optional[bytes] = None + self.peer_rand: bytes | None = None self.peer_identity_resolving_key = None - self.peer_bd_addr: Optional[Address] = None + self.peer_bd_addr: Address | None = None self.peer_signature_key = None self.peer_expected_distributions: list[type[SMP_Command]] = [] self.dh_key = b'' self.confirm_value = None - self.passkey: Optional[int] = None + self.passkey: int | None = None self.passkey_ready = asyncio.Event() self.passkey_step = 0 self.passkey_display = False self.pairing_method: PairingMethod = PairingMethod.JUST_WORKS self.pairing_config = pairing_config - self.wait_before_continuing: Optional[asyncio.Future[None]] = None + self.wait_before_continuing: asyncio.Future[None] | None = None self.completed = False - self.ctkd_task: Optional[Awaitable[None]] = None + self.ctkd_task: Awaitable[None] | None = None # Decide if we're the initiator or the responder self.is_initiator = is_initiator @@ -711,7 +712,7 @@ class Session: # Create a future that can be used to wait for the session to complete if self.is_initiator: - self.pairing_result: Optional[asyncio.Future[None]] = ( + self.pairing_result: asyncio.Future[None] | None = ( asyncio.get_running_loop().create_future() ) else: @@ -819,7 +820,7 @@ class Session: def auth_req(self) -> int: return smp_auth_req(self.bonding, self.mitm, self.sc, self.keypress, self.ct2) - def get_long_term_key(self, rand: bytes, ediv: int) -> Optional[bytes]: + def get_long_term_key(self, rand: bytes, ediv: int) -> bytes | None: if not self.sc and not self.completed: if rand == self.ltk_rand and ediv == self.ltk_ediv: return self.stk @@ -930,7 +931,7 @@ class Session: self.pairing_config.delegate.display_number(self.passkey, digits=6) ) - def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None: + def input_passkey(self, next_steps: Callable[[], None] | None = None) -> None: # Prompt the user for the passkey displayed on the peer def after_input(passkey: int) -> None: self.passkey = passkey @@ -947,7 +948,7 @@ class Session: self.prompt_user_for_number(after_input) def display_or_input_passkey( - self, next_steps: Optional[Callable[[], None]] = None + self, next_steps: Callable[[], None] | None = None ) -> None: if self.passkey_display: @@ -1918,7 +1919,7 @@ class Manager(utils.EventEmitter): sessions: dict[int, Session] pairing_config_factory: Callable[[Connection], PairingConfig] session_proxy: type[Session] - _ecc_key: Optional[crypto.EccKey] + _ecc_key: crypto.EccKey | None def __init__( self, @@ -2011,7 +2012,7 @@ class Manager(utils.EventEmitter): self.device.on_pairing_start(session.connection) async def on_pairing( - self, session: Session, identity_address: Optional[Address], keys: PairingKeys + self, session: Session, identity_address: Address | None, keys: PairingKeys ) -> None: # Store the keys in the key store if self.device.keystore and identity_address is not None: @@ -2030,7 +2031,7 @@ class Manager(utils.EventEmitter): def get_long_term_key( self, connection: Connection, rand: bytes, ediv: int - ) -> Optional[bytes]: + ) -> bytes | None: if session := self.sessions.get(connection.handle): return session.get_long_term_key(rand, ediv) diff --git a/bumble/snoop.py b/bumble/snoop.py index 84c1a8f4..401bf56f 100644 --- a/bumble/snoop.py +++ b/bumble/snoop.py @@ -16,13 +16,14 @@ import datetime import logging import os import struct +from collections.abc import Generator # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- from contextlib import contextmanager from enum import IntEnum -from typing import BinaryIO, Generator +from typing import BinaryIO from bumble import core from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET diff --git a/bumble/transport/__init__.py b/bumble/transport/__init__.py index dc60c2d0..18c31ab0 100644 --- a/bumble/transport/__init__.py +++ b/bumble/transport/__init__.py @@ -18,7 +18,6 @@ import logging import os import re -from typing import Optional from bumble import utils from bumble.snoop import create_snooper @@ -111,7 +110,7 @@ async def open_transport(name: str) -> Transport: # ----------------------------------------------------------------------------- -async def _open_transport(scheme: str, spec: Optional[str]) -> Transport: +async def _open_transport(scheme: str, spec: str | None) -> Transport: # pylint: disable=import-outside-toplevel # pylint: disable=too-many-return-statements diff --git a/bumble/transport/android_emulator.py b/bumble/transport/android_emulator.py index 12078409..fe0a0fbe 100644 --- a/bumble/transport/android_emulator.py +++ b/bumble/transport/android_emulator.py @@ -16,7 +16,6 @@ # Imports # ----------------------------------------------------------------------------- import logging -from typing import Optional, Union import grpc.aio @@ -44,7 +43,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- -async def open_android_emulator_transport(spec: Optional[str]) -> Transport: +async def open_android_emulator_transport(spec: str | None) -> Transport: ''' Open a transport connection to an Android emulator via its gRPC interface. The parameter string has this syntax: @@ -89,7 +88,7 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport: logger.debug('connecting to gRPC server at %s', server_address) channel = grpc.aio.insecure_channel(server_address) - service: Union[EmulatedBluetoothServiceStub, VhciForwardingServiceStub] + service: EmulatedBluetoothServiceStub | VhciForwardingServiceStub if mode == 'host': # Connect as a host service = EmulatedBluetoothServiceStub(channel) diff --git a/bumble/transport/android_netsim.py b/bumble/transport/android_netsim.py index 4a6a210c..904c5767 100644 --- a/bumble/transport/android_netsim.py +++ b/bumble/transport/android_netsim.py @@ -22,7 +22,6 @@ import os import pathlib import platform import sys -from typing import Optional import grpc.aio @@ -66,7 +65,7 @@ DEFAULT_VARIANT = '' # ----------------------------------------------------------------------------- -def get_ini_dir() -> Optional[pathlib.Path]: +def get_ini_dir() -> pathlib.Path | None: if sys.platform == 'darwin': if tmpdir := os.getenv('TMPDIR', None): return pathlib.Path(tmpdir) @@ -100,7 +99,7 @@ def find_grpc_port(instance_number: int) -> int: ini_file = ini_dir / ini_file_name(instance_number) logger.debug(f'Looking for .ini file at {ini_file}') if ini_file.is_file(): - with open(ini_file, 'r') as ini_file_data: + with open(ini_file) as ini_file_data: for line in ini_file_data.readlines(): if '=' in line: key, value = line.split('=') @@ -146,7 +145,7 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool: # ----------------------------------------------------------------------------- async def open_android_netsim_controller_transport( - server_host: Optional[str], server_port: int, options: dict[str, str] + server_host: str | None, server_port: int, options: dict[str, str] ) -> Transport: if server_host == '_' or not server_host: server_host = 'localhost' @@ -301,9 +300,9 @@ async def open_android_netsim_controller_transport( # ----------------------------------------------------------------------------- async def open_android_netsim_host_transport_with_address( - server_host: Optional[str], + server_host: str | None, server_port: int, - options: Optional[dict[str, str]] = None, + options: dict[str, str] | None = None, ): if server_host == '_' or not server_host: server_host = 'localhost' @@ -328,7 +327,7 @@ async def open_android_netsim_host_transport_with_address( # ----------------------------------------------------------------------------- async def open_android_netsim_host_transport_with_channel( - channel, options: Optional[dict[str, str]] = None + channel, options: dict[str, str] | None = None ): # Wrapper for I/O operations class HciDevice: @@ -408,7 +407,7 @@ async def open_android_netsim_host_transport_with_channel( # ----------------------------------------------------------------------------- -async def open_android_netsim_transport(spec: Optional[str]) -> Transport: +async def open_android_netsim_transport(spec: str | None) -> Transport: ''' Open a transport connection as a client or server, implementing Android's `netsim` simulator protocol over gRPC. diff --git a/bumble/transport/common.py b/bumble/transport/common.py index 208241c2..594a66b5 100644 --- a/bumble/transport/common.py +++ b/bumble/transport/common.py @@ -23,7 +23,7 @@ import io import logging import struct from collections.abc import Awaitable, Callable -from typing import Any, ContextManager, Optional, Protocol +from typing import Any, Protocol from bumble import core, hci from bumble.colors import color @@ -107,11 +107,11 @@ class PacketParser: NEED_LENGTH = 1 NEED_BODY = 2 - sink: Optional[TransportSink] + sink: TransportSink | None extended_packet_info: dict[int, tuple[int, int, str]] - packet_info: Optional[tuple[int, int, str]] = None + packet_info: tuple[int, int, str] | None = None - def __init__(self, sink: Optional[TransportSink] = None) -> None: + def __init__(self, sink: TransportSink | None = None) -> None: self.sink = sink self.extended_packet_info = {} self.reset() @@ -176,7 +176,7 @@ class PacketReader: self.source = source self.at_end = False - def next_packet(self) -> Optional[bytes]: + def next_packet(self) -> bytes | None: # Get the packet type packet_type = self.source.read(1) if len(packet_type) != 1: @@ -253,7 +253,7 @@ class BaseSource: """ terminated: asyncio.Future[None] - sink: Optional[TransportSink] + sink: TransportSink | None def __init__(self) -> None: self.terminated = asyncio.get_running_loop().create_future() @@ -357,7 +357,7 @@ class Transport: # ----------------------------------------------------------------------------- class PumpedPacketSource(ParserSource): - pump_task: Optional[asyncio.Task[None]] + pump_task: asyncio.Task[None] | None def __init__(self, receive) -> None: super().__init__() @@ -390,7 +390,7 @@ class PumpedPacketSource(ParserSource): # ----------------------------------------------------------------------------- class PumpedPacketSink: - pump_task: Optional[asyncio.Task[None]] + pump_task: asyncio.Task[None] | None def __init__(self, send: Callable[[bytes], Awaitable[Any]]): self.send_function = send @@ -443,7 +443,7 @@ class SnoopingTransport(Transport): @staticmethod def create_with( - transport: Transport, snooper: ContextManager[Snooper] + transport: Transport, snooper: contextlib.AbstractContextManager[Snooper] ) -> SnoopingTransport: """ Create an instance given a snooper that works as as context manager. diff --git a/bumble/transport/file.py b/bumble/transport/file.py index 90e1be50..54aacbf2 100644 --- a/bumble/transport/file.py +++ b/bumble/transport/file.py @@ -16,7 +16,6 @@ # Imports # ----------------------------------------------------------------------------- import asyncio -import io import logging from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport @@ -36,7 +35,7 @@ async def open_file_transport(spec: str) -> Transport: ''' # Open the file - file = io.open(spec, 'r+b', buffering=0) + file = open(spec, 'r+b', buffering=0) # Setup reading read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe( diff --git a/bumble/transport/hci_socket.py b/bumble/transport/hci_socket.py index cc735239..536534b8 100644 --- a/bumble/transport/hci_socket.py +++ b/bumble/transport/hci_socket.py @@ -22,7 +22,6 @@ import logging import os import socket import struct -from typing import Optional from bumble.transport.common import ParserSource, Transport @@ -33,7 +32,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- -async def open_hci_socket_transport(spec: Optional[str]) -> Transport: +async def open_hci_socket_transport(spec: str | None) -> Transport: ''' Open an HCI Socket (only available on some platforms). The parameter string is either empty (to use the first/default Bluetooth adapter) @@ -87,7 +86,7 @@ async def open_hci_socket_transport(spec: Optional[str]) -> Transport: ) != 0 ): - raise IOError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) + raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) class HciSocketSource(ParserSource): def __init__(self, hci_socket): diff --git a/bumble/transport/pty.py b/bumble/transport/pty.py index 21611d8f..f29a0c5f 100644 --- a/bumble/transport/pty.py +++ b/bumble/transport/pty.py @@ -17,12 +17,10 @@ # ----------------------------------------------------------------------------- import asyncio import atexit -import io import logging import os import pty import tty -from typing import Optional from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport @@ -33,7 +31,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- -async def open_pty_transport(spec: Optional[str]) -> Transport: +async def open_pty_transport(spec: str | None) -> Transport: ''' Open a PTY transport. The parameter string may be empty, or a path name where a symbolic link @@ -48,11 +46,11 @@ async def open_pty_transport(spec: Optional[str]) -> Transport: tty.setraw(replica) read_transport, packet_source = await asyncio.get_running_loop().connect_read_pipe( - StreamPacketSource, io.open(primary, 'rb', closefd=False) + StreamPacketSource, open(primary, 'rb', closefd=False) ) write_transport, _ = await asyncio.get_running_loop().connect_write_pipe( - asyncio.BaseProtocol, io.open(primary, 'wb', closefd=False) + asyncio.BaseProtocol, open(primary, 'wb', closefd=False) ) packet_sink = StreamPacketSink(write_transport) diff --git a/bumble/transport/pyusb.py b/bumble/transport/pyusb.py index 360d85a2..00665d05 100644 --- a/bumble/transport/pyusb.py +++ b/bumble/transport/pyusb.py @@ -19,7 +19,6 @@ import asyncio import logging import threading import time -from typing import Optional import usb.core import usb.util @@ -389,7 +388,7 @@ def _set_port_status(device: UsbDevice, port: int, on: bool): ) -def _find_device_by_path(sys_path: str) -> Optional[UsbDevice]: +def _find_device_by_path(sys_path: str) -> UsbDevice | None: """Finds a USB device based on its system path.""" bus_num, *port_parts = sys_path.split('-') ports = [int(port) for port in port_parts[0].split('.')] @@ -402,7 +401,7 @@ def _find_device_by_path(sys_path: str) -> Optional[UsbDevice]: return None -def _find_hub_by_device_path(sys_path: str) -> Optional[UsbDevice]: +def _find_hub_by_device_path(sys_path: str) -> UsbDevice | None: """Finds the USB hub associated with a specific device path.""" hub_sys_path = sys_path.rsplit('.', 1)[0] hub_device = _find_device_by_path(hub_sys_path) diff --git a/bumble/transport/serial.py b/bumble/transport/serial.py index a812851b..dca498fd 100644 --- a/bumble/transport/serial.py +++ b/bumble/transport/serial.py @@ -17,7 +17,6 @@ # ----------------------------------------------------------------------------- import asyncio import logging -from typing import Optional import serial_asyncio @@ -52,7 +51,7 @@ class SerialPacketSource(StreamPacketSource): logger.debug('connection made') self._ready.set() - def connection_lost(self, exc: Optional[Exception]) -> None: + def connection_lost(self, exc: Exception | None) -> None: logger.debug('connection lost') self.on_transport_lost() diff --git a/bumble/transport/vhci.py b/bumble/transport/vhci.py index 8ae0d898..1143e8fc 100644 --- a/bumble/transport/vhci.py +++ b/bumble/transport/vhci.py @@ -16,7 +16,6 @@ # Imports # ----------------------------------------------------------------------------- import logging -from typing import Optional from bumble.transport.common import Transport from bumble.transport.file import open_file_transport @@ -28,7 +27,7 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- -async def open_vhci_transport(spec: Optional[str]) -> Transport: +async def open_vhci_transport(spec: str | None) -> Transport: ''' Open a VHCI transport (only available on some platforms). The parameter string is either empty (to use the default VHCI device diff --git a/bumble/transport/ws_server.py b/bumble/transport/ws_server.py index c2340907..d01b247a 100644 --- a/bumble/transport/ws_server.py +++ b/bumble/transport/ws_server.py @@ -16,7 +16,6 @@ # Imports # ----------------------------------------------------------------------------- import logging -from typing import Optional import websockets.asyncio.server @@ -43,8 +42,8 @@ async def open_ws_server_transport(spec: str) -> Transport: class WsServerTransport(Transport): sink: PumpedPacketSink source: ParserSource - connection: Optional[websockets.asyncio.server.ServerConnection] - server: Optional[websockets.asyncio.server.Server] + connection: websockets.asyncio.server.ServerConnection | None + server: websockets.asyncio.server.Server | None def __init__(self) -> None: source = ParserSource() diff --git a/bumble/utils.py b/bumble/utils.py index 115975ae..9966473d 100644 --- a/bumble/utils.py +++ b/bumble/utils.py @@ -23,14 +23,11 @@ import enum import functools import logging import warnings +from collections.abc import Awaitable, Callable from typing import ( Any, - Awaitable, - Callable, - Optional, Protocol, TypeVar, - Union, overload, ) @@ -169,8 +166,8 @@ class EventWatcher: ) -> _Handler: ... def on( - self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None - ) -> Union[_Handler, Callable[[_Handler], _Handler]]: + self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None + ) -> _Handler | Callable[[_Handler], _Handler]: '''Watch an event until the context is closed. Args: @@ -198,8 +195,8 @@ class EventWatcher: ) -> _Handler: ... def once( - self, emitter: pyee.EventEmitter, event: str, handler: Optional[_Handler] = None - ) -> Union[_Handler, Callable[[_Handler], _Handler]]: + self, emitter: pyee.EventEmitter, event: str, handler: _Handler | None = None + ) -> _Handler | Callable[[_Handler], _Handler]: '''Watch an event for once. Args: diff --git a/bumble/vendor/android/hci.py b/bumble/vendor/android/hci.py index fb49fadb..f6783b4a 100644 --- a/bumble/vendor/android/hci.py +++ b/bumble/vendor/android/hci.py @@ -18,7 +18,6 @@ import dataclasses import struct from dataclasses import field -from typing import Optional from bumble import hci @@ -209,7 +208,7 @@ class HCI_Android_Vendor_Event(hci.HCI_Extended_Event): @classmethod def subclass_from_parameters( cls, parameters: bytes - ) -> Optional[hci.HCI_Extended_Event]: + ) -> hci.HCI_Extended_Event | None: subevent_code = parameters[0] if subevent_code == HCI_BLUETOOTH_QUALITY_REPORT_EVENT: quality_report_id = parameters[1] diff --git a/examples/hid_report_parser.py b/examples/hid_report_parser.py index 434f8ba4..49136c59 100644 --- a/examples/hid_report_parser.py +++ b/examples/hid_report_parser.py @@ -46,7 +46,7 @@ class Keyboard: def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None: if report_length >= 8: modifier = input_report[1] - self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)] + self.report[0] = [int(x) for x in f'{modifier:08b}'] self.report[0].reverse() # type: ignore modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex()) @@ -106,7 +106,7 @@ class Mouse: ] def decode_mouse_report(self, input_report: bytes, report_length: int) -> None: - self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])] + self.report[0] = [int(x) for x in f'{input_report[1]:08b}'] self.report[0].reverse() # type: ignore self.report[1] = input_report[2] self.report[2] = input_report[3] diff --git a/examples/run_ams_client.py b/examples/run_ams_client.py index b58cb765..ef2f8186 100644 --- a/examples/run_ams_client.py +++ b/examples/run_ams_client.py @@ -49,9 +49,9 @@ async def handle_command_client( await ams_client.command(RemoteCommandId[command.upper()]) continue except Exception as error: - writer.write(f"ERROR: {error}\n".encode("utf-8")) + writer.write(f"ERROR: {error}\n".encode()) - writer.write(f"unknown command {command}\n".encode("utf-8")) + writer.write(f"unknown command {command}\n".encode()) # ----------------------------------------------------------------------------- diff --git a/examples/run_ancs_client.py b/examples/run_ancs_client.py index 8f6c2f77..6c5092ac 100644 --- a/examples/run_ancs_client.py +++ b/examples/run_ancs_client.py @@ -91,15 +91,13 @@ async def process_notifications(ancs_client: AncsClient): notification.notification_uid, requested_attributes ) max_attribute_name_width = max( - (len(attribute.attribute_id.name) for attribute in attributes) + len(attribute.attribute_id.name) for attribute in attributes ) app_identifier = str( next( - ( - attribute.value - for attribute in attributes - if attribute.attribute_id == NotificationAttributeId.APP_IDENTIFIER - ) + attribute.value + for attribute in attributes + if attribute.attribute_id == NotificationAttributeId.APP_IDENTIFIER ) ) if app_identifier not in _cached_app_names: @@ -145,9 +143,9 @@ async def handle_command_client( notification_uid = int(command_args) await ancs_client.perform_negative_action(notification_uid) else: - writer.write(f"unknown command {command_name}".encode("utf-8")) + writer.write(f"unknown command {command_name}".encode()) except Exception as error: - writer.write(f"ERROR: {error}\n".encode("utf-8")) + writer.write(f"ERROR: {error}\n".encode()) # ----------------------------------------------------------------------------- diff --git a/examples/run_asha_sink.py b/examples/run_asha_sink.py index 252de06e..4f53d94b 100644 --- a/examples/run_asha_sink.py +++ b/examples/run_asha_sink.py @@ -18,7 +18,6 @@ import asyncio import logging import sys -from typing import Optional import websockets.asyncio.server @@ -29,7 +28,7 @@ from bumble.device import AdvertisingParameters, Device from bumble.profiles import asha from bumble.transport import open_transport -ws_connection: Optional[websockets.asyncio.server.ServerConnection] = None +ws_connection: websockets.asyncio.server.ServerConnection | None = None g722_decoder = decoder.G722Decoder() diff --git a/examples/run_avrcp.py b/examples/run_avrcp.py index 21895fdc..94d2a6ba 100644 --- a/examples/run_avrcp.py +++ b/examples/run_avrcp.py @@ -21,7 +21,6 @@ import asyncio import json import logging import sys -from typing import Optional import websockets.asyncio.server @@ -218,7 +217,7 @@ def on_avrcp_start(avrcp_protocol: avrcp.Protocol, websocket_server: WebSocketSe # ----------------------------------------------------------------------------- class WebSocketServer: - socket: Optional[websockets.asyncio.server.ServerConnection] + socket: websockets.asyncio.server.ServerConnection | None def __init__( self, avrcp_protocol: avrcp.Protocol, avrcp_delegate: Delegate diff --git a/examples/run_extended_advertiser_2.py b/examples/run_extended_advertiser_2.py index defb968d..409aa6c8 100644 --- a/examples/run_extended_advertiser_2.py +++ b/examples/run_extended_advertiser_2.py @@ -52,7 +52,7 @@ async def main() -> None: if device.host.number_of_supported_advertising_sets >= 1: advertising_data1 = AdvertisingData( - [(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 1".encode("utf-8"))] + [(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 1")] ) set1 = await device.create_advertising_set( @@ -61,7 +61,7 @@ async def main() -> None: print("Selected TX power 1:", set1.selected_tx_power) advertising_data2 = AdvertisingData( - [(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 2".encode("utf-8"))] + [(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 2")] ) # pylint: disable=possibly-used-before-assignment @@ -78,7 +78,7 @@ async def main() -> None: if device.host.number_of_supported_advertising_sets >= 3: scan_response_data3 = AdvertisingData( - [(AdvertisingData.COMPLETE_LOCAL_NAME, "Bumble 3".encode("utf-8"))] + [(AdvertisingData.COMPLETE_LOCAL_NAME, b"Bumble 3")] ) set3 = await device.create_advertising_set( diff --git a/examples/run_gatt_client_and_server.py b/examples/run_gatt_client_and_server.py index f14f8910..092e6d1e 100644 --- a/examples/run_gatt_client_and_server.py +++ b/examples/run_gatt_client_and_server.py @@ -70,13 +70,13 @@ async def main() -> None: descriptor = Descriptor( GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR, Descriptor.READABLE, - 'My Description'.encode(), + b'My Description', ) manufacturer_name_characteristic = Characteristic[bytes]( GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC, Characteristic.Properties.READ, Characteristic.READABLE, - "Fitbit".encode(), + b"Fitbit", [descriptor], ) device_info_service = Service( diff --git a/examples/run_gatt_server.py b/examples/run_gatt_server.py index fcb2ac80..afc6c05a 100644 --- a/examples/run_gatt_server.py +++ b/examples/run_gatt_server.py @@ -93,13 +93,13 @@ async def main() -> None: descriptor = Descriptor( GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR, Descriptor.READABLE, - 'My Description'.encode(), + b'My Description', ) manufacturer_name_characteristic = Characteristic( GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC, Characteristic.Properties.READ, Characteristic.READABLE, - 'Fitbit'.encode(), + b'Fitbit', [descriptor], ) device_info_service = Service( diff --git a/examples/run_gatt_with_adapters.py b/examples/run_gatt_with_adapters.py index 9ec636df..81e2ca9a 100644 --- a/examples/run_gatt_with_adapters.py +++ b/examples/run_gatt_with_adapters.py @@ -24,7 +24,7 @@ import functools import random import struct import sys -from typing import Any, Union +from typing import Any import bumble.logging from bumble import core, gatt, gatt_adapters, gatt_client, hci, transport @@ -118,7 +118,7 @@ async def client(device: Device, address: hci.Address) -> None: c1 = characteristics[0] c1_value = await c1.read_value() print(f"@@@ C1 {c1} value = {c1_value!r} (type={type(c1_value)})") - await c1.write_value("happy π day".encode("utf-8")) + await c1.write_value("happy π day".encode()) await c1.subscribe(functools.partial(on_adapted_characteristic_update, c1)) # Static characteristic with a string value. @@ -187,7 +187,7 @@ async def client(device: Device, address: hci.Address) -> None: # ----------------------------------------------------------------------------- -def dynamic_read(selector: str) -> Union[bytes, str]: +def dynamic_read(selector: str) -> bytes | str: if selector == "bytes": print("$$$ Returning random bytes") return random.randbytes(7) diff --git a/examples/run_hfp_gateway.py b/examples/run_hfp_gateway.py index 90984671..b5253dfa 100644 --- a/examples/run_hfp_gateway.py +++ b/examples/run_hfp_gateway.py @@ -20,7 +20,7 @@ import io import json import logging import sys -from typing import Iterable, Optional +from collections.abc import Iterable import websockets.asyncio.server @@ -33,9 +33,9 @@ from bumble.transport import open_transport logger = logging.getLogger(__name__) -ws: Optional[websockets.asyncio.server.ServerConnection] = None -ag_protocol: Optional[hfp.AgProtocol] = None -source_file: Optional[io.BufferedReader] = None +ws: websockets.asyncio.server.ServerConnection | None = None +ag_protocol: hfp.AgProtocol | None = None +source_file: io.BufferedReader | None = None def _default_configuration() -> hfp.AgConfiguration: diff --git a/examples/run_hfp_handsfree.py b/examples/run_hfp_handsfree.py index ff0b6a16..33bd2137 100644 --- a/examples/run_hfp_handsfree.py +++ b/examples/run_hfp_handsfree.py @@ -20,7 +20,6 @@ import contextlib import functools import json import sys -from typing import Optional import websockets.asyncio.server @@ -30,8 +29,8 @@ from bumble.device import Connection, Device from bumble.hfp import HfProtocol from bumble.transport import open_transport -ws: Optional[websockets.asyncio.server.ServerConnection] = None -hf_protocol: Optional[HfProtocol] = None +ws: websockets.asyncio.server.ServerConnection | None = None +hf_protocol: HfProtocol | None = None # ----------------------------------------------------------------------------- diff --git a/examples/run_mcp_client.py b/examples/run_mcp_client.py index 5f688a90..01b06c4d 100644 --- a/examples/run_mcp_client.py +++ b/examples/run_mcp_client.py @@ -18,7 +18,6 @@ import asyncio import json import sys -from typing import Optional import websockets.asyncio.server @@ -101,8 +100,8 @@ async def main() -> None: ) device.add_service(AudioStreamControlService(device, sink_ase_id=[1])) - ws: Optional[websockets.asyncio.server.ServerConnection] = None - mcp: Optional[MediaControlServiceProxy] = None + ws: websockets.asyncio.server.ServerConnection | None = None + mcp: MediaControlServiceProxy | None = None advertising_data = bytes( AdvertisingData( diff --git a/examples/run_vcp_renderer.py b/examples/run_vcp_renderer.py index 263d480e..e14b63ee 100644 --- a/examples/run_vcp_renderer.py +++ b/examples/run_vcp_renderer.py @@ -19,7 +19,6 @@ import asyncio import json import secrets import sys -from typing import Optional import websockets.asyncio.server @@ -110,7 +109,7 @@ async def main() -> None: vcs = VolumeControlService() device.add_service(vcs) - ws: Optional[websockets.asyncio.server.ServerConnection] = None + ws: websockets.asyncio.server.ServerConnection | None = None def on_volume_state_change(): if ws: diff --git a/pyproject.toml b/pyproject.toml index cbc15c9a..2c6176ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,5 +214,8 @@ exclude = [ "bumble/transport/grpc_protobuf" ] +[tool.ruff.lint] +extend-select = ["UP"] + [tool.ruff.format] quote-style = "preserve" \ No newline at end of file diff --git a/tests/a2dp_test.py b/tests/a2dp_test.py index aedf138f..86382943 100644 --- a/tests/a2dp_test.py +++ b/tests/a2dp_test.py @@ -19,7 +19,7 @@ import asyncio import logging import os import struct -from typing import Awaitable +from collections.abc import Awaitable import pytest diff --git a/tests/hfp_test.py b/tests/hfp_test.py index 1282ceaa..19615a5d 100644 --- a/tests/hfp_test.py +++ b/tests/hfp_test.py @@ -18,7 +18,6 @@ import asyncio import logging import os -from typing import Optional import pytest import pytest_asyncio @@ -113,8 +112,8 @@ def _default_ag_sdp_features() -> hfp.AgSdpFeature: # ----------------------------------------------------------------------------- async def make_hfp_connections( - hf_config: Optional[hfp.HfConfiguration] = None, - ag_config: Optional[hfp.AgConfiguration] = None, + hf_config: hfp.HfConfiguration | None = None, + ag_config: hfp.AgConfiguration | None = None, ): if not hf_config: hf_config = _default_hf_configuration() diff --git a/tests/keystore_test.py b/tests/keystore_test.py index 35bb4191..9cc9966a 100644 --- a/tests/keystore_test.py +++ b/tests/keystore_test.py @@ -118,7 +118,7 @@ async def test_basic(temporary_file): assert foo.ltk is not None assert foo.ltk.value == ltk - with open(file.name, "r", encoding="utf-8") as json_file: + with open(file.name, encoding="utf-8") as json_file: json_data = json.load(json_file) assert 'my_namespace' in json_data assert 'foo' in json_data['my_namespace'] @@ -161,7 +161,7 @@ async def test_default_namespace(temporary_file): ltk = bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) keys.ltk = PairingKeys.Key(ltk) await keystore.update('foo', keys) - with open(file.name, "r", encoding="utf-8") as json_file: + with open(file.name, encoding="utf-8") as json_file: json_data = json.load(json_file) assert '__DEFAULT__' in json_data assert 'foo' in json_data['__DEFAULT__'] diff --git a/tests/l2cap_test.py b/tests/l2cap_test.py index 9e35a75a..689704c1 100644 --- a/tests/l2cap_test.py +++ b/tests/l2cap_test.py @@ -20,7 +20,6 @@ import itertools import logging import os import random -import struct from collections.abc import Sequence from unittest import mock diff --git a/tests/mcp_test.py b/tests/mcp_test.py index 6158c3d0..993bd52e 100644 --- a/tests/mcp_test.py +++ b/tests/mcp_test.py @@ -87,7 +87,7 @@ async def test_update_track_title(gmcs_context): await gmcs_context.devices[0].notify_subscribers( gmcs_context.server.track_title_characteristic, - value="My Song".encode(), + value=b"My Song", ) assert (await asyncio.wait_for(state.get(), TIMEOUT)) == "My Song" diff --git a/tests/smp_test.py b/tests/smp_test.py index 90df8096..f48c309b 100644 --- a/tests/smp_test.py +++ b/tests/smp_test.py @@ -16,7 +16,7 @@ # Imports # ----------------------------------------------------------------------------- -from typing import Any, Optional +from typing import Any from unittest import mock import pytest @@ -294,7 +294,7 @@ def test_link_key_to_ltk(ct2: bool, expected: str, crypto_backend: Any): ) @pytest.mark.asyncio async def test_send_identity_address_command( - identity_address_type: Optional[pairing.PairingConfig.AddressType], + identity_address_type: pairing.PairingConfig.AddressType | None, public_address: Address, random_address: Address, expected_identity_address: Address, diff --git a/tests/test_utils.py b/tests/test_utils.py index 86a43244..154ade6b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,7 +17,6 @@ # ----------------------------------------------------------------------------- import asyncio import functools -from typing import Optional from typing_extensions import Self @@ -32,7 +31,7 @@ from bumble.transport.common import AsyncPipeSink # ----------------------------------------------------------------------------- class Devices: - connections: list[Optional[Connection]] + connections: list[Connection | None] def __init__(self, num_devices: int) -> None: self.connections = [None for _ in range(num_devices)] diff --git a/tools/generate_company_id_list.py b/tools/generate_company_id_list.py index c4f96ada..819f19d3 100644 --- a/tools/generate_company_id_list.py +++ b/tools/generate_company_id_list.py @@ -27,7 +27,7 @@ import sys import yaml # ----------------------------------------------------------------------------- -with open(sys.argv[1], "r") as yaml_file: +with open(sys.argv[1]) as yaml_file: root = yaml.safe_load(yaml_file) companies = {} for company in root["company_identifiers"]: diff --git a/tools/intel_util.py b/tools/intel_util.py index 3d779871..539f3bc4 100644 --- a/tools/intel_util.py +++ b/tools/intel_util.py @@ -18,7 +18,7 @@ import asyncio # Imports # ----------------------------------------------------------------------------- import logging -from typing import Any, Optional +from typing import Any import click @@ -47,7 +47,7 @@ def print_device_info(device_info: dict[intel.ValueType, Any]) -> None: # ----------------------------------------------------------------------------- -async def get_driver(host: Host, force: bool) -> Optional[intel.Driver]: +async def get_driver(host: Host, force: bool) -> intel.Driver | None: # Create a driver driver = await intel.Driver.for_host(host, force) if driver is None: