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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
branches: [ master, develop ]
paths: [ 'bleak/**', 'tests/**' ]
workflow_dispatch:

jobs:
build_desktop:
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
`Unreleased`_
=============

Changed
-------
- `build_and_test.yml` workflow_dispatch trigger added
- Simplified conditional imports from `typing` with alternatives from `typing_extensions`
that resolves it under the hood, so we need no control it manually
- Undefined annotations fixed

Added
-----
- Added `_compat.py` module to simplify future support for other python implementations, like `pypy` or `circuitpython`

Added
-----
- Added support for Pythonista iOS app backend.
- Added ``BleakClient.name`` property for getting the peripheral's name. Fixes #1802.

Fixed
-----
- Fixed ``AttributeError`` in Python4Android backend when accessing ``is_connected`` before connecting. Fixes #1791.
Expand Down
19 changes: 5 additions & 14 deletions bleak/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,9 @@
from types import TracebackType
from typing import Any, Literal, Optional, TypedDict, Union, cast, overload

if sys.version_info < (3, 12):
from typing_extensions import Buffer
else:
from collections.abc import Buffer

if sys.version_info < (3, 11):
from async_timeout import timeout as async_timeout
from typing_extensions import Never, Self, Unpack, assert_never
else:
from asyncio import timeout as async_timeout
from typing import Never, Self, Unpack, assert_never
from typing_extensions import Buffer, Never, Self, Unpack, assert_never

from bleak._compat import async_timeout
from bleak.args.bluez import BlueZScannerArgs
from bleak.args.corebluetooth import CBScannerArgs, CBStartNotifyArgs
from bleak.args.winrt import WinRTClientArgs
Expand Down Expand Up @@ -60,7 +51,7 @@


# prevent tasks from being garbage collected
_background_tasks = set[asyncio.Task[None]]()
_background_tasks: set[asyncio.Task[None]] = set()


class BleakScanner:
Expand Down Expand Up @@ -170,7 +161,7 @@ async def advertisement_data(

.. versionadded:: 0.21
"""
devices = asyncio.Queue[tuple[BLEDevice, AdvertisementData]]()
devices: asyncio.Queue[tuple[BLEDevice, AdvertisementData]] = asyncio.Queue()

unregister_callback = self._backend.register_detection_callback(
lambda bd, ad: devices.put_nowait((bd, ad))
Expand Down Expand Up @@ -481,7 +472,7 @@ class BleakClient:
def __init__(
self,
address_or_ble_device: Union[BLEDevice, str],
disconnected_callback: Optional[Callable[[BleakClient], None]] = None,
disconnected_callback: Optional[Callable[["BleakClient"], None]] = None,
services: Optional[Iterable[str]] = None,
*,
timeout: float = 10.0,
Expand Down
57 changes: 57 additions & 0 deletions bleak/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import asyncio
import sys

if sys.implementation.name == "cpython":
if sys.version_info < (3, 11):
from async_timeout import timeout as async_timeout
else:
from asyncio import timeout as async_timeout

elif sys.implementation.name == "circuitpython":
# This code is preparing the bleak to be compatible with `circuitpython>=9`

if sys.implementation.version <= (9,):
raise NotImplementedError("Bleak does not support CircuitPython < 9")
else:

class _AsyncTimeout:
def __init__(self, timeout):
if sys.implementation.name != "circuitpython":
print(
"Warning: This timeout manager is for CircuitPython. "
"Use the native `asyncio.timeout` on standard Python."
)
self.timeout = timeout
self._task = None

async def __aenter__(self):
self._task = asyncio.create_task(asyncio.sleep(self.timeout))
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
if self._task and not self._task.done():
self._task.cancel()
try:
await self._task
except asyncio.CancelledError:
pass

if exc_type is asyncio.CancelledError and self._task.done():
if self._task.cancelled():
raise asyncio.TimeoutError("Operation timed out") from exc_val

# If any other exception occurred, let it propagate.
return False

async_timeout = _AsyncTimeout

elif sys.implementation.name == "pypy":
raise NotImplementedError("Unsupported Python implementation: pypy")

else:
# can't use on pypy for example
raise NotImplementedError(
f"Unsupported Python implementation: {sys.implementation.name}"
)

assert async_timeout # Ensure we have async_timeout defined
8 changes: 2 additions & 6 deletions bleak/backends/bluezdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@
from contextlib import AsyncExitStack
from typing import Any, Optional, Union

if sys.version_info < (3, 12):
from typing_extensions import Buffer, override
else:
from collections.abc import Buffer
from typing import override
from typing_extensions import Buffer, override

if sys.version_info < (3, 11):
from async_timeout import timeout as async_timeout
Expand Down Expand Up @@ -49,7 +45,7 @@
logger = logging.getLogger(__name__)

# prevent tasks from being garbage collected
_background_tasks = set[asyncio.Task[None]]()
_background_tasks: set[asyncio.Task[None]] = set()


class BleakClientBlueZDBus(BaseBleakClient):
Expand Down
6 changes: 1 addition & 5 deletions bleak/backends/bluezdbus/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
from typing import Any, Literal, Optional
from warnings import warn

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

from dbus_fast import Variant
from typing_extensions import override

from bleak.args.bluez import BlueZDiscoveryFilters as _BlueZDiscoveryFilters
from bleak.args.bluez import BlueZScannerArgs as _BlueZScannerArgs
Expand Down
5 changes: 1 addition & 4 deletions bleak/backends/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
from collections.abc import Callable
from typing import Any, Optional, Union

if sys.version_info < (3, 12):
from typing_extensions import Buffer
else:
from collections.abc import Buffer
from typing_extensions import Buffer

from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.descriptor import BleakGATTDescriptor
Expand Down
7 changes: 1 addition & 6 deletions bleak/backends/corebluetooth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
import logging
from typing import Any, Optional, Union

if sys.version_info < (3, 12):
from typing_extensions import Buffer, override
else:
from collections.abc import Buffer
from typing import override

from CoreBluetooth import (
CBUUID,
CBCharacteristicWriteWithoutResponse,
Expand All @@ -28,6 +22,7 @@
CBPeripheralStateConnected,
)
from Foundation import NSArray, NSData
from typing_extensions import Buffer, override

from bleak import BleakScanner
from bleak.args.corebluetooth import CBStartNotifyArgs
Expand Down
6 changes: 1 addition & 5 deletions bleak/backends/corebluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@
from typing import Any, Literal, Optional
from warnings import warn

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

import objc
from CoreBluetooth import CBPeripheral
from Foundation import NSBundle, NSDictionary
from typing_extensions import override

from bleak.args.corebluetooth import CBScannerArgs as _CBScannerArgs
from bleak.backends.corebluetooth.CentralManagerDelegate import CentralManagerDelegate
Expand Down
6 changes: 1 addition & 5 deletions bleak/backends/p4android/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
import warnings
from typing import Any, Optional, Union

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

from android.broadcast import BroadcastReceiver
from jnius import java_method
from typing_extensions import override

from bleak.assigned_numbers import gatt_char_props_to_strs
from bleak.backends.characteristic import BleakGATTCharacteristic
Expand Down
7 changes: 2 additions & 5 deletions bleak/backends/p4android/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@
import warnings
from typing import Literal, Optional

from typing_extensions import override

if sys.version_info < (3, 11):
from async_timeout import timeout as async_timeout
else:
from asyncio import timeout as async_timeout

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

from android.broadcast import BroadcastReceiver
from android.permissions import Permission, request_permissions
from jnius import cast, java_method
Expand Down
2 changes: 1 addition & 1 deletion bleak/backends/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from bleak.exc import BleakError

# prevent tasks from being garbage collected
_background_tasks = set[asyncio.Task[None]]()
_background_tasks: set[asyncio.Task[None]] = set()


class AdvertisementData(NamedTuple):
Expand Down
8 changes: 1 addition & 7 deletions bleak/backends/winrt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,12 @@
from typing import Any, Generic, Optional, Protocol, Sequence, TypeVar, Union, cast
from warnings import warn

if sys.version_info < (3, 12):
from typing_extensions import Buffer, override
else:
from collections.abc import Buffer
from typing import override
from typing_extensions import Buffer, Self, assert_never, override

if sys.version_info < (3, 11):
from async_timeout import timeout as async_timeout
from typing_extensions import Self, assert_never
else:
from asyncio import timeout as async_timeout
from typing import Self, assert_never

from winrt.system import Object
from winrt.windows.devices.bluetooth import (
Expand Down
6 changes: 1 addition & 5 deletions bleak/backends/winrt/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
from typing import Literal, NamedTuple, Optional
from uuid import UUID

if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override

from typing_extensions import override
from winrt.windows.devices.bluetooth.advertisement import (
BluetoothLEAdvertisementReceivedEventArgs,
BluetoothLEAdvertisementType,
Expand Down
10 changes: 1 addition & 9 deletions typings/Foundation/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ import sys
from collections.abc import Iterator, Mapping, Sequence
from typing import Any, NewType, Optional, TypeVar, overload

if sys.version_info < (3, 12):
from typing_extensions import Buffer
else:
from collections.abc import Buffer

if sys.version_info < (3, 11):
from typing_extensions import Self
else:
from typing import Self
from typing_extensions import Buffer, Self

TNSObject = TypeVar("TNSObject", bound=NSObject)

Expand Down
Loading