From 343f307e324d5aaf91dc1b85c8be847a812dc4dd Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 29 Oct 2022 10:09:44 -0500 Subject: [PATCH 01/45] bump version for develop --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 16e6fddb..096c015a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bleak" -version = "0.19.1" +version = "0.20.0a1" description = "Bluetooth Low Energy platform Agnostic Klient" authors = ["Henrik Blidh "] license = "MIT" From aafa09223c152dbe0194f75dece75d6b6bd32f51 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 29 Oct 2022 10:13:12 -0500 Subject: [PATCH 02/45] github: bump actions versions Older versions are showing deprecation warnings because they use nodejs v12. --- .github/workflows/build_and_test.yml | 2 +- .github/workflows/build_android.yml | 6 +++--- .github/workflows/format_and_lint.yml | 4 ++-- .github/workflows/pypi-publish.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a0feb3f3..8378f843 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,7 +15,7 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 id: py diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml index 912ced83..cc8798f9 100644 --- a/.github/workflows/build_android.yml +++ b/.github/workflows/build_android.yml @@ -5,13 +5,13 @@ on: workflow_dispatch jobs: build_android: name: "Build Android" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: pip install buildozer cython - name: Cache buildozer files - uses: actions/cache@v2 + uses: actions/cache@v3 id: buildozer-cache with: path: | diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index 14ca0402..01c66de4 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -11,9 +11,9 @@ jobs: name: "Format and lint" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install development dependencies run: pipx run poetry install --only docs,lint - name: Check code formatting with black diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 6fb9c523..d3009cc1 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build run: pipx run poetry build - name: Publish From 359e9b0017cc1ca04c17a6eae622971bc4a1eb0c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 31 Oct 2022 15:56:24 -0500 Subject: [PATCH 03/45] winrt/client: fix crash when getting services Python 3.11 crashes on Windows when connecting to a device: Traceback (most recent call last): File "C:\Users\david\Documents\GitHub\Pybricks\bleak\examples\service_explorer.py", line 64, in asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else ADDRESS)) File "C:\Users\david\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "C:\Users\david\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 118, in run return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\david\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 650, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "C:\Users\david\Documents\GitHub\Pybricks\bleak\examples\service_explorer.py", line 29, in main async with BleakClient(address) as client: File "C:\Users\david\Documents\GitHub\Pybricks\bleak\bleak\__init__.py", line 433, in __aenter__ await self.connect() File "C:\Users\david\Documents\GitHub\Pybricks\bleak\bleak\__init__.py", line 471, in connect return await self._backend.connect(**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await self.get_services() File "C:\Users\david\Documents\GitHub\Pybricks\bleak\bleak\backends\winrt\client.py", line 574, in get_services File "C:\Users\david\AppData\Local\Programs\Python\Python311\Lib\asyncio\tasks.py", line 418, in wait return await _wait(fs, timeout, return_when, loop) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\david\AppData\Local\Programs\Python\Python311\Lib\asyncio\tasks.py", line 522, in _wait f.add_done_callback(_on_completion) ^^^^^^^^^^^^^^^^^^^ AttributeError: '_bleak_winrt_Windows_Foundation.IAsyncOperation' object has no attribute 'add_done_callback' asyncio.wait() requires an asyncio.Task or asyncio.Future, but we were passing a WinRT IAsyncResult object. So we have to wrap it in a coroutine and then in a task to avoid the error. --- CHANGELOG.rst | 4 ++++ bleak/backends/winrt/client.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 529c2267..c99b76d8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ and this project adheres to `Semantic Versioning BleakGATTServiceCollection: services_changed_event.wait() ) self._services_changed_events.append(services_changed_event) - get_services_task = self._requester.get_gatt_services_async(*args) + + async def get_services(): + return await self._requester.get_gatt_services_async(*args) + + get_services_task = asyncio.create_task(get_services()) try: await asyncio.wait( @@ -590,7 +594,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: args = [BluetoothCacheMode.UNCACHED] services: Sequence[GattDeviceService] = _ensure_success( - get_services_task.get_results(), + get_services_task.result(), "services", "Could not get GATT services", ) From 50057cccacc26feccfa63cf5886fcf6f0c74debd Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 31 Oct 2022 16:32:09 -0500 Subject: [PATCH 04/45] winrt/client: change get services retry cache mode This changes the cache mode when getting services after a ServicesChanged event in the WinRT backend. This matches the recommendation of the docs. https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice.gattserviceschanged?view=winrt-20348 --- CHANGELOG.rst | 1 + bleak/backends/winrt/client.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c99b76d8..d36919f2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ and this project adheres to `Semantic Versioning Date: Mon, 31 Oct 2022 20:31:55 -0500 Subject: [PATCH 05/45] winrt/client: add FutureLike wrapper for IAsyncOperation The fix from #1101 causes the following when run with `python3.10 -X dev`. ERROR:asyncio:Exception in callback Future.set_result(, <_bleak_winrt...001C33C60B100>) handle: , <_bleak_winrt...001C33C60B100>)> Traceback (most recent call last): File "C:\Users\david\AppData\Local\Programs\Python\Python310\lib\asyncio\events.py", line 80, in _run self._context.run(self._callback, *self._args) asyncio.exceptions.InvalidStateError: invalid state ERROR:asyncio:Exception in callback Future.set_result(, <_bleak_winrt...001C33C60BFC0>) handle: , <_bleak_winrt...001C33C60BFC0>)> Traceback (most recent call last): File "C:\Users\david\AppData\Local\Programs\Python\Python310\lib\asyncio\events.py", line 80, in _run self._context.run(self._callback, *self._args) asyncio.exceptions.InvalidStateError: invalid state This adds a new FutureLike wrapper to make the IAsyncOperation object look like an asyncio.Future instead of wrapping it in a task. --- bleak/backends/winrt/client.py | 78 +++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 07b495f9..f6072dd4 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -6,10 +6,12 @@ """ import asyncio +import functools import logging import sys import uuid import warnings +from ctypes import pythonapi from typing import Any, Dict, List, Optional, Sequence, Union, cast import async_timeout @@ -44,11 +46,15 @@ DevicePairingResultStatus, DeviceUnpairingResultStatus, ) -from bleak_winrt.windows.foundation import EventRegistrationToken +from bleak_winrt.windows.foundation import ( + AsyncStatus, + EventRegistrationToken, + IAsyncOperation, +) from bleak_winrt.windows.storage.streams import Buffer from ... import BleakScanner -from ...exc import PROTOCOL_ERROR_CODES, BleakError, BleakDeviceNotFoundError +from ...exc import PROTOCOL_ERROR_CODES, BleakDeviceNotFoundError, BleakError from ..characteristic import BleakGATTCharacteristic from ..client import BaseBleakClient, NotifyCallback from ..device import BLEDevice @@ -569,10 +575,9 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) self._services_changed_events.append(services_changed_event) - async def get_services(): - return await self._requester.get_gatt_services_async(*args) - - get_services_task = asyncio.create_task(get_services()) + get_services_task = FutureLike( + self._requester.get_gatt_services_async(*args) + ) try: await asyncio.wait( @@ -889,3 +894,64 @@ async def stop_notify( event_handler_token = self._notification_callbacks.pop(characteristic.handle) characteristic.obj.remove_value_changed(event_handler_token) + + +class FutureLike: + """ + Wraps a WinRT IAsyncOperation in a "future-like" object so that it can + be passed to Python APIs. + + Needed until https://github.com/pywinrt/pywinrt/issues/14 + """ + + _asyncio_future_blocking = True + + def __init__(self, async_result: IAsyncOperation) -> None: + self._async_result = async_result + self._callbacks = [] + self._loop = asyncio.get_running_loop() + + def call_callbacks(op: IAsyncOperation, status: AsyncStatus): + for c in self._callbacks: + c(self) + + async_result.completed = functools.partial( + self._loop.call_soon_threadsafe, call_callbacks + ) + + def result(self) -> Any: + return self._async_result.get_results() + + def done(self) -> bool: + return self._async_result.status != AsyncStatus.STARTED + + def cancelled(self) -> bool: + return self._async_result.status == AsyncStatus.CANCELED + + def add_done_callback(self, callback, *, context=None) -> None: + self._callbacks.append(callback) + + def remove_done_callback(self, callback) -> None: + self._callbacks.remove(callback) + + def cancel(self, msg=None) -> bool: + if self._async_result.status != AsyncStatus.STARTED: + return False + self._async_result.cancel() + return True + + def exception(self) -> Optional[Exception]: + if self._async_result.status == AsyncStatus.STARTED: + raise asyncio.InvalidStateError + if self._async_result.status == AsyncStatus.COMPLETED: + return None + if self._async_result.status == AsyncStatus.CANCELED: + raise asyncio.CancelledError + if self._async_result.status == AsyncStatus.ERROR: + try: + pythonapi.PyErr_SetFromWindowsErr(self._async_result.error_code) + except OSError as e: + return e + + def get_loop(self) -> asyncio.AbstractEventLoop: + return self._loop From b1a1bd64fc399143511d0a13461b35e091caa184 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 1 Nov 2022 13:26:37 -0500 Subject: [PATCH 06/45] winrt/client: fix logger.warn deprecation warning When run with python3.10 -dev X, we get a warning that warn is deprecated and we should replace it with warning. --- bleak/backends/winrt/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index f6072dd4..64659804 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -256,7 +256,7 @@ async def connect(self, **kwargs) -> bool: def handle_services_changed(): if not self._services_changed_events: - logger.warn("%s: unhandled services changed event", self.address) + logger.warning("%s: unhandled services changed event", self.address) else: for event in self._services_changed_events: event.set() From 0d3fa86574cdf499ccf6ea7d4d3dc69223f82cfb Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 3 Nov 2022 10:40:00 -0500 Subject: [PATCH 07/45] bluezdbus/manager: ignore KeyError when properties removed Apparently, BlueZ will sometimes request to remove properties that were not set in the first place. Fixes: #1107 --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/manager.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d36919f2..8d8b3202 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Fixed ------ * Fixed crash when getting services in WinRT backend. * Fixed cache mode when retrying get services in WinRT backend. +* Fixed ``KeyError`` crash in BlueZ backend when removing non-existent property. Fixes #1107. `0.19.1`_ (2022-10-29) ====================== diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index 0ccf6f39..8ee6e5d0 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -818,7 +818,12 @@ def _parse_msg(self, message: Message): self_interface.update(unpack_variants(changed)) for name in invalidated: - del self_interface[name] + try: + del self_interface[name] + except KeyError: + # sometimes there BlueZ tries to remove properties + # that were never added + pass # then call any callbacks so they will be called with the # updated state From 2df2678a83bb7e469faea8f99a2f9e3d34bc19b2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 1 Nov 2022 13:37:55 -0500 Subject: [PATCH 08/45] pyproject: drop async-timeout dependency on Python >= 3.11 Python 3.11 introduced a new `asyncio.timeout()` method that can be used instead of the `async_timeout` package. --- CHANGELOG.rst | 4 ++++ bleak/__init__.py | 7 +++++-- bleak/backends/bluezdbus/client.py | 13 +++++++++---- .../corebluetooth/CentralManagerDelegate.py | 9 +++++++-- bleak/backends/corebluetooth/PeripheralDelegate.py | 9 +++++++-- bleak/backends/p4android/scanner.py | 8 ++++++-- bleak/backends/winrt/client.py | 9 ++++++--- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 9 files changed, 49 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8d8b3202..be63bc9d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ and this project adheres to `Semantic Versioning = 3.11. + Fixed ------ * Fixed crash when getting services in WinRT backend. diff --git a/bleak/__init__.py b/bleak/__init__.py index c8346292..f2c49807 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -28,7 +28,10 @@ ) from warnings import warn -import async_timeout +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[:2] < (3, 8): from typing_extensions import Literal @@ -322,7 +325,7 @@ def apply_filter(d: BLEDevice, ad: AdvertisementData): async with cls(detection_callback=apply_filter, **kwargs): try: - async with async_timeout.timeout(timeout): + async with async_timeout(timeout): return await found_device_queue.get() except asyncio.TimeoutError: return None diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index ff374599..6c419f22 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -5,11 +5,16 @@ import asyncio import logging import os +import sys import warnings from typing import Callable, Dict, Optional, Union, cast from uuid import UUID -import async_timeout +if sys.version_info < (3, 11): + from async_timeout import timeout as async_timeout +else: + from asyncio import timeout as async_timeout + from dbus_fast.aio import MessageBus from dbus_fast.constants import BusType, ErrorType, MessageType from dbus_fast.message import Message @@ -168,7 +173,7 @@ def on_value_changed(char_path: str, value: bytes) -> None: # if not manager.is_connected(self._device_path): logger.debug("Connecting to BlueZ path %s", self._device_path) - async with async_timeout.timeout(timeout): + async with async_timeout(timeout): reply = await self._bus.call( Message( destination=defs.BLUEZ_SERVICE, @@ -329,7 +334,7 @@ async def disconnect(self) -> bool: if self._disconnecting_event: # another call to disconnect() is already in progress logger.debug(f"already in progress ({self._device_path})") - async with async_timeout.timeout(10): + async with async_timeout(10): await self._disconnecting_event.wait() elif self.is_connected: self._disconnecting_event = asyncio.Event() @@ -344,7 +349,7 @@ async def disconnect(self) -> bool: ) ) assert_reply(reply) - async with async_timeout.timeout(10): + async with async_timeout(10): await self._disconnecting_event.wait() finally: self._disconnecting_event = None diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index cce9eacb..42726f30 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -8,10 +8,15 @@ import asyncio import logging +import sys import threading from typing import Any, Callable, Dict, Optional -import async_timeout +if sys.version_info < (3, 11): + from async_timeout import timeout as async_timeout +else: + from asyncio import timeout as async_timeout + import objc from CoreBluetooth import ( CBCentralManager, @@ -156,7 +161,7 @@ async def connect( self._connect_futures[peripheral.identifier()] = future try: self.central_manager.connectPeripheral_options_(peripheral, None) - async with async_timeout.timeout(timeout): + async with async_timeout(timeout): await future finally: del self._connect_futures[peripheral.identifier()] diff --git a/bleak/backends/corebluetooth/PeripheralDelegate.py b/bleak/backends/corebluetooth/PeripheralDelegate.py index 3fa90c02..02ca39a4 100644 --- a/bleak/backends/corebluetooth/PeripheralDelegate.py +++ b/bleak/backends/corebluetooth/PeripheralDelegate.py @@ -9,9 +9,14 @@ import asyncio import itertools import logging +import sys from typing import Any, Dict, Iterable, NewType, Optional -import async_timeout +if sys.version_info < (3, 11): + from async_timeout import timeout as async_timeout +else: + from asyncio import timeout as async_timeout + import objc from Foundation import NSNumber, NSObject, NSArray, NSData, NSError, NSUUID, NSString from CoreBluetooth import ( @@ -146,7 +151,7 @@ async def read_characteristic( self._characteristic_read_futures[characteristic.handle()] = future try: self.peripheral.readValueForCharacteristic_(characteristic) - async with async_timeout.timeout(timeout): + async with async_timeout(timeout): return await future finally: del self._characteristic_read_futures[characteristic.handle()] diff --git a/bleak/backends/p4android/scanner.py b/bleak/backends/p4android/scanner.py index 5fe4a0dc..609e5846 100644 --- a/bleak/backends/p4android/scanner.py +++ b/bleak/backends/p4android/scanner.py @@ -6,12 +6,16 @@ import warnings from typing import List, Optional +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[:2] < (3, 8): from typing_extensions import Literal else: from typing import Literal -import async_timeout from android.broadcast import BroadcastReceiver from android.permissions import Permission, request_permissions from jnius import cast, java_method @@ -137,7 +141,7 @@ def handle_permissions(permissions, grantResults): self.__javascanner.flushPendingScanResults(self.__callback.java) try: - async with async_timeout.timeout(0.2): + async with async_timeout(0.2): await scanfuture except asyncio.exceptions.TimeoutError: pass diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 64659804..f8f32577 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -14,7 +14,10 @@ from ctypes import pythonapi from typing import Any, Dict, List, Optional, Sequence, Union, cast -import async_timeout +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[:2] < (3, 8): from typing_extensions import Literal, TypedDict @@ -367,7 +370,7 @@ def max_pdu_size_changed_handler(sender: GattSession, args): # This keeps the device connected until we set maintain_connection = False. # wait for the session to become active - async with async_timeout.timeout(timeout): + async with async_timeout(timeout): await event.wait() except BaseException: handle_disconnect() @@ -416,7 +419,7 @@ async def disconnect(self) -> bool: self._requester.close() # sometimes it can take over one minute before Windows decides # to end the GATT session/disconnect the device - async with async_timeout.timeout(120): + async with async_timeout(120): await event.wait() finally: self._session_closed_events.remove(event) diff --git a/poetry.lock b/poetry.lock index 0800aa05..0e70401e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -37,7 +37,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "Babel" @@ -98,7 +98,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "click" @@ -457,7 +457,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "snowballstemmer" @@ -637,7 +637,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "de7999d44b4fa4c454de85be9523558c9d375983122f54d97a97440fb25e558a" +content-hash = "a8845e45f46b310de561b52d1f17d3c6309c28513c16ac25a50ce751c1a6a116" [metadata.files] alabaster = [ diff --git a/pyproject.toml b/pyproject.toml index 096c015a..930bf624 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.7" -async-timeout = ">= 3.0.0, < 5" +async-timeout = { version = ">= 3.0.0, < 5", python = "<3.11" } typing-extensions = { version = "^4.2.0", python = "<3.8" } pyobjc-core = { version = "^8.5.1", markers = "platform_system=='Darwin'" } pyobjc-framework-CoreBluetooth = { version = "^8.5.1", markers = "platform_system=='Darwin'" } From bcae93710f10da2be335b10c95b30cb2c789658a Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 6 Nov 2022 15:30:39 -0600 Subject: [PATCH 09/45] backends/device: deprecate BLEDevice.rssi and .metadata (#1059) This make a clean separation between the BLEDevice which is the OS handle for a "known" BLE device and the advertising data for that device. There are now alternatives in place for getting advertising data paired with the BLEDevice so we can deprecate these attributes. Fixes #1025. --- CHANGELOG.rst | 1 + bleak/backends/device.py | 66 ++++++++++++++++++++++++++++----------- bleak/backends/scanner.py | 3 +- examples/kivy/main.py | 2 -- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 435daa9d..b81a90d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ and this project adheres to `Semantic Versioning = 3.11. +* Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. `0.19.2`_ (2022-11-06) ====================== diff --git a/bleak/backends/device.py b/bleak/backends/device.py index 6ef93f0b..f85ed6ee 100644 --- a/bleak/backends/device.py +++ b/bleak/backends/device.py @@ -8,31 +8,61 @@ """ +from typing import Any, Optional +from warnings import warn + + class BLEDevice: - """A simple wrapper class representing a BLE server detected during - a `discover` call. - - - When using Windows backend, `details` attribute is a - ``Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisement`` object, unless - it is created with the Windows.Devices.Enumeration discovery method, then is is a - ``Windows.Devices.Enumeration.DeviceInformation``. - - When using Linux backend, ``details`` attribute is a - dict with keys ``path`` which has the string path to the DBus device object and ``props`` - which houses the properties dictionary of the D-Bus Device. - - When using macOS backend, ``details`` attribute will be a CBPeripheral object. + """ + A simple wrapper class representing a BLE server detected during scanning. """ - def __init__(self, address, name=None, details=None, rssi=0, **kwargs): - #: The Bluetooth address of the device on this machine. + def __init__( + self, address: str, name: Optional[str], details: Any, rssi: int, **kwargs + ): + #: The Bluetooth address of the device on this machine (UUID on macOS). self.address = address - #: The advertised name of the device. + #: The operating system name of the device (not necessarily the local name + #: from the advertising data), suitable for display to the user. self.name = name #: The OS native details required for connecting to the device. self.details = details - #: RSSI, if available - self.rssi = rssi - #: Device specific details. Contains a ``uuids`` key which is a list of service UUIDs and a ``manufacturer_data`` field with a bytes-object from the advertised data. - self.metadata = kwargs + + # for backwards compatibility + self._rssi = rssi + self._metadata = kwargs + + @property + def rssi(self) -> int: + """ + Gets the RSSI of the last received advertisement. + + .. deprecated:: 0.19.0 + Use :class:`AdvertisementData` from detection callback or + :attr:`BleakScanner.discovered_devices_and_advertisement_data` instead. + """ + warn( + "BLEDevice.rssi is deprecated and will be removed in a future version of Bleak, use AdvertisementData.rssi instead", + FutureWarning, + stacklevel=2, + ) + return self._rssi + + @property + def metadata(self) -> dict: + """ + Gets additional advertisement data for the device. + + .. deprecated:: 0.19.0 + Use :class:`AdvertisementData` from detection callback or + :attr:`BleakScanner.discovered_devices_and_advertisement_data` instead. + """ + warn( + "BLEDevice.metadata is deprecated and will be removed in a future version of Bleak, use AdvertisementData instead", + FutureWarning, + stacklevel=2, + ) + return self._metadata def __str__(self): return f"{self.address}: {self.name}" diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py index 664b4f67..359c6ce5 100644 --- a/bleak/backends/scanner.py +++ b/bleak/backends/scanner.py @@ -197,7 +197,8 @@ def create_or_update_device( try: device, _ = self.seen_devices[address] - device.metadata = metadata + device._rssi = adv.rssi + device._metadata = metadata except KeyError: device = BLEDevice( address, diff --git a/examples/kivy/main.py b/examples/kivy/main.py index 2be33704..c90e340a 100644 --- a/examples/kivy/main.py +++ b/examples/kivy/main.py @@ -49,8 +49,6 @@ async def example(self): if len(scanned_devices) == 0: raise bleak.exc.BleakError("no devices found") - scanned_devices.sort(key=lambda device: -device.rssi) - for device in scanned_devices: self.line(f"{device.name} ({device.address})") From 7337591027486dc44f0a5d435ab1ffafd199dff5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 6 Nov 2022 16:13:52 -0600 Subject: [PATCH 10/45] Reduce memory usage of BLEDevice with __slots__ Not done for AdvertisementData since NamedTuple already uses __slots__ under the hood --- CHANGELOG.rst | 1 + bleak/backends/device.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b81a90d1..bbd91359 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Changed ------- * Dropped ``async-timeout`` dependency on Python >= 3.11. * Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. +* ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. `0.19.2`_ (2022-11-06) ====================== diff --git a/bleak/backends/device.py b/bleak/backends/device.py index f85ed6ee..5ce5c89a 100644 --- a/bleak/backends/device.py +++ b/bleak/backends/device.py @@ -17,6 +17,8 @@ class BLEDevice: A simple wrapper class representing a BLE server detected during scanning. """ + __slots__ = ("address", "name", "details", "_rssi", "_metadata") + def __init__( self, address: str, name: Optional[str], details: Any, rssi: int, **kwargs ): From 96ced27f82e9b166834a8f91e1d1103fe09719d6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sun, 6 Nov 2022 23:45:36 -0600 Subject: [PATCH 11/45] CHANGELOG: fix bad merge --- CHANGELOG.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 049e4f09..cda3f51e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,12 @@ and this project adheres to `Semantic Versioning = 3.11. +* Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. +* ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. + `0.19.4`_ (2022-11-06) ====================== @@ -27,12 +33,6 @@ Fixed * Fixed ``TimeoutError`` when connecting to certain devices with WinRT backend. Fixes #604. -Changed -------- -* Dropped ``async-timeout`` dependency on Python >= 3.11. -* Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. -* ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. - `0.19.2`_ (2022-11-06) ====================== From 049eb5e4a181bf99d321891156889172c2219c08 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 7 Oct 2022 12:53:08 -0500 Subject: [PATCH 12/45] backends/corebluetooth/scanner: add use_bdaddr option This adds a CoreBluetooth-specific option `use_bdaddr` to BleakScanner. When enabled, the BLEDevice objects will use the actual Bluetooth address instead of the UUID identifier provided by the CoreBluetooth APIs. This uses an undocumented CBCentralManager method to get the address so could stop working in future macOS releases. --- CHANGELOG.rst | 4 +++ bleak/__init__.py | 11 ++++++++- bleak/backends/corebluetooth/scanner.py | 33 ++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3e3d7e1e..644233e4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ and this project adheres to `Semantic Versioning = 3.11. diff --git a/bleak/__init__.py b/bleak/__init__.py index f2c49807..28cb9f07 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -53,6 +53,7 @@ if TYPE_CHECKING: from .backends.bluezdbus.scanner import BlueZScannerArgs + from .backends.corebluetooth.scanner import CBScannerArgs from .backends.winrt.client import WinRTClientArgs @@ -92,6 +93,8 @@ class BleakScanner: :class:`BleakError` if set to ``"passive"`` on macOS. bluez: Dictionary of arguments specific to the BlueZ backend. + cb: + Dictionary of arguments specific to the CoreBluetooth backend. backend: Used to override the automatically selected backend (i.e. for a custom backend). @@ -114,6 +117,7 @@ def __init__( scanning_mode: Literal["active", "passive"] = "active", *, bluez: BlueZScannerArgs = {}, + cb: CBScannerArgs = {}, backend: Optional[Type[BaseBleakScanner]] = None, **kwargs, ): @@ -122,7 +126,12 @@ def __init__( ) self._backend = PlatformBleakScanner( - detection_callback, service_uuids, scanning_mode, bluez=bluez, **kwargs + detection_callback, + service_uuids, + scanning_mode, + bluez=bluez, + cb=cb, + **kwargs, ) async def __aenter__(self): diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py index 3002dae1..bfcd2e30 100644 --- a/bleak/backends/corebluetooth/scanner.py +++ b/bleak/backends/corebluetooth/scanner.py @@ -3,9 +3,9 @@ from typing import Any, Dict, List, Optional if sys.version_info[:2] < (3, 8): - from typing_extensions import Literal + from typing_extensions import Literal, TypedDict else: - from typing import Literal + from typing import Literal, TypedDict import objc from CoreBluetooth import CBPeripheral @@ -19,6 +19,20 @@ logger = logging.getLogger(__name__) +class CBScannerArgs(TypedDict, total=False): + """ + Platform-specific :class:`BleakScanner` args for the CoreBluetooth backend. + """ + + use_bdaddr: bool + """ + If true, use Bluetooth address instead of UUID. + + .. warning:: This uses an undocumented IOBluetooth API to get the Bluetooth + address and may break in the future macOS releases. + """ + + class BleakScannerCoreBluetooth(BaseBleakScanner): """The native macOS Bleak BLE Scanner. @@ -52,12 +66,16 @@ def __init__( detection_callback: Optional[AdvertisementDataCallback], service_uuids: Optional[List[str]], scanning_mode: Literal["active", "passive"], + *, + cb: CBScannerArgs, **kwargs ): super(BleakScannerCoreBluetooth, self).__init__( detection_callback, service_uuids ) + self._use_bdaddr = cb.get("use_bdaddr", False) + if scanning_mode == "passive": raise BleakError("macOS does not support passive scanning") @@ -112,8 +130,17 @@ def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None: platform_data=(p, a, r), ) + if self._use_bdaddr: + # HACK: retrieveAddressForPeripheral_ is undocumented but seems to do the trick + address_bytes: bytes = ( + self._manager.central_manager.retrieveAddressForPeripheral_(p) + ) + address = address_bytes.hex(":").upper() + else: + address = p.identifier().UUIDString() + device = self.create_or_update_device( - p.identifier().UUIDString(), + address, p.name(), (p, self._manager.central_manager.delegate()), advertisement_data, From 828b0777ee002ed22fe66465619df47a67b2584c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 18 Nov 2022 21:00:26 -0600 Subject: [PATCH 13/45] bleak: add BleakScanner.find_device_by_name() Finding a device by name is often more convenient than by address. --- CHANGELOG.rst | 1 + bleak/__init__.py | 29 ++++++++++++++++++++++++----- docs/api/scanner.rst | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 644233e4..5b61afff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ and this project adheres to `Semantic Versioning Optional[BLEDevice]: + """Obtain a ``BLEDevice`` for a BLE server specified by the local name in the advertising data. + + Args: + name: The name sought. + timeout: Optional timeout to wait for detection of specified peripheral before giving up. Defaults to 10.0 seconds. + **kwargs: additional args passed to the :class:`BleakScanner` constructor. + + Returns: + The ``BLEDevice`` sought or ``None`` if not detected. + + """ + return await cls.find_device_by_filter( + lambda d, ad: ad.local_name == name, + timeout=timeout, + **kwargs, + ) + @classmethod async def find_device_by_filter( cls, filterfunc: AdvertisementDataFilter, timeout: float = 10.0, **kwargs diff --git a/docs/api/scanner.rst b/docs/api/scanner.rst index 100b7cbf..f9a15787 100644 --- a/docs/api/scanner.rst +++ b/docs/api/scanner.rst @@ -16,6 +16,7 @@ more advanced use cases like long running programs, GUIs or connecting to multiple devices. .. automethod:: bleak.BleakScanner.discover +.. automethod:: bleak.BleakScanner.find_device_by_name .. automethod:: bleak.BleakScanner.find_device_by_address .. automethod:: bleak.BleakScanner.find_device_by_filter From 74ea1c4feb0f7149089501f986d83cb8d3d42d6c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 18 Nov 2022 22:48:18 -0600 Subject: [PATCH 14/45] examples: use argparse and logging This updates some of the examples to use argparse to make it easier to use the examples without having to modify the code. Logging is used with timestamps for more useful feedback. Some examples are removed because they are redundant. --- examples/async_callback_with_queue.py | 100 +++++++++++++++++++------- examples/connect_by_bledevice.py | 31 -------- examples/detection_callback.py | 33 +++++++-- examples/disconnect_callback.py | 65 ++++++++++++++--- examples/discover.py | 19 ++++- examples/enable_notifications.py | 85 ++++++++++++++++------ examples/get_services.py | 32 --------- examples/scanner.py | 31 -------- examples/scanner_byname.py | 29 -------- examples/service_explorer.py | 95 +++++++++++++++++------- 10 files changed, 307 insertions(+), 213 deletions(-) delete mode 100644 examples/connect_by_bledevice.py delete mode 100644 examples/get_services.py delete mode 100644 examples/scanner.py delete mode 100644 examples/scanner_byname.py diff --git a/examples/async_callback_with_queue.py b/examples/async_callback_with_queue.py index bc241048..da230bfb 100644 --- a/examples/async_callback_with_queue.py +++ b/examples/async_callback_with_queue.py @@ -9,38 +9,57 @@ Created on 2021-02-25 by hbldh """ -import sys +import argparse import time -import platform import asyncio import logging -from bleak import BleakClient +from bleak import BleakClient, BleakScanner logger = logging.getLogger(__name__) -ADDRESS = ( - "24:71:89:cc:09:05" - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" -) -CHARACTERISTIC_UUID = f"0000{0xFFE1:x}-0000-1000-8000-00805f9b34fb" +class DeviceNotFoundError(Exception): + pass + + +async def run_ble_client(args: argparse.Namespace, queue: asyncio.Queue): + logger.info("starting scan...") + + if args.address: + device = await BleakScanner.find_device_by_address( + args.address, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with address '%s'", args.address) + raise DeviceNotFoundError + else: + device = await BleakScanner.find_device_by_name( + args.name, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with name '%s'", args.name) + raise DeviceNotFoundError + + logger.info("connecting to device...") -async def run_ble_client(address: str, char_uuid: str, queue: asyncio.Queue): async def callback_handler(_, data): await queue.put((time.time(), data)) - async with BleakClient(address) as client: - logger.info(f"Connected: {client.is_connected}") - await client.start_notify(char_uuid, callback_handler) + async with BleakClient(device) as client: + logger.info("connected") + await client.start_notify(args.characteristic, callback_handler) await asyncio.sleep(10.0) - await client.stop_notify(char_uuid) + await client.stop_notify(args.characteristic) # Send an "exit command to the consumer" await queue.put((time.time(), None)) + logger.info("disconnected") + async def run_queue_consumer(queue: asyncio.Queue): + logger.info("Starting queue consumer") + while True: # Use await asyncio.wait_for(queue.get(), timeout=1.0) if you want a timeout for getting data. epoch, data = await queue.get() @@ -50,22 +69,55 @@ async def run_queue_consumer(queue: asyncio.Queue): ) break else: - logger.info(f"Received callback data via async queue at {epoch}: {data}") + logger.info("Received callback data via async queue at %s: %r", epoch, data) -async def main(address: str, char_uuid: str): +async def main(args: argparse.Namespace): queue = asyncio.Queue() - client_task = run_ble_client(address, char_uuid, queue) + client_task = run_ble_client(args, queue) consumer_task = run_queue_consumer(queue) - await asyncio.gather(client_task, consumer_task) + + try: + await asyncio.gather(client_task, consumer_task) + except DeviceNotFoundError: + pass + logger.info("Main method done.") if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - asyncio.run( - main( - sys.argv[1] if len(sys.argv) > 1 else ADDRESS, - sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID, - ) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", ) + + parser = argparse.ArgumentParser() + + device_group = parser.add_mutually_exclusive_group(required=True) + + device_group.add_argument( + "--name", + metavar="", + help="the name of the bluetooth device to connect to", + ) + device_group.add_argument( + "--address", + metavar="
", + help="the address of the bluetooth device to connect to", + ) + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", + ) + + parser.add_argument( + "characteristic", + metavar="", + help="UUID of a characteristic that supports notifications", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) diff --git a/examples/connect_by_bledevice.py b/examples/connect_by_bledevice.py deleted file mode 100644 index 565ac3a1..00000000 --- a/examples/connect_by_bledevice.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Connect by BLEDevice -""" - -import asyncio -import platform -import sys - -from bleak import BleakClient, BleakScanner -from bleak.exc import BleakError - - -ADDRESS = ( - "24:71:89:cc:09:05" - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" -) - - -async def main(ble_address: str): - device = await BleakScanner.find_device_by_address(ble_address, timeout=20.0) - if not device: - raise BleakError(f"A device with address {ble_address} could not be found.") - async with BleakClient(device) as client: - print("Services:") - for service in client.services: - print(service) - - -if __name__ == "__main__": - asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else ADDRESS)) diff --git a/examples/detection_callback.py b/examples/detection_callback.py index 7aae77d8..1e55e611 100644 --- a/examples/detection_callback.py +++ b/examples/detection_callback.py @@ -8,9 +8,9 @@ """ +import argparse import asyncio import logging -import sys from bleak import BleakScanner from bleak.backends.device import BLEDevice @@ -20,14 +20,16 @@ def simple_callback(device: BLEDevice, advertisement_data: AdvertisementData): - logger.info(f"{device.address}: {advertisement_data}") + logger.info("%s: %r", device.address, advertisement_data) -async def main(service_uuids): - scanner = BleakScanner(simple_callback, service_uuids) +async def main(args: argparse.Namespace): + scanner = BleakScanner( + simple_callback, args.services, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) while True: - print("(re)starting scanner") + logger.info("(re)starting scanner") await scanner.start() await asyncio.sleep(5.0) await scanner.stop() @@ -38,5 +40,22 @@ async def main(service_uuids): level=logging.INFO, format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", ) - service_uuids = sys.argv[1:] - asyncio.run(main(service_uuids)) + + parser = argparse.ArgumentParser() + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", + ) + + parser.add_argument( + "--services", + metavar="", + nargs="*", + help="UUIDs of one or more services to filter for", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) diff --git a/examples/disconnect_callback.py b/examples/disconnect_callback.py index 8362d878..0dde8cd7 100644 --- a/examples/disconnect_callback.py +++ b/examples/disconnect_callback.py @@ -8,30 +8,75 @@ """ +import argparse import asyncio +import logging from bleak import BleakScanner, BleakClient -async def main(): - devs = await BleakScanner.discover() - if not devs: - print("No devices found, try again later.") - return +logger = logging.getLogger(__name__) + + +async def main(args: argparse.Namespace): + logger.info("scanning...") + + if args.address: + device = await BleakScanner.find_device_by_address( + args.address, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with address '%s'", args.address) + return + else: + device = await BleakScanner.find_device_by_name( + args.name, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with name '%s'", args.name) + return disconnected_event = asyncio.Event() def disconnected_callback(client): - print("Disconnected callback called!") + logger.info("Disconnected callback called!") disconnected_event.set() async with BleakClient( - devs[0], disconnected_callback=disconnected_callback + device, disconnected_callback=disconnected_callback ) as client: - print("Sleeping until device disconnects...") + logger.info("Sleeping until device disconnects...") await disconnected_event.wait() - print("Connected:", client.is_connected) + logger.info("Connected: %r", client.is_connected) if __name__ == "__main__": - asyncio.run(main()) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + + parser = argparse.ArgumentParser() + + device_group = parser.add_mutually_exclusive_group(required=True) + + device_group.add_argument( + "--name", + metavar="", + help="the name of the bluetooth device to connect to", + ) + device_group.add_argument( + "--address", + metavar="
", + help="the address of the bluetooth device to connect to", + ) + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) diff --git a/examples/discover.py b/examples/discover.py index 56d556cd..c16f5bd1 100644 --- a/examples/discover.py +++ b/examples/discover.py @@ -8,15 +8,18 @@ """ +import argparse import asyncio from bleak import BleakScanner -async def main(): +async def main(args: argparse.Namespace): print("scanning for 5 seconds, please wait...") - devices = await BleakScanner.discover(return_adv=True) + devices = await BleakScanner.discover( + return_adv=True, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) for d, a in devices.values(): print() @@ -26,4 +29,14 @@ async def main(): if __name__ == "__main__": - asyncio.run(main()) + parser = argparse.ArgumentParser() + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) diff --git a/examples/enable_notifications.py b/examples/enable_notifications.py index ce293f78..3c770372 100644 --- a/examples/enable_notifications.py +++ b/examples/enable_notifications.py @@ -9,41 +9,82 @@ """ -import sys +import argparse import asyncio -import platform +import logging -from bleak import BleakClient +from bleak import BleakClient, BleakScanner from bleak.backends.characteristic import BleakGATTCharacteristic - -# you can change these to match your device or override them from the command line -CHARACTERISTIC_UUID = "f000aa65-0451-4000-b000-000000000000" -ADDRESS = ( - "24:71:89:cc:09:05" - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" -) +logger = logging.getLogger(__name__) def notification_handler(characteristic: BleakGATTCharacteristic, data: bytearray): """Simple notification handler which prints the data received.""" - print(f"{characteristic.description}: {data}") + logger.info("%s: %r", characteristic.description, data) + + +async def main(args: argparse.Namespace): + logger.info("starting scan...") + if args.address: + device = await BleakScanner.find_device_by_address( + args.address, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with address '%s'", args.address) + return + else: + device = await BleakScanner.find_device_by_name( + args.name, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with name '%s'", args.name) + return -async def main(address, char_uuid): - async with BleakClient(address) as client: - print(f"Connected: {client.is_connected}") + logger.info("connecting to device...") - await client.start_notify(char_uuid, notification_handler) + async with BleakClient(device) as client: + logger.info("Connected") + + await client.start_notify(args.characteristic, notification_handler) await asyncio.sleep(5.0) - await client.stop_notify(char_uuid) + await client.stop_notify(args.characteristic) if __name__ == "__main__": - asyncio.run( - main( - sys.argv[1] if len(sys.argv) > 1 else ADDRESS, - sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID, - ) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + + parser = argparse.ArgumentParser() + + device_group = parser.add_mutually_exclusive_group(required=True) + + device_group.add_argument( + "--name", + metavar="", + help="the name of the bluetooth device to connect to", + ) + device_group.add_argument( + "--address", + metavar="
", + help="the address of the bluetooth device to connect to", + ) + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", ) + + parser.add_argument( + "characteristic", + metavar="", + help="UUID of a characteristic that supports notifications", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) diff --git a/examples/get_services.py b/examples/get_services.py deleted file mode 100644 index 575dae89..00000000 --- a/examples/get_services.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Services ----------------- - -An example showing how to fetch all services and print them. - -Updated on 2019-03-25 by hbldh - -""" - -import sys -import asyncio -import platform - -from bleak import BleakClient - -ADDRESS = ( - "24:71:89:cc:09:05" - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" -) - - -async def main(address: str): - async with BleakClient(address) as client: - print("Services:") - for service in client.services: - print(service) - - -if __name__ == "__main__": - asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else ADDRESS)) diff --git a/examples/scanner.py b/examples/scanner.py deleted file mode 100644 index 664f4aa8..00000000 --- a/examples/scanner.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Bleak Scanner -------------- - - - -Updated on 2020-08-12 by hbldh - -""" - -import asyncio -import platform -import sys - -from bleak import BleakScanner - - -ADDRESS = ( - "24:71:89:cc:09:05" # <--- Change to your device's address here if you are using Windows or Linux - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" # <--- Change to your device's address here if you are using macOS -) - - -async def main(address): - device = await BleakScanner.find_device_by_address(address) - print(device) - - -if __name__ == "__main__": - asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else ADDRESS)) diff --git a/examples/scanner_byname.py b/examples/scanner_byname.py deleted file mode 100644 index 4cf1d3da..00000000 --- a/examples/scanner_byname.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Bleak Scanner -------------- - - - -Updated on 2020-08-12 by hbldh - -""" - -import asyncio -import sys - -from bleak import BleakScanner - - -async def main(wanted_name): - device = await BleakScanner.find_device_by_filter( - lambda d, ad: d.name and d.name.lower() == wanted_name.lower() - ) - print(device) - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} name") - sys.exit(1) - - asyncio.run(main(sys.argv[1])) diff --git a/examples/service_explorer.py b/examples/service_explorer.py index 897864e2..b2ef278d 100644 --- a/examples/service_explorer.py +++ b/examples/service_explorer.py @@ -9,56 +9,103 @@ """ -import sys -import platform +import argparse import asyncio import logging -from bleak import BleakClient +from bleak import BleakClient, BleakScanner logger = logging.getLogger(__name__) -ADDRESS = ( - "24:71:89:cc:09:05" - if platform.system() != "Darwin" - else "B9EA5233-37EF-4DD6-87A8-2A875E821C46" -) +async def main(args: argparse.Namespace): + logger.info("starting scan...") -async def main(address): - async with BleakClient(address) as client: - logger.info(f"Connected: {client.is_connected}") + if args.address: + device = await BleakScanner.find_device_by_address( + args.address, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with address '%s'", args.address) + return + else: + device = await BleakScanner.find_device_by_name( + args.name, cb=dict(use_bdaddr=args.macos_use_bdaddr) + ) + if device is None: + logger.error("could not find device with name '%s'", args.name) + return + + logger.info("connecting to device...") + + async with BleakClient(device) as client: + logger.info("connected") for service in client.services: - logger.info(f"[Service] {service}") + logger.info("[Service] %s", service) + for char in service.characteristics: if "read" in char.properties: try: - value = bytes(await client.read_gatt_char(char.uuid)) + value = await client.read_gatt_char(char.uuid) logger.info( - f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}" + " [Characteristic] %s (%s), Value: %r", + char, + ",".join(char.properties), + value, ) except Exception as e: logger.error( - f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {e}" + " [Characteristic] %s (%s), Error: %s", + char, + ",".join(char.properties), + e, ) else: - value = None logger.info( - f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}" + " [Characteristic] %s (%s)", char, ",".join(char.properties) ) for descriptor in char.descriptors: try: - value = bytes( - await client.read_gatt_descriptor(descriptor.handle) - ) - logger.info(f"\t\t[Descriptor] {descriptor}) | Value: {value}") + value = await client.read_gatt_descriptor(descriptor.handle) + logger.info(" [Descriptor] %s, Value: %r", descriptor, value) except Exception as e: - logger.error(f"\t\t[Descriptor] {descriptor}) | Value: {e}") + logger.error(" [Descriptor] %s, Error: %s", descriptor, e) + + logger.info("disconnecting...") + + logger.info("disconnected") if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else ADDRESS)) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + + parser = argparse.ArgumentParser() + + device_group = parser.add_mutually_exclusive_group(required=True) + + device_group.add_argument( + "--name", + metavar="", + help="the name of the bluetooth device to connect to", + ) + device_group.add_argument( + "--address", + metavar="
", + help="the address of the bluetooth device to connect to", + ) + + parser.add_argument( + "--macos-use-bdaddr", + action="store_true", + help="when true use Bluetooth address instead of UUID on macOS", + ) + + args = parser.parse_args() + + asyncio.run(main(args)) From b0074d3d12463ac5e56899d024ece9b63a48c621 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 12:03:10 -0600 Subject: [PATCH 15/45] uuids: remove invalid UTF-8 This removes some invalid UTF-8 characters. This was noticed when Bleak examples were run with `python -m trace --trace`. I caused the trace to crash with a UnicodeError. The Chinese name for Huawei has been removed from the Bluetooth assigned numbers doc so we removed all the Chinese names just to keep it simple. --- CHANGELOG.rst | 4 ++++ bleak/uuids.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b61afff..6f8c211b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,10 @@ Changed * ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. +Fixed +----- +- Fixed invalid UTF-8 in ``uuids.uuid16_dict``. + `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/uuids.py b/bleak/uuids.py index c368c614..0621c611 100644 --- a/bleak/uuids.py +++ b/bleak/uuids.py @@ -843,8 +843,8 @@ 0xFE83: "Blue Bite", 0xFE84: "RF Digital Corp", 0xFE85: "RF Digital Corp", - 0xFE86: "HUAWEI Technologies Co.: Ltd. ( 华为技术有限公司 )", - 0xFE87: "Qingdao Yeelink Information Technology Co.: Ltd. ( 青岛亿联客信息技术有限公司 )", + 0xFE86: "HUAWEI Technologies Co.: Ltd.", + 0xFE87: "Qingdao Yeelink Information Technology Co.: Ltd.", 0xFE88: "SALTO SYSTEMS S.L.", 0xFE89: "B&O Play A/S", 0xFE8A: "Apple: Inc.", From 652026831fe95045bd12743a4af85ee5a66613e3 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 12:12:23 -0600 Subject: [PATCH 16/45] winrt/client: fix AttributeError in _ensure_success result could be None, in which case, result.status would raise an AttributeError. `status` is the intended value in any case. --- CHANGELOG.rst | 3 +++ bleak/backends/winrt/client.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6f8c211b..b33977b3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,9 @@ Changed * Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. * ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. +Fixed +----- +- Fixed ``AttributeError`` in ``_ensure_success`` in WinRT backend. Fixed ----- diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 09e775da..f5ab41cf 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -122,7 +122,7 @@ def _ensure_success(result: Any, attr: Optional[str], fail_msg: str) -> Any: if status == GattCommunicationStatus.UNREACHABLE: raise BleakError(f"{fail_msg}: Unreachable") - raise BleakError(f"{fail_msg}: Unexpected status code 0x{result.status:02X}") + raise BleakError(f"{fail_msg}: Unexpected status code 0x{status:02X}") class WinRTClientArgs(TypedDict, total=False): From 5e7d1911d72b25adce84cb1e3d720a3e741eccee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Poto=C4=8Dnik?= Date: Tue, 22 Nov 2022 12:52:30 +0100 Subject: [PATCH 17/45] Instantiate BaseBleakClient.services collection after service discovery --- CHANGELOG.rst | 2 ++ bleak/__init__.py | 6 ++++++ bleak/backends/bluezdbus/client.py | 6 ++---- bleak/backends/client.py | 4 +--- bleak/backends/corebluetooth/client.py | 25 +++++++++++-------------- bleak/backends/p4android/client.py | 15 ++++++++------- bleak/backends/winrt/client.py | 11 +++++------ 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b33977b3..cf0dc24f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,8 @@ Changed * Dropped ``async-timeout`` dependency on Python >= 3.11. * Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. * ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. +* ``BaseBleakClient.services`` is now ``None`` instead of empty service collection + until services are discovered. Fixed ----- diff --git a/bleak/__init__.py b/bleak/__init__.py index c2d340c4..2ebace99 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -576,7 +576,13 @@ def services(self) -> BleakGATTServiceCollection: Gets the collection of GATT services available on the device. The returned value is only valid as long as the device is connected. + + Raises: + BleakError: if service discovery has not been performed yet during this connection. """ + if not self._backend.services: + raise BleakError("Service Discovery has not been performed yet") + return self._backend.services # I/O methods diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 6c419f22..8bf88ff0 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -309,8 +309,7 @@ def _cleanup_all(self) -> None: self._bus = None # Reset all stored services. - self.services = BleakGATTServiceCollection() - self._services_resolved = False + self.services = None async def disconnect(self) -> bool: """Disconnect from the specified GATT server. @@ -587,7 +586,7 @@ async def get_services( if not self.is_connected: raise BleakError("Not connected") - if self._services_resolved: + if self.services is not None: return self.services manager = await get_global_bluez_manager() @@ -595,7 +594,6 @@ async def get_services( self.services = await manager.get_services( self._device_path, dangerous_use_bleak_cache ) - self._services_resolved = True return self.services diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 6bcdf760..d2bf6323 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -42,9 +42,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): else: self.address = address_or_ble_device - self.services = BleakGATTServiceCollection() - - self._services_resolved = False + self.services: Optional[BleakGATTServiceCollection] = None self._timeout = kwargs.get("timeout", 10.0) self._disconnected_callback = kwargs.get("disconnected_callback") diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index fb91b93c..4fd38c3d 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -14,7 +14,7 @@ CBPeripheral, CBPeripheralStateConnected, ) -from Foundation import NSArray, NSData +from Foundation import NSData from ... import BleakScanner from ...exc import BleakError, BleakDeviceNotFoundError @@ -57,8 +57,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._central_manager_delegate, ) = address_or_ble_device.details - self._services: Optional[NSArray] = None - def __str__(self): return "BleakClientCoreBluetooth ({})".format(self.address) @@ -91,11 +89,9 @@ async def connect(self, **kwargs) -> bool: ) def disconnect_callback(): - self.services = BleakGATTServiceCollection() # Ensure that `get_services` retrieves services again, rather # than using the cached object - self._services_resolved = False - self._services = None + self.services = None # If there are any pending futures waiting for delegate callbacks, we # need to raise an exception since the callback will no longer be @@ -190,20 +186,22 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ - if self._services is not None: + if self.services is not None: return self.services + services = BleakGATTServiceCollection() + logger.debug("Retrieving services...") - services = await self._delegate.discover_services() + cb_services = await self._delegate.discover_services() - for service in services: + for service in cb_services: serviceUUID = service.UUID().UUIDString() logger.debug( "Retrieving characteristics for service {}".format(serviceUUID) ) characteristics = await self._delegate.discover_characteristics(service) - self.services.add_service(BleakGATTServiceCoreBluetooth(service)) + services.add_service(BleakGATTServiceCoreBluetooth(service)) for characteristic in characteristics: cUUID = characteristic.UUID().UUIDString() @@ -212,7 +210,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) descriptors = await self._delegate.discover_descriptors(characteristic) - self.services.add_characteristic( + services.add_characteristic( BleakGATTCharacteristicCoreBluetooth( characteristic, self._peripheral.maximumWriteValueLengthForType_( @@ -221,7 +219,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) ) for descriptor in descriptors: - self.services.add_descriptor( + services.add_descriptor( BleakGATTDescriptorCoreBluetooth( descriptor, cb_uuid_to_str(characteristic.UUID()), @@ -229,8 +227,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) ) logger.debug("Services resolved for %s", str(self)) - self._services_resolved = True - self._services = services + self.services = services return self.services async def read_gatt_char( diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py index 3a19a183..24e5eae9 100644 --- a/bleak/backends/p4android/client.py +++ b/bleak/backends/p4android/client.py @@ -151,8 +151,7 @@ async def disconnect(self) -> bool: self.__callbacks = None # Reset all stored services. - self.services = BleakGATTServiceCollection() - self._services_resolved = False + self.services = None return True @@ -246,14 +245,16 @@ async def get_services(self) -> BleakGATTServiceCollection: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ - if self._services_resolved: + if self.services is not None: return self.services + services = BleakGATTServiceCollection() + logger.debug("Get Services...") for java_service in self.__gatt.getServices(): service = BleakGATTServiceP4Android(java_service) - self.services.add_service(service) + services.add_service(service) for java_characteristic in java_service.getCharacteristics(): @@ -263,7 +264,7 @@ async def get_services(self) -> BleakGATTServiceCollection: service.handle, self.__mtu - 3, ) - self.services.add_characteristic(characteristic) + services.add_characteristic(characteristic) for descriptor_index, java_descriptor in enumerate( java_characteristic.getDescriptors() @@ -275,9 +276,9 @@ async def get_services(self) -> BleakGATTServiceCollection: characteristic.handle, descriptor_index, ) - self.services.add_descriptor(descriptor) + services.add_descriptor(descriptor) - self._services_resolved = True + self.services = services return self.services # IO methods diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index f5ab41cf..5241eec8 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -414,7 +414,6 @@ def max_pdu_size_changed_handler(sender: GattSession, args): # services did not change while getting services, # so this is the final result self.services = get_services_task.result() - self._services_resolved = True break logger.debug( @@ -463,10 +462,10 @@ async def disconnect(self) -> bool: self._notification_callbacks.clear() # Dispose all service components that we have requested and created. - for service in self.services: - service.obj.close() - self.services = BleakGATTServiceCollection() - self._services_resolved = False + if self.services: + for service in self.services: + service.obj.close() + self.services = None # Without this, disposing the BluetoothLEDevice won't disconnect it if self._session: @@ -616,7 +615,7 @@ async def get_services( """ # Return the Service Collection. - if self._services_resolved: + if self.services is not None: return self.services logger.debug( From b39beec48e7a3a4556b9c95a2fa140d7e6aebaf2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 15:19:52 -0600 Subject: [PATCH 18/45] bleak: include %(threadName)s in BLEAK_LOGGING (#1144) This adds `%(threadName)s` to `FORMAT` passed to `logging.basicConfig()` when `BLEAK_LOGGING` is used. This can be useful to debug OS callbacks on background threads. --- CHANGELOG.rst | 1 + bleak/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf0dc24f..cac65bc1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,7 @@ Changed * ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. * ``BaseBleakClient.services`` is now ``None`` instead of empty service collection until services are discovered. +* Include thread name in ``BLEAK_LOGGING`` output. Merged #1144. Fixed ----- diff --git a/bleak/__init__.py b/bleak/__init__.py index 2ebace99..2ced4b02 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -60,7 +60,7 @@ _logger = logging.getLogger(__name__) _logger.addHandler(logging.NullHandler()) if bool(os.environ.get("BLEAK_LOGGING", False)): - FORMAT = "%(asctime)-15s %(name)-8s %(levelname)s: %(message)s" + FORMAT = "%(asctime)-15s %(name)-8s %(threadName)s %(levelname)s: %(message)s" handler = logging.StreamHandler(sys.stdout) handler.setLevel(logging.DEBUG) handler.setFormatter(logging.Formatter(fmt=FORMAT)) From bf30be8c53dc2d5955c395961e48e576c9bbc783 Mon Sep 17 00:00:00 2001 From: Lorenzo Felletti <60483783+lorenzofelletti@users.noreply.github.com> Date: Tue, 29 Nov 2022 00:21:46 +0100 Subject: [PATCH 19/45] Examples: added debug flag (#1153) Added a debug flag to all applicable examples that enables DEBUG level logging instead of default INFO level. The flag can be called with either -d or --debug. --- CHANGELOG.rst | 1 + examples/async_callback_with_queue.py | 18 +++++++++++++----- examples/detection_callback.py | 18 +++++++++++++----- examples/disconnect_callback.py | 18 +++++++++++++----- examples/enable_notifications.py | 18 +++++++++++++----- examples/service_explorer.py | 18 +++++++++++++----- 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cac65bc1..54651600 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added ----- * Added optional hack to use Bluetooth address instead of UUID on macOS. * Added ``BleakScanner.find_device_by_name()`` class method. +* Added optional command line argument to use debug log level to all applicable examples. Changed ------- diff --git a/examples/async_callback_with_queue.py b/examples/async_callback_with_queue.py index da230bfb..960e0f77 100644 --- a/examples/async_callback_with_queue.py +++ b/examples/async_callback_with_queue.py @@ -86,11 +86,6 @@ async def main(args: argparse.Namespace): if __name__ == "__main__": - logging.basicConfig( - level=logging.INFO, - format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", - ) - parser = argparse.ArgumentParser() device_group = parser.add_mutually_exclusive_group(required=True) @@ -118,6 +113,19 @@ async def main(args: argparse.Namespace): help="UUID of a characteristic that supports notifications", ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="sets the logging level to debug", + ) + args = parser.parse_args() + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + asyncio.run(main(args)) diff --git a/examples/detection_callback.py b/examples/detection_callback.py index 1e55e611..7698353c 100644 --- a/examples/detection_callback.py +++ b/examples/detection_callback.py @@ -36,11 +36,6 @@ async def main(args: argparse.Namespace): if __name__ == "__main__": - logging.basicConfig( - level=logging.INFO, - format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", - ) - parser = argparse.ArgumentParser() parser.add_argument( @@ -56,6 +51,19 @@ async def main(args: argparse.Namespace): help="UUIDs of one or more services to filter for", ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="sets the logging level to debug", + ) + args = parser.parse_args() + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + asyncio.run(main(args)) diff --git a/examples/disconnect_callback.py b/examples/disconnect_callback.py index 0dde8cd7..9c455836 100644 --- a/examples/disconnect_callback.py +++ b/examples/disconnect_callback.py @@ -51,11 +51,6 @@ def disconnected_callback(client): if __name__ == "__main__": - logging.basicConfig( - level=logging.INFO, - format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", - ) - parser = argparse.ArgumentParser() device_group = parser.add_mutually_exclusive_group(required=True) @@ -77,6 +72,19 @@ def disconnected_callback(client): help="when true use Bluetooth address instead of UUID on macOS", ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="sets the log level to debug", + ) + args = parser.parse_args() + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + asyncio.run(main(args)) diff --git a/examples/enable_notifications.py b/examples/enable_notifications.py index 3c770372..93bc13d8 100644 --- a/examples/enable_notifications.py +++ b/examples/enable_notifications.py @@ -53,11 +53,6 @@ async def main(args: argparse.Namespace): if __name__ == "__main__": - logging.basicConfig( - level=logging.INFO, - format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", - ) - parser = argparse.ArgumentParser() device_group = parser.add_mutually_exclusive_group(required=True) @@ -85,6 +80,19 @@ async def main(args: argparse.Namespace): help="UUID of a characteristic that supports notifications", ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="sets the log level to debug", + ) + args = parser.parse_args() + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + asyncio.run(main(args)) diff --git a/examples/service_explorer.py b/examples/service_explorer.py index b2ef278d..a8cf44ac 100644 --- a/examples/service_explorer.py +++ b/examples/service_explorer.py @@ -80,11 +80,6 @@ async def main(args: argparse.Namespace): if __name__ == "__main__": - logging.basicConfig( - level=logging.INFO, - format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", - ) - parser = argparse.ArgumentParser() device_group = parser.add_mutually_exclusive_group(required=True) @@ -106,6 +101,19 @@ async def main(args: argparse.Namespace): help="when true use Bluetooth address instead of UUID on macOS", ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="sets the log level to debug", + ) + args = parser.parse_args() + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, + format="%(asctime)-15s %(name)-8s %(levelname)s: %(message)s", + ) + asyncio.run(main(args)) From 9cd7205066e95573fd3653d35bf137ea746325ec Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 25 Nov 2022 17:30:57 -0600 Subject: [PATCH 20/45] bluezdbus/manager: suppress org.bluez.Error.NotReady when stopping scanner When stopping scanning, we don't want to raise errors if we can be sure that scanning is in fact stopped. `org.bluez.Error.NotReady` can be triggered if the Bluetooth adapter is turned off after scanning started, in which case we know that it is not scanning, so it is safe to ignore this error. --- CHANGELOG.rst | 3 ++- bleak/backends/bluezdbus/manager.py | 31 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54651600..ab1eb15f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,7 +27,8 @@ Changed Fixed ----- -- Fixed ``AttributeError`` in ``_ensure_success`` in WinRT backend. +* Fixed ``AttributeError`` in ``_ensure_success`` in WinRT backend. +* Fixed ``BleakScanner.stop()`` can raise ``BleakDBusError`` with ``org.bluez.Error.NotReady`` in BlueZ backend. Fixed ----- diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index 8ee6e5d0..cfb826ed 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -27,7 +27,7 @@ from dbus_fast import BusType, Message, MessageType, Variant, unpack_variants from dbus_fast.aio.message_bus import MessageBus -from ...exc import BleakError +from ...exc import BleakDBusError, BleakError from ..service import BleakGATTServiceCollection from . import defs from .advertisement_monitor import AdvertisementMonitor, OrPatternLike @@ -385,20 +385,25 @@ async def stop() -> None: member="StopDiscovery", ) ) - assert_reply(reply) - # remove the filters - reply = await self._bus.call( - Message( - destination=defs.BLUEZ_SERVICE, - path=adapter_path, - interface=defs.ADAPTER_INTERFACE, - member="SetDiscoveryFilter", - signature="a{sv}", - body=[{}], + try: + assert_reply(reply) + except BleakDBusError as ex: + if ex.dbus_error != "org.bluez.Error.NotReady": + raise + else: + # remove the filters + reply = await self._bus.call( + Message( + destination=defs.BLUEZ_SERVICE, + path=adapter_path, + interface=defs.ADAPTER_INTERFACE, + member="SetDiscoveryFilter", + signature="a{sv}", + body=[{}], + ) ) - ) - assert_reply(reply) + assert_reply(reply) return stop except BaseException: From 22610ae92e013dadb941f5004debc49fb7fc52a7 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 28 Nov 2022 17:29:49 -0600 Subject: [PATCH 21/45] winrt/scanner: fix stop hanging when Bluetooth disabled When Bluetooth is disabled on Windows, we will never get the `BluetoothLEAdvertisementWatcher.Stopped` event from the WinRT API. We can work around this by checking the status of the watcher to see if we can expect an event. --- CHANGELOG.rst | 1 + bleak/backends/winrt/scanner.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab1eb15f..0b0e10d0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -29,6 +29,7 @@ Fixed ----- * Fixed ``AttributeError`` in ``_ensure_success`` in WinRT backend. * Fixed ``BleakScanner.stop()`` can raise ``BleakDBusError`` with ``org.bluez.Error.NotReady`` in BlueZ backend. +* Fixed ``BleakScanner.stop()`` hanging in WinRT backend when Bluetooth is disabled. Fixed ----- diff --git a/bleak/backends/winrt/scanner.py b/bleak/backends/winrt/scanner.py index 1da9553c..68b0940b 100644 --- a/bleak/backends/winrt/scanner.py +++ b/bleak/backends/winrt/scanner.py @@ -5,10 +5,11 @@ from uuid import UUID from bleak_winrt.windows.devices.bluetooth.advertisement import ( - BluetoothLEScanningMode, - BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementReceivedEventArgs, BluetoothLEAdvertisementType, + BluetoothLEAdvertisementWatcher, + BluetoothLEAdvertisementWatcherStatus, + BluetoothLEScanningMode, ) if sys.version_info[:2] < (3, 8): @@ -16,9 +17,8 @@ else: from typing import Literal -from ..scanner import AdvertisementDataCallback, BaseBleakScanner, AdvertisementData from ...assigned_numbers import AdvertisementDataType - +from ..scanner import AdvertisementData, AdvertisementDataCallback, BaseBleakScanner logger = logging.getLogger(__name__) @@ -257,7 +257,14 @@ async def start(self): async def stop(self): self.watcher.stop() - await self._stopped_event.wait() + + if self.watcher.status == BluetoothLEAdvertisementWatcherStatus.STOPPING: + await self._stopped_event.wait() + else: + logger.debug( + "skipping waiting for stop because status is %s", + self.watcher.status.name, + ) try: self.watcher.remove_received(self._received_token) From ba172f03ec7475b9d0e6042fe56e2e0aa9269a77 Mon Sep 17 00:00:00 2001 From: Arthur Crepin-Leblond Date: Thu, 1 Dec 2022 18:20:58 +0100 Subject: [PATCH 22/45] bluezdbus/client: Make sure the disconnect monitor task is properly cancelled. Sometimes the exception on connect is raised so early that the disconnect monitor task does not get the disconnect event set because the on_connected_changed callback is already unregistered and the task stays in pending state causing asyncio to throw an exception: "Task was destroyed but it is pending!" --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/client.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b0e10d0..a95c77f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ Added * Added optional hack to use Bluetooth address instead of UUID on macOS. * Added ``BleakScanner.find_device_by_name()`` class method. * Added optional command line argument to use debug log level to all applicable examples. +* Make sure the disconnect monitor task is properly cancelled on the BlueZ client. Changed ------- diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 8bf88ff0..da2f9f7a 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -161,6 +161,8 @@ def on_value_changed(char_path: str, value: bytes) -> None: ) self._remove_device_watcher = lambda: manager.remove_device_watcher(watcher) + local_disconnect_monitor_event = asyncio.Event() + try: try: # @@ -197,10 +199,10 @@ def on_value_changed(char_path: str, value: bytes) -> None: self._is_connected = True # Create a task that runs until the device is disconnected. - self._disconnect_monitor_event = asyncio.Event() + self._disconnect_monitor_event = local_disconnect_monitor_event asyncio.ensure_future( self._disconnect_monitor( - self._bus, self._device_path, self._disconnect_monitor_event + self._bus, self._device_path, local_disconnect_monitor_event ) ) @@ -246,6 +248,9 @@ def on_value_changed(char_path: str, value: bytes) -> None: raise except BaseException: + # this effectively cancels the disconnect monitor in case the event + # was not triggered by a D-Bus callback + local_disconnect_monitor_event.set() self._cleanup_all() raise From 746a389bbc8c2bae6f61c8452e86f264424b7fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 07:58:00 +0000 Subject: [PATCH 23/45] build(deps): bump certifi from 2022.6.15.1 to 2022.12.7 Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.6.15.1 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2022.06.15.1...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0e70401e..3c310e69 100644 --- a/poetry.lock +++ b/poetry.lock @@ -83,7 +83,7 @@ python-versions = "*" [[package]] name = "certifi" -version = "2022.6.15.1" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false @@ -699,8 +699,8 @@ bleak-winrt = [ {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, ] certifi = [ - {file = "certifi-2022.6.15.1-py3-none-any.whl", hash = "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0"}, - {file = "certifi-2022.6.15.1.tar.gz", hash = "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, From 36876cb93f9c0db012b81593729c5d944378c407 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 19 Dec 2022 10:14:50 -0600 Subject: [PATCH 24/45] github/workflows/build_and_test: don't fail fast By default github actions will cancel all builds in a matrix if one fails. It is useful to have all builds complete to help diagnose issues. --- .github/workflows/build_and_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8378f843..c23f6f8b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -11,6 +11,7 @@ jobs: name: "Build and test" runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] From 9c26e496449c63de095d10257c34eeda20d477de Mon Sep 17 00:00:00 2001 From: Koen Vervloesem Date: Mon, 19 Dec 2022 10:14:38 +0100 Subject: [PATCH 25/45] Add return type None to some scanner methods --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/scanner.py | 6 +++--- bleak/backends/corebluetooth/scanner.py | 6 +++--- bleak/backends/p4android/scanner.py | 12 ++++++------ bleak/backends/scanner.py | 6 +++--- bleak/backends/winrt/scanner.py | 6 +++--- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a95c77f8..279c7d14 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ and this project adheres to `Semantic Versioning None: manager = await get_global_bluez_manager() if self._adapter: @@ -195,14 +195,14 @@ async def start(self): self._handle_device_removed, ) - async def stop(self): + async def stop(self) -> None: if self._stop: # avoid reentrancy stop, self._stop = self._stop, None await stop() - def set_scanning_filter(self, **kwargs): + def set_scanning_filter(self, **kwargs) -> None: """Sets OS level scanning filters for the BleakScanner. For possible values for `filters`, see the parameters to the diff --git a/bleak/backends/corebluetooth/scanner.py b/bleak/backends/corebluetooth/scanner.py index bfcd2e30..b23803e8 100644 --- a/bleak/backends/corebluetooth/scanner.py +++ b/bleak/backends/corebluetooth/scanner.py @@ -92,7 +92,7 @@ def __init__( "macOS 12.0, 12.1 and 12.2 require non-empty service_uuids kwarg, otherwise no advertisement data will be received" ) - async def start(self): + async def start(self) -> None: self.seen_devices = {} def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None: @@ -154,11 +154,11 @@ def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None: self._manager.callbacks[id(self)] = callback await self._manager.start_scan(self._service_uuids) - async def stop(self): + async def stop(self) -> None: await self._manager.stop_scan() self._manager.callbacks.pop(id(self), None) - def set_scanning_filter(self, **kwargs): + def set_scanning_filter(self, **kwargs) -> None: """Set scanning filter for the scanner. .. note:: diff --git a/bleak/backends/p4android/scanner.py b/bleak/backends/p4android/scanner.py index 609e5846..2a69a7b2 100644 --- a/bleak/backends/p4android/scanner.py +++ b/bleak/backends/p4android/scanner.py @@ -63,10 +63,10 @@ def __init__( self.__javascanner = None self.__callback = None - def __del__(self): + def __del__(self) -> None: self.__stop() - async def start(self): + async def start(self) -> None: if BleakScannerP4Android.__scanner is not None: raise BleakError("A BleakScanner is already scanning on this adapter.") @@ -213,7 +213,7 @@ def handleAdapterStateChanged(context, intent): return await self.start() - def __stop(self): + def __stop(self) -> None: if self.__javascanner is not None: logger.debug("Stopping BTLE scan") self.__javascanner.stopScan(self.__callback.java) @@ -222,15 +222,15 @@ def __stop(self): else: logger.debug("BTLE scan already stopped") - async def stop(self): + async def stop(self) -> None: self.__stop() - def set_scanning_filter(self, **kwargs): + def set_scanning_filter(self, **kwargs) -> None: # If we do end up implementing this, this should accept List # and ScanSettings java objects to pass to startScan(). raise NotImplementedError("not implemented in Android backend") - def _handle_scan_result(self, result): + def _handle_scan_result(self, result) -> None: native_device = result.getDevice() record = result.getScanRecord() diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py index 359c6ce5..55636923 100644 --- a/bleak/backends/scanner.py +++ b/bleak/backends/scanner.py @@ -213,17 +213,17 @@ def create_or_update_device( return device @abc.abstractmethod - async def start(self): + async def start(self) -> None: """Start scanning for devices""" raise NotImplementedError() @abc.abstractmethod - async def stop(self): + async def stop(self) -> None: """Stop scanning for devices""" raise NotImplementedError() @abc.abstractmethod - def set_scanning_filter(self, **kwargs): + def set_scanning_filter(self, **kwargs) -> None: """Set scanning filter for the BleakScanner. Args: diff --git a/bleak/backends/winrt/scanner.py b/bleak/backends/winrt/scanner.py index 68b0940b..9e2f15cf 100644 --- a/bleak/backends/winrt/scanner.py +++ b/bleak/backends/winrt/scanner.py @@ -230,7 +230,7 @@ def _stopped_handler(self, sender, e): ) self._stopped_event.set() - async def start(self): + async def start(self) -> None: # start with fresh list of discovered devices self.seen_devices = {} self._advertisement_pairs.clear() @@ -255,7 +255,7 @@ async def start(self): self.watcher.start() - async def stop(self): + async def stop(self) -> None: self.watcher.stop() if self.watcher.status == BluetoothLEAdvertisementWatcherStatus.STOPPING: @@ -277,7 +277,7 @@ async def stop(self): self.watcher = None - def set_scanning_filter(self, **kwargs): + def set_scanning_filter(self, **kwargs) -> None: """Set a scanning filter for the BleakScanner. Keyword Args: From 7c63658d0d0add5aa34b87fd4b3d64e07ef721ca Mon Sep 17 00:00:00 2001 From: K Date: Mon, 26 Dec 2022 20:41:15 -0500 Subject: [PATCH 26/45] Update poetry.lock --- poetry.lock | 818 ++++++++++++++++++++++++++-------------------------- 1 file changed, 409 insertions(+), 409 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3c310e69..aa45924f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "alabaster" version = "0.7.12" @@ -5,6 +7,10 @@ description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false python-versions = "*" +files = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] [[package]] name = "async-timeout" @@ -13,6 +19,10 @@ description = "Timeout context manager for asyncio programs" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} @@ -24,6 +34,10 @@ description = "Enhance the standard unittest package with features for testing a category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, + {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, +] [[package]] name = "attrs" @@ -32,6 +46,10 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -46,6 +64,10 @@ description = "Internationalization utilities" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] [package.dependencies] pytz = ">=2015.7" @@ -57,6 +79,31 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" +files = [ + {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, + {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, + {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, + {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, + {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, + {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, + {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, + {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, + {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, + {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, + {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, + {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, + {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, + {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, + {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, + {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, + {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, + {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, + {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, +] [package.dependencies] click = ">=8.0.0" @@ -80,6 +127,19 @@ description = "Python WinRT bindings for Bleak" category = "main" optional = false python-versions = "*" +files = [ + {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, + {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, + {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, + {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, + {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, + {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, + {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, + {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, + {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, + {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, + {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, +] [[package]] name = "certifi" @@ -88,6 +148,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "charset-normalizer" @@ -96,6 +160,10 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "dev" optional = false python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] [package.extras] unicode-backport = ["unicodedata2"] @@ -107,6 +175,10 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -119,6 +191,10 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] [[package]] name = "coverage" @@ -127,6 +203,58 @@ description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, + {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, + {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, + {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, + {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, + {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, + {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, + {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, + {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, + {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, + {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, + {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, + {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, + {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, + {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, + {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, +] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} @@ -141,6 +269,36 @@ description = "A faster version of dbus-next" category = "main" optional = false python-versions = ">=3.7,<4.0" +files = [ + {file = "dbus-fast-1.22.0.tar.gz", hash = "sha256:de6936cd4f70eb094051167d2faa1547c1088966f60f444e5732d1c6315fa64b"}, + {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:a61153714e637492de9935ae1a0c6c6e7a2d470eb8b6b97b76702ab04216460f"}, + {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96a93b08d7dd896734e6a022ce631906e773a9d10d397f26f96906583c7020a9"}, + {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f3667a2c2753aafa042dbe252ad57e562c2e2fe2e9556042b376561d660ad176"}, + {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc8c6e2b62987dbb48aa22f8c6d3d48e53d04629773d58a3f34342920cac251"}, + {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:f072d5e7400e5956a56ead71c92a975b0438b36e7694b3d10afe1af9ede456d9"}, + {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86d5abdddd19deef483a7e5628fff073d34b050e9edc462bf0246da04e754006"}, + {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f2036ffd7f4fdf9d7343a99aa569e2c5c8d15cbe915c9b4f493736b277bfae89"}, + {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dc3323ec405a0f5660dbf8dd632011e56a3a01040316a5cec9c062a0a930aadf"}, + {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:327b3903470ce66075952ab6f59abf5a3d81b6a34ca76f5afd0295a224079b96"}, + {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7367fe0d25d2f7f2e7e4da65527e03b5f05295e2f8d3189bdda6b5cdcf41079"}, + {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:49def23a08f459af4cd8e9170fda7ac191b8577108d355a1914c7c862dfa6eb9"}, + {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:31a6ebd1bad2fa9681482f56071d7fab7e21e5f2e280460ef90bf98d48a0addf"}, + {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33a79590a38ee0956e8e5f89dd7fb230743a2b630a83e6636440e90000274eaf"}, + {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf40664a8d647f09687d1ea9bcac959f78eaf8afca90cff7faa0a655307fe7b"}, + {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0a0e569caffc67614a033e2841b13a123537ff4959e10a571d3cb1fd07b1ed6c"}, + {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a7b0c733fad74be2864dfbc54be39e6efac1499ebeddacd9f8d3ca8cabc54816"}, + {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d7141f4d550c8b52a005087a54880727b52fbaeafc6850481f7a4aa9fd7fd32b"}, + {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9f7c977714608de03d5c98851ae2f95b86f06f6bca0a94868653ba284ba305b"}, + {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:905730fd9ca77ff0e7c7927b58542a07169ad75712363bab2281e5c2b807dc86"}, + {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9fc041966d5422b77c5814a42b495157bf3626902a50a803c4a815f85f035aa"}, + {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8e0de8daa007ab6d24237d53d9198ddcea43d30ae4d942565dc7d0b6decd791"}, + {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8321e36f3cfce490ad0ac278a6a79b5aad5542c9c7224c7d131fa156b093b0a9"}, + {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d1a55211a0f81a8701d48b2d2a71747e70a45bde042e1dabb7d5db024dbaa0"}, + {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:71ecafd6b994a19bdfa4d34c88b7917d7bc3fcae86b26007ff10dcea41a67afd"}, + {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:943fcdcd0aef6923e1da6472abf64f3cca6d47b6f733b464887821032889c673"}, + {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0da4f7288236f89c9d25a4ba56a9f3990ae92f1369dfdfa3dad2fc6c9df7de74"}, + {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0b037c4244002bcf7ae93b0e9eff3533b5feaa6b658ee64c432773afd6c0167"}, +] [package.dependencies] async-timeout = ">=3.0.0" @@ -155,6 +313,10 @@ description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] [[package]] name = "flake8" @@ -163,6 +325,10 @@ description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = ">=3.6.1" +files = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" @@ -176,6 +342,10 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] [[package]] name = "imagesize" @@ -184,6 +354,10 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] [[package]] name = "importlib-metadata" @@ -192,6 +366,10 @@ description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -209,6 +387,10 @@ description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "Jinja2" @@ -217,6 +399,10 @@ description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] MarkupSafe = ">=2.0" @@ -231,6 +417,48 @@ description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] [[package]] name = "mccabe" @@ -239,6 +467,10 @@ description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] [[package]] name = "mypy-extensions" @@ -247,6 +479,10 @@ description = "Experimental type system extensions for programs checked with the category = "dev" optional = false python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "packaging" @@ -255,6 +491,10 @@ description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -266,6 +506,10 @@ description = "Utility library for gitignore style pattern matching of file path category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, + {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, +] [[package]] name = "platformdirs" @@ -274,6 +518,10 @@ description = "A small Python module for determining appropriate platform-specif category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] [package.extras] docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] @@ -286,6 +534,10 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -301,6 +553,10 @@ description = "library with cross-python path, ini-parsing, io, code, log facili category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] [[package]] name = "pycodestyle" @@ -309,6 +565,10 @@ description = "Python style guide checker" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] [[package]] name = "pyflakes" @@ -317,6 +577,10 @@ description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] [[package]] name = "Pygments" @@ -325,6 +589,10 @@ description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] [package.extras] plugins = ["importlib-metadata"] @@ -336,6 +604,16 @@ description = "Python<->ObjC Interoperability Module" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pyobjc-core-8.5.1.tar.gz", hash = "sha256:f8592a12de076c27006700c4a46164478564fa33d7da41e7cbdd0a3bf9ddbccf"}, + {file = "pyobjc_core-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b62dcf987cc511188fc2aa5b4d3b9fd895361ea4984380463497ce4b0752ddf4"}, + {file = "pyobjc_core-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0accc653501a655f66c13f149a1d3d30e6cb65824edf852f7960a00c4f930d5b"}, + {file = "pyobjc_core-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f82b32affc898e9e5af041c1cecde2c99f2ce160b87df77f678c99f1550a4655"}, + {file = "pyobjc_core-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f7b2f6b6f3caeb882c658fe0c7098be2e8b79893d84daa8e636cb3e58a07df00"}, + {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:872c0202c911a5a2f1269261c168e36569f6ddac17e5d854ac19e581726570cc"}, + {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:21f92e231a4bae7f2d160d065f5afbf5e859a1e37f29d34ac12592205fc8c108"}, + {file = "pyobjc_core-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:315334dd09781129af6a39641248891c4caa57043901750b0139c6614ce84ec0"}, +] [[package]] name = "pyobjc-framework-Cocoa" @@ -344,6 +622,16 @@ description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pyobjc-framework-Cocoa-8.5.1.tar.gz", hash = "sha256:9a3de5cdb4644e85daf53f2ed912ef6c16ea5804a9e65552eafe62c2e139eb8c"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa572acc2628488a47be8d19f4701fc96fce7377cc4da18316e1e08c3918521a"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb3ae21c8d81b7f02a891088c623cef61bca89bd671eff58c632d2f926b649f3"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:88f08f5bd94c66d373d8413c1d08218aff4cff0b586e0cc4249b2284023e7577"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:063683b57e4bd88cb0f9631ae65d25ec4eecf427d2fe8d0c578f88da9c896f3f"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f8806ddfac40620fb27f185d0f8937e69e330617319ecc2eccf6b9c8451bdd1"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7733a9a201df9e0cc2a0cf7bf54d76bd7981cba9b599353b243e3e0c9eefec10"}, + {file = "pyobjc_framework_Cocoa-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f0ab227f99d3e25dd3db73f8cde0999914a5f0dd6a08600349d25f95eaa0da63"}, +] [package.dependencies] pyobjc-core = ">=8.5.1" @@ -355,8 +643,14 @@ description = "Wrappers for the framework CoreBluetooth on macOS" category = "main" optional = false python-versions = ">=3.6" - -[package.dependencies] +files = [ + {file = "pyobjc-framework-CoreBluetooth-8.5.1.tar.gz", hash = "sha256:b4f621fc3b5bf289db58e64fd746773b18297f87a0ffc5502de74f69133301c1"}, + {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bc720f2987a4d28dc73b13146e7c104d717100deb75c244da68f1d0849096661"}, + {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2167f22886beb5b3ae69e475e055403f28eab065c49a25e2b98b050b483be799"}, + {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aa9587a36eca143701731e8bb6c369148f8cc48c28168d41e7323828e5117f2d"}, +] + +[package.dependencies] pyobjc-core = ">=8.5.1" pyobjc-framework-Cocoa = ">=8.5.1" @@ -367,6 +661,16 @@ description = "Wrappers for libdispatch on macOS" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "pyobjc-framework-libdispatch-8.5.1.tar.gz", hash = "sha256:066fb34fceb326307559104d45532ec2c7b55426f9910b70dbefd5d1b8fd530f"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a316646ab30ba2a97bc828f8e27e7bb79efdf993d218a9c5118396b4f81dc762"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7730a29e4d9c7d8c2e8d9ffb60af0ab6699b2186296d2bff0a2dd54527578bc3"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:76208d9d2b0071df2950800495ac0300360bb5f25cbe9ab880b65cb809764979"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1ad9aa4773ff1d89bf4385c081824c4f8708b50e3ac2fe0a9d590153242c0f67"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81e1833bd26f15930faba678f9efdffafc79ec04e2ea8b6d1b88cafc0883af97"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:73226e224436eb6383e7a8a811c90ed597995adb155b4f46d727881a383ac550"}, + {file = "pyobjc_framework_libdispatch-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d115355ce446fc073c75cedfd7ab0a13958adda8e3a3b1e421e1f1e5f65640da"}, +] [package.dependencies] pyobjc-core = ">=8.5.1" @@ -378,6 +682,10 @@ description = "pyparsing module - Classes and methods to define and execute pars category = "dev" optional = false python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -389,6 +697,10 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, + {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, +] [package.dependencies] attrs = ">=19.2.0" @@ -410,6 +722,10 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, + {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, +] [package.dependencies] pytest = ">=6.1.0" @@ -425,6 +741,10 @@ description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} @@ -440,6 +760,10 @@ description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" +files = [ + {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, + {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, +] [[package]] name = "requests" @@ -448,6 +772,10 @@ description = "Python HTTP for Humans." category = "dev" optional = false python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] [package.dependencies] certifi = ">=2017.4.17" @@ -466,6 +794,10 @@ description = "This package provides 29 stemmers for 28 languages generated from category = "dev" optional = false python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] [[package]] name = "Sphinx" @@ -474,6 +806,10 @@ description = "Python documentation generator" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, + {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, +] [package.dependencies] alabaster = ">=0.7,<0.8" @@ -506,6 +842,10 @@ description = "Read the Docs theme for Sphinx" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +files = [ + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, +] [package.dependencies] docutils = "<0.18" @@ -521,6 +861,10 @@ description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -533,6 +877,10 @@ description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -545,6 +893,10 @@ description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML h category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -557,6 +909,10 @@ description = "A sphinx extension which renders display math in HTML via JavaScr category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] [package.extras] test = ["flake8", "mypy", "pytest"] @@ -568,6 +924,10 @@ description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp d category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -580,6 +940,10 @@ description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -592,6 +956,10 @@ description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typed-ast" @@ -600,6 +968,32 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] [[package]] name = "typing-extensions" @@ -608,6 +1002,10 @@ description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] [[package]] name = "urllib3" @@ -616,6 +1014,10 @@ description = "HTTP library with thread-safe connection pooling, file post, and category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +files = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] @@ -629,418 +1031,16 @@ description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, +] [package.extras] docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.7" content-hash = "a8845e45f46b310de561b52d1f17d3c6309c28513c16ac25a50ce751c1a6a116" - -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -async-timeout = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, -] -asynctest = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -Babel = [ - {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, - {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, -] -black = [ - {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, - {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, - {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, - {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, - {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, - {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, - {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, - {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, - {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, - {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, - {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, - {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, - {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, - {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, - {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, - {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, - {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, - {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, - {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, -] -bleak-winrt = [ - {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, - {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, - {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, - {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, - {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, - {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, - {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, - {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, - {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, - {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, - {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coverage = [ - {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, - {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, - {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, - {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, - {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, - {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, - {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, - {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, - {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, - {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, - {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, - {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, - {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, - {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, - {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, - {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, -] -dbus-fast = [ - {file = "dbus-fast-1.22.0.tar.gz", hash = "sha256:de6936cd4f70eb094051167d2faa1547c1088966f60f444e5732d1c6315fa64b"}, - {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:a61153714e637492de9935ae1a0c6c6e7a2d470eb8b6b97b76702ab04216460f"}, - {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96a93b08d7dd896734e6a022ce631906e773a9d10d397f26f96906583c7020a9"}, - {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f3667a2c2753aafa042dbe252ad57e562c2e2fe2e9556042b376561d660ad176"}, - {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc8c6e2b62987dbb48aa22f8c6d3d48e53d04629773d58a3f34342920cac251"}, - {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:f072d5e7400e5956a56ead71c92a975b0438b36e7694b3d10afe1af9ede456d9"}, - {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86d5abdddd19deef483a7e5628fff073d34b050e9edc462bf0246da04e754006"}, - {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f2036ffd7f4fdf9d7343a99aa569e2c5c8d15cbe915c9b4f493736b277bfae89"}, - {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dc3323ec405a0f5660dbf8dd632011e56a3a01040316a5cec9c062a0a930aadf"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:327b3903470ce66075952ab6f59abf5a3d81b6a34ca76f5afd0295a224079b96"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7367fe0d25d2f7f2e7e4da65527e03b5f05295e2f8d3189bdda6b5cdcf41079"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:49def23a08f459af4cd8e9170fda7ac191b8577108d355a1914c7c862dfa6eb9"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:31a6ebd1bad2fa9681482f56071d7fab7e21e5f2e280460ef90bf98d48a0addf"}, - {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33a79590a38ee0956e8e5f89dd7fb230743a2b630a83e6636440e90000274eaf"}, - {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf40664a8d647f09687d1ea9bcac959f78eaf8afca90cff7faa0a655307fe7b"}, - {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0a0e569caffc67614a033e2841b13a123537ff4959e10a571d3cb1fd07b1ed6c"}, - {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a7b0c733fad74be2864dfbc54be39e6efac1499ebeddacd9f8d3ca8cabc54816"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d7141f4d550c8b52a005087a54880727b52fbaeafc6850481f7a4aa9fd7fd32b"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9f7c977714608de03d5c98851ae2f95b86f06f6bca0a94868653ba284ba305b"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:905730fd9ca77ff0e7c7927b58542a07169ad75712363bab2281e5c2b807dc86"}, - {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9fc041966d5422b77c5814a42b495157bf3626902a50a803c4a815f85f035aa"}, - {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8e0de8daa007ab6d24237d53d9198ddcea43d30ae4d942565dc7d0b6decd791"}, - {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8321e36f3cfce490ad0ac278a6a79b5aad5542c9c7224c7d131fa156b093b0a9"}, - {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d1a55211a0f81a8701d48b2d2a71747e70a45bde042e1dabb7d5db024dbaa0"}, - {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:71ecafd6b994a19bdfa4d34c88b7917d7bc3fcae86b26007ff10dcea41a67afd"}, - {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:943fcdcd0aef6923e1da6472abf64f3cca6d47b6f733b464887821032889c673"}, - {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0da4f7288236f89c9d25a4ba56a9f3990ae92f1369dfdfa3dad2fc6c9df7de74"}, - {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0b037c4244002bcf7ae93b0e9eff3533b5feaa6b658ee64c432773afd6c0167"}, -] -docutils = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, -] -flake8 = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -Jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -MarkupSafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, - {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] -pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] -Pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, -] -pyobjc-core = [ - {file = "pyobjc-core-8.5.1.tar.gz", hash = "sha256:f8592a12de076c27006700c4a46164478564fa33d7da41e7cbdd0a3bf9ddbccf"}, - {file = "pyobjc_core-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b62dcf987cc511188fc2aa5b4d3b9fd895361ea4984380463497ce4b0752ddf4"}, - {file = "pyobjc_core-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0accc653501a655f66c13f149a1d3d30e6cb65824edf852f7960a00c4f930d5b"}, - {file = "pyobjc_core-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f82b32affc898e9e5af041c1cecde2c99f2ce160b87df77f678c99f1550a4655"}, - {file = "pyobjc_core-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f7b2f6b6f3caeb882c658fe0c7098be2e8b79893d84daa8e636cb3e58a07df00"}, - {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:872c0202c911a5a2f1269261c168e36569f6ddac17e5d854ac19e581726570cc"}, - {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:21f92e231a4bae7f2d160d065f5afbf5e859a1e37f29d34ac12592205fc8c108"}, - {file = "pyobjc_core-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:315334dd09781129af6a39641248891c4caa57043901750b0139c6614ce84ec0"}, -] -pyobjc-framework-Cocoa = [ - {file = "pyobjc-framework-Cocoa-8.5.1.tar.gz", hash = "sha256:9a3de5cdb4644e85daf53f2ed912ef6c16ea5804a9e65552eafe62c2e139eb8c"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa572acc2628488a47be8d19f4701fc96fce7377cc4da18316e1e08c3918521a"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb3ae21c8d81b7f02a891088c623cef61bca89bd671eff58c632d2f926b649f3"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:88f08f5bd94c66d373d8413c1d08218aff4cff0b586e0cc4249b2284023e7577"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:063683b57e4bd88cb0f9631ae65d25ec4eecf427d2fe8d0c578f88da9c896f3f"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f8806ddfac40620fb27f185d0f8937e69e330617319ecc2eccf6b9c8451bdd1"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7733a9a201df9e0cc2a0cf7bf54d76bd7981cba9b599353b243e3e0c9eefec10"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f0ab227f99d3e25dd3db73f8cde0999914a5f0dd6a08600349d25f95eaa0da63"}, -] -pyobjc-framework-CoreBluetooth = [ - {file = "pyobjc-framework-CoreBluetooth-8.5.1.tar.gz", hash = "sha256:b4f621fc3b5bf289db58e64fd746773b18297f87a0ffc5502de74f69133301c1"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bc720f2987a4d28dc73b13146e7c104d717100deb75c244da68f1d0849096661"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2167f22886beb5b3ae69e475e055403f28eab065c49a25e2b98b050b483be799"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aa9587a36eca143701731e8bb6c369148f8cc48c28168d41e7323828e5117f2d"}, -] -pyobjc-framework-libdispatch = [ - {file = "pyobjc-framework-libdispatch-8.5.1.tar.gz", hash = "sha256:066fb34fceb326307559104d45532ec2c7b55426f9910b70dbefd5d1b8fd530f"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a316646ab30ba2a97bc828f8e27e7bb79efdf993d218a9c5118396b4f81dc762"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7730a29e4d9c7d8c2e8d9ffb60af0ab6699b2186296d2bff0a2dd54527578bc3"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:76208d9d2b0071df2950800495ac0300360bb5f25cbe9ab880b65cb809764979"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1ad9aa4773ff1d89bf4385c081824c4f8708b50e3ac2fe0a9d590153242c0f67"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81e1833bd26f15930faba678f9efdffafc79ec04e2ea8b6d1b88cafc0883af97"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:73226e224436eb6383e7a8a811c90ed597995adb155b4f46d727881a383ac550"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d115355ce446fc073c75cedfd7ab0a13958adda8e3a3b1e421e1f1e5f65640da"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pytest = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, - {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, -] -pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] -pytz = [ - {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, - {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -Sphinx = [ - {file = "Sphinx-5.1.1-py3-none-any.whl", hash = "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693"}, - {file = "Sphinx-5.1.1.tar.gz", hash = "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89"}, -] -sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, - {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] From 4a6d1c495634f38449180bef113bcdb06a3cf783 Mon Sep 17 00:00:00 2001 From: K Date: Mon, 26 Dec 2022 20:44:10 -0500 Subject: [PATCH 27/45] bluezdbus: Add BLEAK_DBUS_AUTH_UID override --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/client.py | 6 ++- bleak/backends/bluezdbus/manager.py | 4 +- bleak/backends/bluezdbus/utils.py | 16 +++++++ docs/backends/linux.rst | 9 ++++ poetry.lock | 65 ++++++++++++++--------------- pyproject.toml | 2 +- 7 files changed, 64 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 279c7d14..f2d32aa2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ and this project adheres to `Semantic Versioning bo # Each BLE connection session needs a new D-Bus connection to avoid a # BlueZ quirk where notifications are automatically enabled on reconnect. self._bus = await MessageBus( - bus_type=BusType.SYSTEM, negotiate_unix_fd=True + bus_type=BusType.SYSTEM, + negotiate_unix_fd=True, + auth=get_dbus_authenticator(), ).connect() def on_connected_changed(connected: bool) -> None: diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index cfb826ed..90151e12 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -36,7 +36,7 @@ from .descriptor import BleakGATTDescriptorBlueZDBus from .service import BleakGATTServiceBlueZDBus from .signals import MatchRules, add_match -from .utils import assert_reply +from .utils import assert_reply, get_dbus_authenticator logger = logging.getLogger(__name__) @@ -187,7 +187,7 @@ async def async_init(self): # We need to create a new MessageBus each time as # dbus-next will destory the underlying file descriptors # when the previous one is closed in its finalizer. - bus = MessageBus(bus_type=BusType.SYSTEM) + bus = MessageBus(bus_type=BusType.SYSTEM, auth=get_dbus_authenticator()) await bus.connect() try: diff --git a/bleak/backends/bluezdbus/utils.py b/bleak/backends/bluezdbus/utils.py index 79ec8b16..5b0eeeb8 100644 --- a/bleak/backends/bluezdbus/utils.py +++ b/bleak/backends/bluezdbus/utils.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- +import os import re +from dbus_fast.auth import AuthExternal from dbus_fast.constants import MessageType from dbus_fast.message import Message @@ -43,3 +45,17 @@ def bdaddr_from_device_path(device_path: str) -> str: A Bluetooth address as a string. """ return ":".join(device_path[-17:].split("_")) + + +def get_dbus_authenticator(): + uid = None + try: + uid = int(os.environ.get("BLEAK_DBUS_AUTH_UID", "")) + except ValueError: + pass + + auth = None + if uid is not None: + auth = AuthExternal(uid=uid) + + return auth diff --git a/docs/backends/linux.rst b/docs/backends/linux.rst index f650766f..a967bdba 100644 --- a/docs/backends/linux.rst +++ b/docs/backends/linux.rst @@ -41,6 +41,15 @@ individual Bleak objects should not be shared between event loops. Otherwise, RuntimeErrors similar to ``[...] got Future attached to a different loop`` will be thrown. +D-Bus Authentication +-------------------- + +Connecting to the host DBus from within a user namespace will fail. This is +because the remapped UID will not match the UID that the hosts sees. To work +around this, you can hardcode a UID with the `BLEAK_DBUS_AUTH_UID` environment +variable. + + API --- diff --git a/poetry.lock b/poetry.lock index aa45924f..480713f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -264,47 +264,44 @@ toml = ["tomli"] [[package]] name = "dbus-fast" -version = "1.22.0" +version = "1.83.1" description = "A faster version of dbus-next" category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "dbus-fast-1.22.0.tar.gz", hash = "sha256:de6936cd4f70eb094051167d2faa1547c1088966f60f444e5732d1c6315fa64b"}, - {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:a61153714e637492de9935ae1a0c6c6e7a2d470eb8b6b97b76702ab04216460f"}, - {file = "dbus_fast-1.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96a93b08d7dd896734e6a022ce631906e773a9d10d397f26f96906583c7020a9"}, - {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f3667a2c2753aafa042dbe252ad57e562c2e2fe2e9556042b376561d660ad176"}, - {file = "dbus_fast-1.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc8c6e2b62987dbb48aa22f8c6d3d48e53d04629773d58a3f34342920cac251"}, - {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:f072d5e7400e5956a56ead71c92a975b0438b36e7694b3d10afe1af9ede456d9"}, - {file = "dbus_fast-1.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86d5abdddd19deef483a7e5628fff073d34b050e9edc462bf0246da04e754006"}, - {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f2036ffd7f4fdf9d7343a99aa569e2c5c8d15cbe915c9b4f493736b277bfae89"}, - {file = "dbus_fast-1.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dc3323ec405a0f5660dbf8dd632011e56a3a01040316a5cec9c062a0a930aadf"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:327b3903470ce66075952ab6f59abf5a3d81b6a34ca76f5afd0295a224079b96"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7367fe0d25d2f7f2e7e4da65527e03b5f05295e2f8d3189bdda6b5cdcf41079"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:49def23a08f459af4cd8e9170fda7ac191b8577108d355a1914c7c862dfa6eb9"}, - {file = "dbus_fast-1.22.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:31a6ebd1bad2fa9681482f56071d7fab7e21e5f2e280460ef90bf98d48a0addf"}, - {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33a79590a38ee0956e8e5f89dd7fb230743a2b630a83e6636440e90000274eaf"}, - {file = "dbus_fast-1.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf40664a8d647f09687d1ea9bcac959f78eaf8afca90cff7faa0a655307fe7b"}, - {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0a0e569caffc67614a033e2841b13a123537ff4959e10a571d3cb1fd07b1ed6c"}, - {file = "dbus_fast-1.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a7b0c733fad74be2864dfbc54be39e6efac1499ebeddacd9f8d3ca8cabc54816"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d7141f4d550c8b52a005087a54880727b52fbaeafc6850481f7a4aa9fd7fd32b"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9f7c977714608de03d5c98851ae2f95b86f06f6bca0a94868653ba284ba305b"}, - {file = "dbus_fast-1.22.0-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:905730fd9ca77ff0e7c7927b58542a07169ad75712363bab2281e5c2b807dc86"}, - {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a9fc041966d5422b77c5814a42b495157bf3626902a50a803c4a815f85f035aa"}, - {file = "dbus_fast-1.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8e0de8daa007ab6d24237d53d9198ddcea43d30ae4d942565dc7d0b6decd791"}, - {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8321e36f3cfce490ad0ac278a6a79b5aad5542c9c7224c7d131fa156b093b0a9"}, - {file = "dbus_fast-1.22.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d1a55211a0f81a8701d48b2d2a71747e70a45bde042e1dabb7d5db024dbaa0"}, - {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:71ecafd6b994a19bdfa4d34c88b7917d7bc3fcae86b26007ff10dcea41a67afd"}, - {file = "dbus_fast-1.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:943fcdcd0aef6923e1da6472abf64f3cca6d47b6f733b464887821032889c673"}, - {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0da4f7288236f89c9d25a4ba56a9f3990ae92f1369dfdfa3dad2fc6c9df7de74"}, - {file = "dbus_fast-1.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0b037c4244002bcf7ae93b0e9eff3533b5feaa6b658ee64c432773afd6c0167"}, + {file = "dbus_fast-1.83.1-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:4f85665403b0a942aa48b5fa7cabd407665234542dd734acf4587a35027fb187"}, + {file = "dbus_fast-1.83.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3806b6a9532747168cffc8fc593e80f08e316959c8b7c93dad438acd95bc98a6"}, + {file = "dbus_fast-1.83.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7befec6a1789762c899a8359a9136c10cbd97d58e3b6f1d8925e8db204458de"}, + {file = "dbus_fast-1.83.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f761157fdaed7821c610f1bf8c874d5985eb0f6844aa64dbdd40fdb3a728a017"}, + {file = "dbus_fast-1.83.1-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:95e0d9a6ccb96daf3c465cebe334dd5f00e17f320e03ce5f737b7012a524f023"}, + {file = "dbus_fast-1.83.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da7ad7d2b2afadb23bf1b14937ed139acafd491905b06218ffb39a19949194c"}, + {file = "dbus_fast-1.83.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d7eee5b69ca9fbad908513bd55efd6bd2090854d2febada886d59a0c01f221d"}, + {file = "dbus_fast-1.83.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6da098cb98520f73ff54e1f54b381bb6b3d8806e31f1908ffa1ff7f7a4f5266b"}, + {file = "dbus_fast-1.83.1-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:4f3150c116d9be7c7f3fd54de2cf55717ed2b3c9ec8533429a7db3025fd286b6"}, + {file = "dbus_fast-1.83.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361d4a5f8f42ff46aca4d8ab148cea5b8d8a439fd41e160360b6c8cb2cb014a3"}, + {file = "dbus_fast-1.83.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8d362f0d5d3e3e39f96a8b3df1090f6147fc96df30fd29488f9bc0f357054e29"}, + {file = "dbus_fast-1.83.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:747875df5a56f67a4561abc1e4aa17c51d62d5c9051e7a5eafa91f7a956fefc4"}, + {file = "dbus_fast-1.83.1-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:68f6d55b64d9393f88526b7b9c6805dcfa3d1b8791160d06c125f1e9d4e176b3"}, + {file = "dbus_fast-1.83.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee5f3663a17db92980056d9bc8d40b41cd4601badaa57f7caef77e7dda33ce3"}, + {file = "dbus_fast-1.83.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:73d7d3194741a3fb889797fb26762e33c0697284175903e46f95c11598472823"}, + {file = "dbus_fast-1.83.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f23d8c71cb99f9d094b1e63b333cc13edd3dc167cb9116b55775c4fb154d61eb"}, + {file = "dbus_fast-1.83.1-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0df53713f7917b963fdeb5125afb09aeac3e98e3dd75483f52e488cf63b87fc6"}, + {file = "dbus_fast-1.83.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40940fc848d76eb598631f0237fbea9b4bf1ed5059f51fd0fcc31424b4f4b5e4"}, + {file = "dbus_fast-1.83.1-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:edd4ffbcbd0b1f05c2f408b20721c4393eda9e599c9f6811c4c79534b66cb1fa"}, + {file = "dbus_fast-1.83.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c7304b5a1a22882b7fe3b9624b182e5f8888a42fbca68217067a385b37f860a3"}, + {file = "dbus_fast-1.83.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ed127a1c4db21ed8ac0535c059490ab7981dfa22b13d142b5c7d8c42bdba840"}, + {file = "dbus_fast-1.83.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e3e86f73c8ad425256bbb729503325104b0e12c4c3f77fbfa87cf4470596cae5"}, + {file = "dbus_fast-1.83.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa0956bf1c372ad07d682c11f47e2d09ee60fce7d51682e7eef4b2696bd3ca"}, + {file = "dbus_fast-1.83.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b2aa1afad9d7acdd0e0fecff8c43e5dcc491a5e8fc38a1aa733ee634f2ca1e8f"}, + {file = "dbus_fast-1.83.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a46e11ab8b19002458f8cf0712ab90ee658a3b04873731f8d4e95ec7caa1c818"}, + {file = "dbus_fast-1.83.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:bedbbb9692da11d0bc43a305655d82c18dbb876001133319573d40a0de38f883"}, + {file = "dbus_fast-1.83.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95f7cdcf1773ab3e9817a0a476791d23faa8d0fc2cab3e6c7537c17898a50c0"}, + {file = "dbus_fast-1.83.1.tar.gz", hash = "sha256:32269bee5d30b3ccc800eccac0b06bb233c514efd2871ebc649f11a987ee8d33"}, ] [package.dependencies] -async-timeout = ">=3.0.0" - -[package.extras] -docs = ["Sphinx (>=5.1.1,<6.0.0)", "myst-parser (>=0.18.0,<0.19.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinxcontrib-fulltoc (>=1.2.0,<2.0.0)"] +async-timeout = {version = ">=3.0.0", markers = "python_version < \"3.11\""} [[package]] name = "docutils" @@ -1043,4 +1040,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "a8845e45f46b310de561b52d1f17d3c6309c28513c16ac25a50ce751c1a6a116" +content-hash = "6eb99c7a7f69b812db4820435d424c92335eef62f345ac47c7415ed19ac52144" diff --git a/pyproject.toml b/pyproject.toml index 930bf624..852e59c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ pyobjc-core = { version = "^8.5.1", markers = "platform_system=='Darwin'" } pyobjc-framework-CoreBluetooth = { version = "^8.5.1", markers = "platform_system=='Darwin'" } pyobjc-framework-libdispatch = { version = "^8.5.1", markers = "platform_system=='Darwin'" } bleak-winrt = { version = "^1.2.0", markers = "platform_system=='Windows'" } -dbus-fast = { version = "^1.22.0", markers = "platform_system == 'Linux'" } +dbus-fast = { version = "^1.83.0", markers = "platform_system == 'Linux'" } [tool.poetry.group.docs.dependencies] Sphinx = { version = "^5.1.1", python = ">=3.8" } From 648dc2e672dfc4369e438d8f13adde19712a48a9 Mon Sep 17 00:00:00 2001 From: x11x <28614156+x11x@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:29:36 +1000 Subject: [PATCH 28/45] docs: Change code example for BleakClient.start_notify callback (#1201) The type of the first argument to the callback changed from `int` to `BleakGATTCharacteristic` in 0.18.0. The code example is still type hinting it as an int. --- bleak/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/__init__.py b/bleak/__init__.py index 2ced4b02..825b967e 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -645,7 +645,7 @@ async def start_notify( .. code-block:: python - def callback(sender: int, data: bytearray): + def callback(sender: BleakGATTCharacteristic, data: bytearray): print(f"{sender}: {data}") client.start_notify(char_uuid, callback) From 1c382a1a556b3ac677ae81eeddb48b6d1ff41667 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 12:27:48 -0600 Subject: [PATCH 29/45] winrt/client: fix leaking service objects If `get_servcies()` is cancelled or otherwise raises an error, the WinRT service objects need to be disposed by calling `close()`. Otherwise, it is going to cause problems for the Windows Bluetooth stack. --- CHANGELOG.rst | 6 +-- bleak/backends/winrt/client.py | 89 ++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2d32aa2..093a76c2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,13 +30,11 @@ Changed Fixed ----- +* Fixed invalid UTF*8 in ``uuids.uuid16_dict``. * Fixed ``AttributeError`` in ``_ensure_success`` in WinRT backend. * Fixed ``BleakScanner.stop()`` can raise ``BleakDBusError`` with ``org.bluez.Error.NotReady`` in BlueZ backend. * Fixed ``BleakScanner.stop()`` hanging in WinRT backend when Bluetooth is disabled. - -Fixed ------ -- Fixed invalid UTF-8 in ``uuids.uuid16_dict``. +* Fixed leaking services when ``get_services()`` is cancelled in WinRT backend. `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 5241eec8..e7c50414 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -642,50 +642,79 @@ async def get_services( if cache_mode is not None: args.append(cache_mode) + logger.debug("calling get_gatt_services_async") + + def dispose_on_cancel(future): + if future._cancel_requested and future._result is not None: + logger.debug("disposing services object because of cancel") + for service in future._result: + service.close() + + future = FutureLike(self._requester.get_gatt_services_async(*srv_args)) + future.add_done_callback(dispose_on_cancel) + services: Sequence[GattDeviceService] = _ensure_success( - await FutureLike(self._requester.get_gatt_services_async(*srv_args)), + await future, "services", "Could not get GATT services", ) - for service in services: - # Windows returns an ACCESS_DENIED error when trying to enumerate - # characteristics of services used by the OS, like the HID service - # so we have to exclude those services. - if service.uuid in _ACCESS_DENIED_SERVICES: - continue + logger.debug("returned from get_gatt_services_async") - new_services.add_service(BleakGATTServiceWinRT(service)) + try: + for service in services: + # Windows returns an ACCESS_DENIED error when trying to enumerate + # characteristics of services used by the OS, like the HID service + # so we have to exclude those services. + if service.uuid in _ACCESS_DENIED_SERVICES: + continue - characteristics: Sequence[GattCharacteristic] = _ensure_success( - await FutureLike(service.get_characteristics_async(*args)), - "characteristics", - f"Could not get GATT characteristics for {service}", - ) + new_services.add_service(BleakGATTServiceWinRT(service)) - for characteristic in characteristics: - new_services.add_characteristic( - BleakGATTCharacteristicWinRT( - characteristic, self._session.max_pdu_size - 3 - ) - ) + logger.debug("calling get_characteristics_async") - descriptors: Sequence[GattDescriptor] = _ensure_success( - await FutureLike(characteristic.get_descriptors_async(*args)), - "descriptors", - f"Could not get GATT descriptors for {service}", + characteristics: Sequence[GattCharacteristic] = _ensure_success( + await FutureLike(service.get_characteristics_async(*args)), + "characteristics", + f"Could not get GATT characteristics for {service}", ) - for descriptor in descriptors: - new_services.add_descriptor( - BleakGATTDescriptorWinRT( - descriptor, - str(characteristic.uuid), - characteristic.attribute_handle, + logger.debug("returned from get_characteristics_async") + + for characteristic in characteristics: + new_services.add_characteristic( + BleakGATTCharacteristicWinRT( + characteristic, self._session.max_pdu_size - 3 ) ) - return new_services + logger.debug("calling get_descriptors_async") + + descriptors: Sequence[GattDescriptor] = _ensure_success( + await FutureLike(characteristic.get_descriptors_async(*args)), + "descriptors", + f"Could not get GATT descriptors for {service}", + ) + + logger.debug("returned from get_descriptors_async") + + for descriptor in descriptors: + new_services.add_descriptor( + BleakGATTDescriptorWinRT( + descriptor, + str(characteristic.uuid), + characteristic.attribute_handle, + ) + ) + + return new_services + except BaseException: + # Don't leak services. WinRT is quite particular about services + # being closed. + logger.debug("disposing service objects") + for service in services: + service.close() + raise # I/O methods From 537c48f410a7debc88e1f4e9a4edd199e1213536 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 13:51:38 -0600 Subject: [PATCH 30/45] winrt/client: move services_changed_event.clear() This ensure that if the event is set while waiting for get_services_task that we don't restart unnecessarily. --- bleak/backends/winrt/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index e7c50414..79360771 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -391,6 +391,7 @@ def max_pdu_size_changed_handler(sender: GattSession, args): async with async_timeout(timeout): while True: + services_changed_event.clear() services_changed_event_task = asyncio.create_task( services_changed_event.wait() ) @@ -421,7 +422,6 @@ def max_pdu_size_changed_handler(sender: GattSession, args): self.address, ) service_cache_mode = BluetoothCacheMode.CACHED - services_changed_event.clear() # ensure the task ran to completion to avoid OSError # on next call to get_services() From 261b39f6ad0f0abec53612c9f66a1b84d2f8be1f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Nov 2022 14:13:02 -0600 Subject: [PATCH 31/45] winrt:client: disable retry on services changed The change from 4756c1ed seems to be causing problems for users. It appears that Windows doesn't handle cancellation of async methods very well which causes hangs waiting for the cancelled methods to return or the next method call hanging because the previous left the Windows Bluetooth stack in a bad state. This leaves the workaround in the code in case we still need it but disables it for now. A WinRTClientArgs option could be added later to optionally use it. --- bleak/backends/winrt/client.py | 75 +++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 79360771..a922d436 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -197,6 +197,7 @@ def __init__( # os-specific options self._use_cached_services = winrt.get("use_cached_services") self._address_type = winrt.get("address_type", kwargs.get("address_type")) + self._retry_on_services_changed = False self._session_services_changed_token: Optional[EventRegistrationToken] = None self._session_status_changed_token: Optional[EventRegistrationToken] = None @@ -390,47 +391,53 @@ def max_pdu_size_changed_handler(sender: GattSession, args): service_cache_mode = cache_mode async with async_timeout(timeout): - while True: - services_changed_event.clear() - services_changed_event_task = asyncio.create_task( - services_changed_event.wait() - ) + if self._retry_on_services_changed: + while True: + services_changed_event.clear() + services_changed_event_task = asyncio.create_task( + services_changed_event.wait() + ) - get_services_task = asyncio.create_task( - self.get_services( - service_cache_mode=service_cache_mode, - cache_mode=cache_mode, + get_services_task = asyncio.create_task( + self.get_services( + service_cache_mode=service_cache_mode, + cache_mode=cache_mode, + ) ) - ) - _, pending = await asyncio.wait( - [services_changed_event_task, get_services_task], - return_when=asyncio.FIRST_COMPLETED, - ) + _, pending = await asyncio.wait( + [services_changed_event_task, get_services_task], + return_when=asyncio.FIRST_COMPLETED, + ) - for p in pending: - p.cancel() + for p in pending: + p.cancel() - if not services_changed_event.is_set(): - # services did not change while getting services, - # so this is the final result - self.services = get_services_task.result() - break + if not services_changed_event.is_set(): + # services did not change while getting services, + # so this is the final result + self.services = get_services_task.result() + break - logger.debug( - "%s: restarting get services due to services changed event", - self.address, + logger.debug( + "%s: restarting get services due to services changed event", + self.address, + ) + service_cache_mode = BluetoothCacheMode.CACHED + + # ensure the task ran to completion to avoid OSError + # on next call to get_services() + try: + await get_services_task + except OSError: + pass + except asyncio.CancelledError: + pass + else: + self.services = await self.get_services( + service_cache_mode=service_cache_mode, + cache_mode=cache_mode, ) - service_cache_mode = BluetoothCacheMode.CACHED - - # ensure the task ran to completion to avoid OSError - # on next call to get_services() - try: - await get_services_task - except OSError: - pass - except asyncio.CancelledError: - pass # a connection may not be made until we request info from the # device, so we have to get services before the GATT session From ea7f96460c7aadc7e5e3ac452ddbc337248d4ae8 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 13:14:01 -0600 Subject: [PATCH 32/45] winrt/scanner: drop waiting for matching scan rsp This removes the logic that skips the detection callback when expecting a scan response. It is valid for devices to broadcast ADV_IND but not respond to a SCAN_REQ (there is no flag that means connectable but not scannable so ADV_IND is the only choice in this case). Fixes: https://github.com/hbldh/bleak/issues/1211 --- CHANGELOG.rst | 2 ++ bleak/__init__.py | 7 +++++++ bleak/backends/winrt/scanner.py | 15 --------------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 093a76c2..2edf97bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,8 @@ Fixed * Fixed ``BleakScanner.stop()`` can raise ``BleakDBusError`` with ``org.bluez.Error.NotReady`` in BlueZ backend. * Fixed ``BleakScanner.stop()`` hanging in WinRT backend when Bluetooth is disabled. * Fixed leaking services when ``get_services()`` is cancelled in WinRT backend. +* Fixed WinRT scanner never calling ``detection_callback`` when a device does + not send a scan response. Fixes #1211. `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/__init__.py b/bleak/__init__.py index 825b967e..16da0e37 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -101,6 +101,13 @@ class BleakScanner: **kwargs: Additional args for backwards compatibility. + .. tip:: The first received advertisement in ``detection_callback`` may or + may not include scan response data if the remote device supports it. + Be sure to take this into account when handing the callback. For example, + the scan response often contains the local name of the device so if you + are matching a device based on other data but want to display the local + name to the user, be sure to wait for ``adv_data.local_name is not None``. + .. versionchanged:: 0.15.0 ``detection_callback``, ``service_uuids`` and ``scanning_mode`` are no longer keyword-only. Added ``bluez`` parameter. diff --git a/bleak/backends/winrt/scanner.py b/bleak/backends/winrt/scanner.py index 9e2f15cf..9ee745b0 100644 --- a/bleak/backends/winrt/scanner.py +++ b/bleak/backends/winrt/scanner.py @@ -124,21 +124,6 @@ def _received_handler( self._advertisement_pairs[bdaddr] = raw_data - # if we are expecting scan response and we haven't received both a - # regular advertisement and a scan response, don't do callbacks yet, - # wait until we have both instead so we get a callback with partial data - - if (raw_data.adv is None or raw_data.scan is None) and ( - event_args.advertisement_type - in [ - BluetoothLEAdvertisementType.CONNECTABLE_UNDIRECTED, - BluetoothLEAdvertisementType.SCANNABLE_UNDIRECTED, - BluetoothLEAdvertisementType.SCAN_RESPONSE, - ] - ): - logger.debug("skipping callback, waiting for scan response") - return - uuids = [] mfg_data = {} service_data = {} From eaa160cdf58b4d6acdac360cf94d2e30b35d7ae5 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 14:37:19 -0600 Subject: [PATCH 33/45] github: set SYSTEM_VERSION_COMPAT=0 This fixes macos builds with older versions of Python. --- .github/workflows/build_and_test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c23f6f8b..ea19753b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -25,6 +25,9 @@ jobs: - name: Create virtual environment run: pipx run poetry env use '${{ steps.py.outputs.python-path }}' - name: Install dependencies + # work around https://github.com/python-poetry/poetry/issues/7161 + env: + SYSTEM_VERSION_COMPAT: 0 run: pipx run poetry install --only main,test - name: Test with pytest run: | From 026e5e515ccbce75876a7fedb0a79cd05a224a78 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 19 Dec 2022 10:31:43 -0600 Subject: [PATCH 34/45] pyproject: update to pyobjc 9.x --- CHANGELOG.rst | 1 + poetry.lock | 83 ++++++++++++++++++++++++-------------------------- pyproject.toml | 6 ++-- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2edf97bc..697160c5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,7 @@ Changed * ``BaseBleakClient.services`` is now ``None`` instead of empty service collection until services are discovered. * Include thread name in ``BLEAK_LOGGING`` output. Merged #1144. +* Updated PyObjC dependency on macOS to v9.x. Fixed ----- diff --git a/poetry.lock b/poetry.lock index 480713f5..6a1d8b12 100644 --- a/poetry.lock +++ b/poetry.lock @@ -596,81 +596,78 @@ plugins = ["importlib-metadata"] [[package]] name = "pyobjc-core" -version = "8.5.1" +version = "9.0.1" description = "Python<->ObjC Interoperability Module" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyobjc-core-8.5.1.tar.gz", hash = "sha256:f8592a12de076c27006700c4a46164478564fa33d7da41e7cbdd0a3bf9ddbccf"}, - {file = "pyobjc_core-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b62dcf987cc511188fc2aa5b4d3b9fd895361ea4984380463497ce4b0752ddf4"}, - {file = "pyobjc_core-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0accc653501a655f66c13f149a1d3d30e6cb65824edf852f7960a00c4f930d5b"}, - {file = "pyobjc_core-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f82b32affc898e9e5af041c1cecde2c99f2ce160b87df77f678c99f1550a4655"}, - {file = "pyobjc_core-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f7b2f6b6f3caeb882c658fe0c7098be2e8b79893d84daa8e636cb3e58a07df00"}, - {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:872c0202c911a5a2f1269261c168e36569f6ddac17e5d854ac19e581726570cc"}, - {file = "pyobjc_core-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:21f92e231a4bae7f2d160d065f5afbf5e859a1e37f29d34ac12592205fc8c108"}, - {file = "pyobjc_core-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:315334dd09781129af6a39641248891c4caa57043901750b0139c6614ce84ec0"}, + {file = "pyobjc-core-9.0.1.tar.gz", hash = "sha256:5ce1510bb0bdff527c597079a42b2e13a19b7592e76850be7960a2775b59c929"}, + {file = "pyobjc_core-9.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b614406d46175b1438a9596b664bf61952323116704d19bc1dea68052a0aad98"}, + {file = "pyobjc_core-9.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd397e729f6271c694fb70df8f5d3d3c9b2f2b8ac02fbbdd1757ca96027b94bb"}, + {file = "pyobjc_core-9.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d919934eaa6d1cf1505ff447a5c2312be4c5651efcb694eb9f59e86f5bd25e6b"}, + {file = "pyobjc_core-9.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67d67ca8b164f38ceacce28a18025845c3ec69613f3301935d4d2c4ceb22e3fd"}, + {file = "pyobjc_core-9.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:39d11d71f6161ac0bd93cffc8ea210bb0178b56d16a7408bf74283d6ecfa7430"}, + {file = "pyobjc_core-9.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25be1c4d530e473ed98b15063b8d6844f0733c98914de6f09fe1f7652b772bbc"}, ] [[package]] -name = "pyobjc-framework-Cocoa" -version = "8.5.1" +name = "pyobjc-framework-cocoa" +version = "9.0.1" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyobjc-framework-Cocoa-8.5.1.tar.gz", hash = "sha256:9a3de5cdb4644e85daf53f2ed912ef6c16ea5804a9e65552eafe62c2e139eb8c"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa572acc2628488a47be8d19f4701fc96fce7377cc4da18316e1e08c3918521a"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb3ae21c8d81b7f02a891088c623cef61bca89bd671eff58c632d2f926b649f3"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:88f08f5bd94c66d373d8413c1d08218aff4cff0b586e0cc4249b2284023e7577"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:063683b57e4bd88cb0f9631ae65d25ec4eecf427d2fe8d0c578f88da9c896f3f"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f8806ddfac40620fb27f185d0f8937e69e330617319ecc2eccf6b9c8451bdd1"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7733a9a201df9e0cc2a0cf7bf54d76bd7981cba9b599353b243e3e0c9eefec10"}, - {file = "pyobjc_framework_Cocoa-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f0ab227f99d3e25dd3db73f8cde0999914a5f0dd6a08600349d25f95eaa0da63"}, + {file = "pyobjc-framework-Cocoa-9.0.1.tar.gz", hash = "sha256:a8b53b3426f94307a58e2f8214dc1094c19afa9dcb96f21be12f937d968b2df3"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f94b0f92a62b781e633e58f09bcaded63d612f9b1e15202f5f372ea59e4aebd"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f062c3bb5cc89902e6d164aa9a66ffc03638645dd5f0468b6f525ac997c86e51"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0b374c0a9d32ba4fc5610ab2741cb05a005f1dfb82a47dbf2dbb2b3a34b73ce5"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8928080cebbce91ac139e460d3dfc94c7cb6935be032dcae9c0a51b247f9c2d9"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9d2bd86a0a98d906f762f5dc59f2fc67cce32ae9633b02ff59ac8c8a33dd862d"}, + {file = "pyobjc_framework_Cocoa-9.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2a41053cbcee30e1e8914efa749c50b70bf782527d5938f2bc2a6393740969ce"}, ] [package.dependencies] -pyobjc-core = ">=8.5.1" +pyobjc-core = ">=9.0.1" [[package]] -name = "pyobjc-framework-CoreBluetooth" -version = "8.5.1" +name = "pyobjc-framework-corebluetooth" +version = "9.0.1" description = "Wrappers for the framework CoreBluetooth on macOS" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyobjc-framework-CoreBluetooth-8.5.1.tar.gz", hash = "sha256:b4f621fc3b5bf289db58e64fd746773b18297f87a0ffc5502de74f69133301c1"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bc720f2987a4d28dc73b13146e7c104d717100deb75c244da68f1d0849096661"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2167f22886beb5b3ae69e475e055403f28eab065c49a25e2b98b050b483be799"}, - {file = "pyobjc_framework_CoreBluetooth-8.5.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aa9587a36eca143701731e8bb6c369148f8cc48c28168d41e7323828e5117f2d"}, + {file = "pyobjc-framework-CoreBluetooth-9.0.1.tar.gz", hash = "sha256:bf008d7bfe13cda12a43ed82346acfad262e90824086b145394c154531b51841"}, + {file = "pyobjc_framework_CoreBluetooth-9.0.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:62f15fc6e1d864a5e6afd26fe01947e5879b5322af23719d988981ca65b34a30"}, + {file = "pyobjc_framework_CoreBluetooth-9.0.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:15673b480b3695aba87ce9574154bd1997f03a784969642b0da5e990e9679f48"}, + {file = "pyobjc_framework_CoreBluetooth-9.0.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3560c55de7799cd7468b1282d6c2fca4823896ffbcb7d53be69b55c01a44592e"}, ] [package.dependencies] -pyobjc-core = ">=8.5.1" -pyobjc-framework-Cocoa = ">=8.5.1" +pyobjc-core = ">=9.0.1" +pyobjc-framework-Cocoa = ">=9.0.1" [[package]] name = "pyobjc-framework-libdispatch" -version = "8.5.1" +version = "9.0.1" description = "Wrappers for libdispatch on macOS" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyobjc-framework-libdispatch-8.5.1.tar.gz", hash = "sha256:066fb34fceb326307559104d45532ec2c7b55426f9910b70dbefd5d1b8fd530f"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a316646ab30ba2a97bc828f8e27e7bb79efdf993d218a9c5118396b4f81dc762"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7730a29e4d9c7d8c2e8d9ffb60af0ab6699b2186296d2bff0a2dd54527578bc3"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:76208d9d2b0071df2950800495ac0300360bb5f25cbe9ab880b65cb809764979"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1ad9aa4773ff1d89bf4385c081824c4f8708b50e3ac2fe0a9d590153242c0f67"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81e1833bd26f15930faba678f9efdffafc79ec04e2ea8b6d1b88cafc0883af97"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:73226e224436eb6383e7a8a811c90ed597995adb155b4f46d727881a383ac550"}, - {file = "pyobjc_framework_libdispatch-8.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d115355ce446fc073c75cedfd7ab0a13958adda8e3a3b1e421e1f1e5f65640da"}, + {file = "pyobjc-framework-libdispatch-9.0.1.tar.gz", hash = "sha256:988c4c8608f2059c8b80ac520bc8d20a46ff85f65c50749110c45df610141fce"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6cd32fea76165157a623ef8871f83cfa627ea2e878417704d6ac9c284c4211d5"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a0f8ba6b498a095edef07e7a55f11dda3a6b37706caaa0f954f297c9aa1122e"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:906f4e705b40ea878d0a7feddddac85965f9709f7a951c3d5459260d48efd56f"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0bd94e697e3739eaf093a9b6f5be9a2cc34faa96c66cc21d2c42a996a3b01242"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7f9798c599acdd21251f57970bafabccc7fa723ae2a6d1fbe82f99ecfa3f7cf9"}, + {file = "pyobjc_framework_libdispatch-9.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:10a877b31960ee958873e5228f7b588c664014be8ad4d13a76a764482a18bf41"}, ] [package.dependencies] -pyobjc-core = ">=8.5.1" +pyobjc-core = ">=9.0.1" [[package]] name = "pyparsing" @@ -1040,4 +1037,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "6eb99c7a7f69b812db4820435d424c92335eef62f345ac47c7415ed19ac52144" +content-hash = "140c9c2d6407cbee7866fba10daa0b9db4138a3aadab89e1c06a20c28095c482" diff --git a/pyproject.toml b/pyproject.toml index 852e59c1..5458d965 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,9 +25,9 @@ classifiers = [ python = "^3.7" async-timeout = { version = ">= 3.0.0, < 5", python = "<3.11" } typing-extensions = { version = "^4.2.0", python = "<3.8" } -pyobjc-core = { version = "^8.5.1", markers = "platform_system=='Darwin'" } -pyobjc-framework-CoreBluetooth = { version = "^8.5.1", markers = "platform_system=='Darwin'" } -pyobjc-framework-libdispatch = { version = "^8.5.1", markers = "platform_system=='Darwin'" } +pyobjc-core = { version = "^9.0.1", markers = "platform_system=='Darwin'" } +pyobjc-framework-CoreBluetooth = { version = "^9.0.1", markers = "platform_system=='Darwin'" } +pyobjc-framework-libdispatch = { version = "^9.0.1", markers = "platform_system=='Darwin'" } bleak-winrt = { version = "^1.2.0", markers = "platform_system=='Windows'" } dbus-fast = { version = "^1.83.0", markers = "platform_system == 'Linux'" } From a586a668797d1d8eee5b89616abb121a97baa413 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 16:08:37 -0600 Subject: [PATCH 35/45] uuids: add normalize_uuid_str() function This adds a new function for normalizing UUID strings to the 128-bit, lower-case format used in Bleak. This is used to reduce some code duplication in Bleak itself and is also useful to end users. --- CHANGELOG.rst | 1 + bleak/backends/corebluetooth/utils.py | 17 +++++--------- bleak/backends/descriptor.py | 34 ++++++++++++++------------- bleak/backends/service.py | 20 +++++----------- bleak/uuids.py | 16 +++++++++++++ tests/test_uuid.py | 12 ++++++++++ 6 files changed, 59 insertions(+), 41 deletions(-) create mode 100644 tests/test_uuid.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 697160c5..f4f13b94 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,7 @@ Added * Added ``BleakScanner.find_device_by_name()`` class method. * Added optional command line argument to use debug log level to all applicable examples. * Make sure the disconnect monitor task is properly cancelled on the BlueZ client. +* Added ``bleak.uuids.normalize_uuid_str()`` function. Changed ------- diff --git a/bleak/backends/corebluetooth/utils.py b/bleak/backends/corebluetooth/utils.py index ccf3ffb9..e635151a 100644 --- a/bleak/backends/corebluetooth/utils.py +++ b/bleak/backends/corebluetooth/utils.py @@ -1,27 +1,22 @@ from Foundation import NSData from CoreBluetooth import CBUUID +from ...uuids import normalize_uuid_str -def cb_uuid_to_str(_uuid: CBUUID) -> str: + +def cb_uuid_to_str(uuid: CBUUID) -> str: """Converts a CoreBluetooth UUID to a Python string. - If ``_uuid`` is a 16-bit UUID, it is assumed to be a Bluetooth GATT UUID + If ``uuid`` is a 16-bit UUID, it is assumed to be a Bluetooth GATT UUID (``0000xxxx-0000-1000-8000-00805f9b34fb``). Args - _uuid: The UUID. + uuid: The UUID. Returns: The UUID as a lower case Python string (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx``) """ - _uuid = _uuid.UUIDString() - if len(_uuid) == 4: - return "0000{0}-0000-1000-8000-00805f9b34fb".format(_uuid.lower()) - # TODO: Evaluate if this is a necessary method... - # elif _is_uuid_16bit_compatible(_uuid): - # return _uuid[4:8].lower() - else: - return _uuid.lower() + return normalize_uuid_str(uuid.UUIDString()) def _is_uuid_16bit_compatible(_uuid: str) -> bool: diff --git a/bleak/backends/descriptor.py b/bleak/backends/descriptor.py index b882829e..cf584937 100644 --- a/bleak/backends/descriptor.py +++ b/bleak/backends/descriptor.py @@ -8,92 +8,94 @@ import abc from typing import Any +from ..uuids import normalize_uuid_str + _descriptor_descriptions = { - "00002905-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2905"): [ "Characteristic Aggregate Format", "org.bluetooth.descriptor.gatt.characteristic_aggregate_format", "0x2905", "GSS", ], - "00002900-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2900"): [ "Characteristic Extended Properties", "org.bluetooth.descriptor.gatt.characteristic_extended_properties", "0x2900", "GSS", ], - "00002904-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2904"): [ "Characteristic Presentation Format", "org.bluetooth.descriptor.gatt.characteristic_presentation_format", "0x2904", "GSS", ], - "00002901-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2901"): [ "Characteristic User Description", "org.bluetooth.descriptor.gatt.characteristic_user_description", "0x2901", "GSS", ], - "00002902-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2902"): [ "Client Characteristic Configuration", "org.bluetooth.descriptor.gatt.client_characteristic_configuration", "0x2902", "GSS", ], - "0000290b-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("290B"): [ "Environmental Sensing Configuration", "org.bluetooth.descriptor.es_configuration", "0x290B", "GSS", ], - "0000290c-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("290C"): [ "Environmental Sensing Measurement", "org.bluetooth.descriptor.es_measurement", "0x290C", "GSS", ], - "0000290d-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("290d"): [ "Environmental Sensing Trigger Setting", "org.bluetooth.descriptor.es_trigger_setting", "0x290D", "GSS", ], - "00002907-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2907"): [ "External Report Reference", "org.bluetooth.descriptor.external_report_reference", "0x2907", "GSS", ], - "00002909-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2909"): [ "Number of Digitals", "org.bluetooth.descriptor.number_of_digitals", "0x2909", "GSS", ], - "00002908-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2908"): [ "Report Reference", "org.bluetooth.descriptor.report_reference", "0x2908", "GSS", ], - "00002903-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2903"): [ "Server Characteristic Configuration", "org.bluetooth.descriptor.gatt.server_characteristic_configuration", "0x2903", "GSS", ], - "0000290e-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("290E"): [ "Time Trigger Setting", "org.bluetooth.descriptor.time_trigger_setting", "0x290E", "GSS", ], - "00002906-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("2906"): [ "Valid Range", "org.bluetooth.descriptor.valid_range", "0x2906", "GSS", ], - "0000290a-0000-1000-8000-00805f9b34fb": [ + normalize_uuid_str("290A"): [ "Value Trigger Setting", "org.bluetooth.descriptor.value_trigger_setting", "0x290A", @@ -138,4 +140,4 @@ def handle(self) -> int: @property def description(self) -> str: """A text description of what this descriptor represents""" - return _descriptor_descriptions.get(self.uuid.lower(), ["None"])[0] + return _descriptor_descriptions.get(self.uuid, ["Unknown"])[0] diff --git a/bleak/backends/service.py b/bleak/backends/service.py index 1528ca66..305e8bd2 100644 --- a/bleak/backends/service.py +++ b/bleak/backends/service.py @@ -11,7 +11,7 @@ from uuid import UUID from ..exc import BleakError -from ..uuids import uuidstr_to_str +from ..uuids import uuidstr_to_str, normalize_uuid_str from .characteristic import BleakGATTCharacteristic from .descriptor import BleakGATTDescriptor @@ -70,13 +70,10 @@ def get_characteristic( The first characteristic matching ``uuid`` or ``None`` if no matching characteristic was found. """ - if type(uuid) == str and len(uuid) == 4: - # Convert 16-bit uuid to 128-bit uuid - uuid = f"0000{uuid}-0000-1000-8000-00805f9b34fb" + uuid = normalize_uuid_str(str(uuid)) + try: - return next( - filter(lambda x: x.uuid == str(uuid).lower(), self.characteristics) - ) + return next(filter(lambda x: x.uuid == uuid, self.characteristics)) except StopIteration: return None @@ -140,16 +137,11 @@ def get_service( if isinstance(specifier, int): return self.services.get(specifier) - _specifier = str(specifier).lower() - - # Assume uuid usage. - # Convert 16-bit uuid to 128-bit uuid - if len(_specifier) == 4: - _specifier = f"0000{_specifier}-0000-1000-8000-00805f9b34fb" + uuid = normalize_uuid_str(str(specifier)) x = list( filter( - lambda x: x.uuid.lower() == _specifier, + lambda x: x.uuid == uuid, self.services.values(), ) ) diff --git a/bleak/uuids.py b/bleak/uuids.py index 0621c611..335028d8 100644 --- a/bleak/uuids.py +++ b/bleak/uuids.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from typing import Dict +from uuid import UUID uuid16_dict = { @@ -1147,3 +1148,18 @@ def register_uuids(uuids_to_descriptions: Dict[str, str]) -> None: """ uuid128_dict.update(uuids_to_descriptions) + + +def normalize_uuid_str(uuid: str) -> str: + """ + Normaizes a UUID to the format used by Bleak. + + - Converted to lower case. + - 16-bit UUIDs are expanded to 128-bit. + """ + if len(uuid) == 4: + # Bluetooth SIG registered 16-bit UUIDs + uuid = f"0000{uuid}-0000-1000-8000-00805f9b34fb" + + # let UUID class do the validation and conversion to lower case + return str(UUID(uuid)) diff --git a/tests/test_uuid.py b/tests/test_uuid.py new file mode 100644 index 00000000..ab97edda --- /dev/null +++ b/tests/test_uuid.py @@ -0,0 +1,12 @@ +from bleak.uuids import normalize_uuid_str + + +def test_uuid_length_normalization(): + assert normalize_uuid_str("1801") == "00001801-0000-1000-8000-00805f9b34fb" + + +def test_uuid_case_normalization(): + assert ( + normalize_uuid_str("00001801-0000-1000-8000-00805F9B34FB") + == "00001801-0000-1000-8000-00805f9b34fb" + ) From 1d59e1b7a8bd7a9db4ca28c1a6634d2ddd7dfb99 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 6 Oct 2021 15:39:42 -0500 Subject: [PATCH 36/45] BleakClient: add optional services argument This adds an optional `services` argument to `BleakClient()` to make use of OS API capabilities of only enumerating services that will be used. These APIs are available on Windows and Mac, but not BlueZ, so on the BlueZ backend, it is faked. Theoretically, this can save battery power on peripheral devices, but this is probably only relevant to super-low-powered devices. --- CHANGELOG.rst | 2 + bleak/__init__.py | 12 +++++ bleak/backends/bluezdbus/client.py | 14 ++++-- bleak/backends/bluezdbus/manager.py | 11 ++++- .../corebluetooth/PeripheralDelegate.py | 4 +- bleak/backends/corebluetooth/client.py | 21 +++++++-- bleak/backends/p4android/client.py | 29 ++++++++---- bleak/backends/p4android/defs.py | 1 + bleak/backends/winrt/client.py | 46 +++++++++++++------ examples/service_explorer.py | 12 ++++- typings/Foundation/__init__.pyi | 7 ++- 11 files changed, 123 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f4f13b94..af9c4a9b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,8 @@ Added * Added optional command line argument to use debug log level to all applicable examples. * Make sure the disconnect monitor task is properly cancelled on the BlueZ client. * Added ``bleak.uuids.normalize_uuid_str()`` function. +* Added optional ``services`` argument to ``BleakClient()`` to filter services + that will be used. Changed ------- diff --git a/bleak/__init__.py b/bleak/__init__.py index 16da0e37..5c5e9b06 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -19,6 +19,7 @@ Awaitable, Callable, Dict, + Iterable, List, Optional, Tuple, @@ -50,6 +51,7 @@ ) from .backends.service import BleakGATTServiceCollection from .exc import BleakError +from .uuids import normalize_uuid_str if TYPE_CHECKING: from .backends.bluezdbus.scanner import BlueZScannerArgs @@ -383,6 +385,12 @@ class BleakClient: Callback that will be scheduled in the event loop when the client is disconnected. The callable must take one argument, which will be this client object. + services: + Optional list of services to filter. If provided, only these services + will be resolved. This may or may not reduce the time needed to + enumerate the services depending on if the OS supports such filtering + in the Bluetooth stack or not (should affect Windows and Mac). + These can be 16-bit or 128-bit UUIDs. timeout: Timeout in seconds passed to the implicit ``discover`` call when ``address_or_ble_device`` is not a :class:`BLEDevice`. Defaults to 10.0. @@ -419,6 +427,7 @@ def __init__( self, address_or_ble_device: Union[BLEDevice, str], disconnected_callback: Optional[Callable[[BleakClient], None]] = None, + services: Optional[Iterable[str]] = None, *, timeout: float = 10.0, winrt: WinRTClientArgs = {}, @@ -432,6 +441,9 @@ def __init__( self._backend = PlatformBleakClient( address_or_ble_device, disconnected_callback=disconnected_callback, + services=None + if services is None + else set(map(normalize_uuid_str, services)), timeout=timeout, winrt=winrt, **kwargs, diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 0d022079..2b9a2c24 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -7,7 +7,7 @@ import os import sys import warnings -from typing import Callable, Dict, Optional, Union, cast +from typing import Callable, Dict, Optional, Set, Union, cast from uuid import UUID if sys.version_info < (3, 11): @@ -43,6 +43,7 @@ class BleakClientBlueZDBus(BaseBleakClient): Args: address_or_ble_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + services: Optional list of service UUIDs that will be used. Keyword Args: timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. @@ -52,7 +53,12 @@ class BleakClientBlueZDBus(BaseBleakClient): adapter (str): Bluetooth adapter to use for discovery. """ - def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + def __init__( + self, + address_or_ble_device: Union[BLEDevice, str], + services: Optional[Set[str]] = None, + **kwargs, + ): super(BleakClientBlueZDBus, self).__init__(address_or_ble_device, **kwargs) # kwarg "device" is for backwards compatibility self._adapter: Optional[str] = kwargs.get("adapter", kwargs.get("device")) @@ -65,6 +71,8 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._device_path = None self._device_info = None + self._requested_services = services + # D-Bus message bus self._bus: Optional[MessageBus] = None # tracks device watcher subscription @@ -599,7 +607,7 @@ async def get_services( manager = await get_global_bluez_manager() self.services = await manager.get_services( - self._device_path, dangerous_use_bleak_cache + self._device_path, dangerous_use_bleak_cache, self._requested_services ) return self.services diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index 90151e12..52eaa7e4 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -553,7 +553,7 @@ def remove_device_watcher(self, watcher: DeviceWatcher) -> None: self._device_watchers.remove(watcher) async def get_services( - self, device_path: str, use_cached: bool + self, device_path: str, use_cached: bool, requested_services: Optional[Set[str]] ) -> BleakGATTServiceCollection: """ Builds a new :class:`BleakGATTServiceCollection` from the current state. @@ -565,6 +565,9 @@ async def get_services( When ``True`` if there is a cached :class:`BleakGATTServiceCollection`, the method will not wait for ``"ServicesResolved"`` to become true and instead return the cached service collection immediately. + requested_services: + When given, only return services with UUID that is in the list + of requested services. Returns: A new :class:`BleakGATTServiceCollection`. @@ -587,6 +590,12 @@ async def get_services( service = BleakGATTServiceBlueZDBus(service_props, service_path) + if ( + requested_services is not None + and service.uuid not in requested_services + ): + continue + services.add_service(service) for char_path in self._characteristic_map.get(service_path, set()): diff --git a/bleak/backends/corebluetooth/PeripheralDelegate.py b/bleak/backends/corebluetooth/PeripheralDelegate.py index 02ca39a4..4fa9d42c 100644 --- a/bleak/backends/corebluetooth/PeripheralDelegate.py +++ b/bleak/backends/corebluetooth/PeripheralDelegate.py @@ -98,12 +98,12 @@ def futures(self) -> Iterable[asyncio.Future]: ) @objc.python_method - async def discover_services(self) -> NSArray: + async def discover_services(self, services: Optional[NSArray]) -> NSArray: future = self._event_loop.create_future() self._services_discovered_future = future try: - self.peripheral.discoverServices_(None) + self.peripheral.discoverServices_(services) return await future finally: del self._services_discovered_future diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 4fd38c3d..e08cf094 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -6,15 +6,16 @@ import asyncio import logging import uuid -from typing import Optional, Union +from typing import Optional, Set, Union from CoreBluetooth import ( + CBUUID, CBCharacteristicWriteWithoutResponse, CBCharacteristicWriteWithResponse, CBPeripheral, CBPeripheralStateConnected, ) -from Foundation import NSData +from Foundation import NSArray, NSData from ... import BleakScanner from ...exc import BleakError, BleakDeviceNotFoundError @@ -38,13 +39,19 @@ class BleakClientCoreBluetooth(BaseBleakClient): Args: address_or_ble_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. + services: Optional set of service UUIDs that will be used. Keyword Args: timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. """ - def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + def __init__( + self, + address_or_ble_device: Union[BLEDevice, str], + services: Optional[Set[str]] = None, + **kwargs, + ): super(BleakClientCoreBluetooth, self).__init__(address_or_ble_device, **kwargs) self._peripheral: Optional[CBPeripheral] = None @@ -57,6 +64,12 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._central_manager_delegate, ) = address_or_ble_device.details + self._requested_services = ( + NSArray.alloc().initWithArray_(list(map(CBUUID.UUIDWithString_, services))) + if services + else None + ) + def __str__(self): return "BleakClientCoreBluetooth ({})".format(self.address) @@ -192,7 +205,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: services = BleakGATTServiceCollection() logger.debug("Retrieving services...") - cb_services = await self._delegate.discover_services() + cb_services = await self._delegate.discover_services(self._requested_services) for service in cb_services: serviceUUID = service.UUID().UUIDString() diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py index 24e5eae9..00b23c4c 100644 --- a/bleak/backends/p4android/client.py +++ b/bleak/backends/p4android/client.py @@ -6,7 +6,7 @@ import logging import uuid import warnings -from typing import Optional, Union +from typing import Optional, Set, Union from android.broadcast import BroadcastReceiver from jnius import java_method @@ -28,17 +28,23 @@ class BleakClientP4Android(BaseBleakClient): """A python-for-android Bleak Client Args: - address_or_ble_device (`BLEDevice` or str): The Bluetooth address of the BLE peripheral to connect to or the `BLEDevice` object representing it. - - Keyword Args: - disconnected_callback (callable): Callback that will be scheduled in the - event loop when the client is disconnected. The callable must take one - argument, which will be this client object. - adapter (str): Bluetooth adapter to use for discovery. [unused] + address_or_ble_device: + The Bluetooth address of the BLE peripheral to connect to or the + :class:`BLEDevice` object representing it. + services: + Optional set of services UUIDs to filter. """ - def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): + def __init__( + self, + address_or_ble_device: Union[BLEDevice, str], + services: Optional[Set[uuid.UUID]], + **kwargs, + ): super(BleakClientP4Android, self).__init__(address_or_ble_device, **kwargs) + self._requested_services = ( + set(map(defs.UUID.fromString, services)) if services else None + ) # kwarg "device" is for backwards compatibility self.__adapter = kwargs.get("adapter", kwargs.get("device", None)) self.__gatt = None @@ -252,6 +258,11 @@ async def get_services(self) -> BleakGATTServiceCollection: logger.debug("Get Services...") for java_service in self.__gatt.getServices(): + if ( + self._requested_services is not None + and java_service.getUuid() not in self._requested_services + ): + continue service = BleakGATTServiceP4Android(java_service) services.add_service(service) diff --git a/bleak/backends/p4android/defs.py b/bleak/backends/p4android/defs.py index 1fdabd11..c097553b 100644 --- a/bleak/backends/p4android/defs.py +++ b/bleak/backends/p4android/defs.py @@ -8,6 +8,7 @@ # caching constants avoids unnecessary extra use of the jni-python interface, which can be slow List = autoclass("java.util.ArrayList") +UUID = autoclass("java.util.UUID") BluetoothAdapter = autoclass("android.bluetooth.BluetoothAdapter") ScanCallback = autoclass("android.bluetooth.le.ScanCallback") ScanFilter = autoclass("android.bluetooth.le.ScanFilter") diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index a922d436..bc91d2df 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -11,7 +11,7 @@ import uuid import warnings from ctypes import pythonapi -from typing import Any, Dict, List, Optional, Sequence, Union, cast +from typing import Any, Dict, List, Optional, Sequence, Set, Union, cast if sys.version_info < (3, 11): from async_timeout import timeout as async_timeout @@ -153,22 +153,18 @@ class WinRTClientArgs(TypedDict, total=False): class BleakClientWinRT(BaseBleakClient): """Native Windows Bleak Client. - Implemented using `winrt `_, - a package that enables Python developers to access Windows Runtime APIs directly from Python. - Args: address_or_ble_device (str or BLEDevice): The Bluetooth address of the BLE peripheral to connect to or the ``BLEDevice`` object representing it. + services: Optional set of service UUIDs that will be used. winrt (dict): A dictionary of Windows-specific configuration values. **timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0. - **disconnected_callback (callable): Callback that will be scheduled in the - event loop when the client is disconnected. The callable must take one - argument, which will be this client object. """ def __init__( self, address_or_ble_device: Union[BLEDevice, str], + services: Optional[Set[str]] = None, *, winrt: WinRTClientArgs, **kwargs, @@ -180,6 +176,9 @@ def __init__( self._device_info = address_or_ble_device.details.adv.bluetooth_address else: self._device_info = None + self._requested_services = ( + [uuid.UUID(s) for s in services] if services else None + ) self._requester: Optional[BluetoothLEDevice] = None self._services_changed_events: List[asyncio.Event] = [] self._session_active_events: List[asyncio.Event] = [] @@ -657,14 +656,33 @@ def dispose_on_cancel(future): for service in future._result: service.close() - future = FutureLike(self._requester.get_gatt_services_async(*srv_args)) - future.add_done_callback(dispose_on_cancel) + services: Sequence[GattDeviceService] - services: Sequence[GattDeviceService] = _ensure_success( - await future, - "services", - "Could not get GATT services", - ) + if self._requested_services is None: + future = FutureLike(self._requester.get_gatt_services_async(*srv_args)) + future.add_done_callback(dispose_on_cancel) + + services = _ensure_success( + await FutureLike(self._requester.get_gatt_services_async(*srv_args)), + "services", + "Could not get GATT services", + ) + else: + services = [] + # REVISIT: should properly dispose services on cancel or protect from cancellation + + for s in self._requested_services: + services.extend( + _ensure_success( + await FutureLike( + self._requester.get_gatt_services_for_uuid_async( + s, *srv_args + ) + ), + "services", + "Could not get GATT services", + ) + ) logger.debug("returned from get_gatt_services_async") diff --git a/examples/service_explorer.py b/examples/service_explorer.py index a8cf44ac..b841b0a2 100644 --- a/examples/service_explorer.py +++ b/examples/service_explorer.py @@ -38,7 +38,10 @@ async def main(args: argparse.Namespace): logger.info("connecting to device...") - async with BleakClient(device) as client: + async with BleakClient( + device, + services=args.services, + ) as client: logger.info("connected") for service in client.services: @@ -101,6 +104,13 @@ async def main(args: argparse.Namespace): help="when true use Bluetooth address instead of UUID on macOS", ) + parser.add_argument( + "--services", + nargs="+", + metavar="", + help="if provided, only enumerate matching service(s)", + ) + parser.add_argument( "-d", "--debug", diff --git a/typings/Foundation/__init__.pyi b/typings/Foundation/__init__.pyi index d3665586..0a4485b0 100644 --- a/typings/Foundation/__init__.pyi +++ b/typings/Foundation/__init__.pyi @@ -1,4 +1,4 @@ -from typing import NewType, Optional, Type, TypeVar +from typing import NewType, Optional, Sequence, Type, TypeVar TNSObject = TypeVar("TNSObject", bound=NSObject) @@ -22,7 +22,10 @@ class NSUUID(NSObject): ... class NSString(NSObject): ... class NSError(NSObject): ... class NSData(NSObject): ... -class NSArray(NSObject): ... + +class NSArray(NSObject): + def initWithArray_(self, array: Sequence) -> NSArray: ... + class NSNumber(NSValue): ... class NSValue(NSObject): ... From cd6b3d9886a9173839889a8f783446f3c9c1190b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 17:47:23 -0600 Subject: [PATCH 37/45] BleakScanner.discover: fix type hints when return_adv=True Pyright/Pylance needs the default value supplied in order to correctly match the overload. --- bleak/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bleak/__init__.py b/bleak/__init__.py index 5c5e9b06..9ab1064e 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -209,7 +209,7 @@ async def discover( @overload @classmethod async def discover( - cls, timeout: float = 5.0, *, return_adv: Literal[True], **kwargs + cls, timeout: float = 5.0, *, return_adv: Literal[True] = True, **kwargs ) -> Dict[str, Tuple[BLEDevice, AdvertisementData]]: ... From 9a5f53c55f44a416e894842a7d9e626bf49df0de Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 17:54:03 -0600 Subject: [PATCH 38/45] backends/scanner: fix BLEDevice name not updated Observed on macOS: sometimes the peripheral name is not populated the first time an advertisement callback is made. Since we were not updating the device with the new name, it was stuck as `None`. --- CHANGELOG.rst | 1 + bleak/backends/scanner.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index af9c4a9b..4681ca6b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -41,6 +41,7 @@ Fixed * Fixed leaking services when ``get_services()`` is cancelled in WinRT backend. * Fixed WinRT scanner never calling ``detection_callback`` when a device does not send a scan response. Fixes #1211. +* Fixed ``BLEDevice`` name sometimes incorrectly ``None``. `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/backends/scanner.py b/bleak/backends/scanner.py index 55636923..d7454a99 100644 --- a/bleak/backends/scanner.py +++ b/bleak/backends/scanner.py @@ -197,6 +197,7 @@ def create_or_update_device( try: device, _ = self.seen_devices[address] + device.name = name device._rssi = adv.rssi device._metadata = metadata except KeyError: From eb48a90b0ac88e67b12f9f9f6de182a3ea971ac3 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 18 Feb 2023 18:14:30 -0600 Subject: [PATCH 39/45] backends/corebluetooth/CentralManagerDelegate: fix unhandled exception in __del__ Since the init() method can raise an exception before registering the observer, attempting to unregister the observer will fail with IndexError. We can safely ignore this error. Fixes: https://github.com/hbldh/bleak/issues/1219 --- CHANGELOG.rst | 1 + bleak/backends/corebluetooth/CentralManagerDelegate.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4681ca6b..41a67564 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -42,6 +42,7 @@ Fixed * Fixed WinRT scanner never calling ``detection_callback`` when a device does not send a scan response. Fixes #1211. * Fixed ``BLEDevice`` name sometimes incorrectly ``None``. +* Fixed unhandled exception in ``CentralManagerDelegate`` destructor on macOS. Fixes #1219. `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/backends/corebluetooth/CentralManagerDelegate.py b/bleak/backends/corebluetooth/CentralManagerDelegate.py index 42726f30..701319ec 100644 --- a/bleak/backends/corebluetooth/CentralManagerDelegate.py +++ b/bleak/backends/corebluetooth/CentralManagerDelegate.py @@ -105,7 +105,13 @@ def init(self) -> Optional["CentralManagerDelegate"]: def __del__(self): if objc.macos_available(10, 13): - self.central_manager.removeObserver_forKeyPath_(self, "isScanning") + try: + self.central_manager.removeObserver_forKeyPath_(self, "isScanning") + except IndexError: + # If self.init() raised an exception before calling + # addObserver_forKeyPath_options_context_, attempting + # to remove the observer will fail with IndexError + pass # User defined functions From 4d1be1dbadc0284c96a3b381b9b17fe356a15246 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 6 Mar 2023 11:32:22 -0600 Subject: [PATCH 40/45] bleak: add versionadded to BleakScanner.find_device_by_name() docs --- bleak/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bleak/__init__.py b/bleak/__init__.py index 9ab1064e..1c6f9143 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -322,6 +322,7 @@ async def find_device_by_name( Returns: The ``BLEDevice`` sought or ``None`` if not detected. + .. versionadded:: 0.20.0 """ return await cls.find_device_by_filter( lambda d, ad: ad.local_name == name, From 05c8cd36fdd01d9b09ef90b6ca04d0ba25f5a640 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 6 Mar 2023 11:42:37 -0600 Subject: [PATCH 41/45] bleak.uuids: add versionadded to normalize_uuid_str() --- bleak/uuids.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bleak/uuids.py b/bleak/uuids.py index 335028d8..281975e8 100644 --- a/bleak/uuids.py +++ b/bleak/uuids.py @@ -1156,6 +1156,8 @@ def normalize_uuid_str(uuid: str) -> str: - Converted to lower case. - 16-bit UUIDs are expanded to 128-bit. + + .. versionadded:: 0.20.0 """ if len(uuid) == 4: # Bluetooth SIG registered 16-bit UUIDs From 138697fc8c8db19dd388d30eb6361fce8ade3225 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 6 Mar 2023 11:57:22 -0600 Subject: [PATCH 42/45] CHANGELOG: minor tweaks to unreleased section --- CHANGELOG.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 41a67564..11f047ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,15 +12,13 @@ and this project adheres to `Semantic Versioning Date: Fri, 17 Mar 2023 16:33:52 -0500 Subject: [PATCH 43/45] bluezdbus/client: add retry on le-connection-abort-by-local Since BlueZ 6.62, BlueZ has a new connection error "le-connection-abort-by-local" that is seen quite frequently, particularly when there are many BLE devices in the area. Reconnecting immediately after receiving this error is generally successful, so we add an automatic retry in the BleakClient. This is a bit tricky because the device does actually connect in this case and we receive D-Bus property changes indicating this, so we have to be careful about waiting for the disconnect event before trying again. Fixes: https://github.com/hbldh/bleak/issues/1220 --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/client.py | 269 ++++++++++++++++------------- 2 files changed, 153 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 11f047ea..a4e8d44f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -29,6 +29,7 @@ Changed until services are discovered. * Include thread name in ``BLEAK_LOGGING`` output. Merged #1144. * Updated PyObjC dependency on macOS to v9.x. +* Added automatic retry on ``le-connection-abort-by-local`` in BlueZ backend. Fixes #1220. Fixed ----- diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 2b9a2c24..587f3a5f 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -135,134 +135,169 @@ async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> bo manager = await get_global_bluez_manager() - # Each BLE connection session needs a new D-Bus connection to avoid a - # BlueZ quirk where notifications are automatically enabled on reconnect. - self._bus = await MessageBus( - bus_type=BusType.SYSTEM, - negotiate_unix_fd=True, - auth=get_dbus_authenticator(), - ).connect() - - def on_connected_changed(connected: bool) -> None: - if not connected: - logger.debug(f"Device disconnected ({self._device_path})") - - self._is_connected = False - - if self._disconnect_monitor_event: - self._disconnect_monitor_event.set() - self._disconnect_monitor_event = None - - self._cleanup_all() - if self._disconnected_callback is not None: - self._disconnected_callback(self) - disconnecting_event = self._disconnecting_event - if disconnecting_event: - disconnecting_event.set() - - def on_value_changed(char_path: str, value: bytes) -> None: - callback = self._notification_callbacks.get(char_path) - - if callback: - callback(bytearray(value)) - - watcher = manager.add_device_watcher( - self._device_path, on_connected_changed, on_value_changed - ) - self._remove_device_watcher = lambda: manager.remove_device_watcher(watcher) + async with async_timeout(timeout): + while True: + # Each BLE connection session needs a new D-Bus connection to avoid a + # BlueZ quirk where notifications are automatically enabled on reconnect. + self._bus = await MessageBus( + bus_type=BusType.SYSTEM, + negotiate_unix_fd=True, + auth=get_dbus_authenticator(), + ).connect() + + def on_connected_changed(connected: bool) -> None: + if not connected: + logger.debug(f"Device disconnected ({self._device_path})") + + self._is_connected = False + + if self._disconnect_monitor_event: + self._disconnect_monitor_event.set() + self._disconnect_monitor_event = None + + self._cleanup_all() + if self._disconnected_callback is not None: + self._disconnected_callback(self) + disconnecting_event = self._disconnecting_event + if disconnecting_event: + disconnecting_event.set() + + def on_value_changed(char_path: str, value: bytes) -> None: + callback = self._notification_callbacks.get(char_path) + + if callback: + callback(bytearray(value)) + + watcher = manager.add_device_watcher( + self._device_path, on_connected_changed, on_value_changed + ) + self._remove_device_watcher = lambda: manager.remove_device_watcher( + watcher + ) - local_disconnect_monitor_event = asyncio.Event() + self._disconnect_monitor_event = ( + local_disconnect_monitor_event + ) = asyncio.Event() - try: - try: - # - # The BlueZ backend does not disconnect devices when the - # application closes or crashes. This can cause problems - # when trying to reconnect to the same device. To work - # around this, we check if the device is already connected. - # - # For additional details see https://github.com/bluez/bluez/issues/89 - # - if not manager.is_connected(self._device_path): - logger.debug("Connecting to BlueZ path %s", self._device_path) - async with async_timeout(timeout): - reply = await self._bus.call( - Message( - destination=defs.BLUEZ_SERVICE, - interface=defs.DEVICE_INTERFACE, - path=self._device_path, - member="Connect", + try: + try: + # + # The BlueZ backend does not disconnect devices when the + # application closes or crashes. This can cause problems + # when trying to reconnect to the same device. To work + # around this, we check if the device is already connected. + # + # For additional details see https://github.com/bluez/bluez/issues/89 + # + if manager.is_connected(self._device_path): + logger.debug( + 'skipping calling "Connect" since %s is already connected', + self._device_path, + ) + else: + logger.debug( + "Connecting to BlueZ path %s", self._device_path + ) + reply = await self._bus.call( + Message( + destination=defs.BLUEZ_SERVICE, + interface=defs.DEVICE_INTERFACE, + path=self._device_path, + member="Connect", + ) ) - ) - - if ( - reply.message_type == MessageType.ERROR - and reply.error_name == ErrorType.UNKNOWN_OBJECT.value - ): - raise BleakDeviceNotFoundError( - self.address, - f"Device with address {self.address} was not found. It may have been removed from BlueZ when scanning stopped.", - ) - - assert_reply(reply) - self._is_connected = True + assert reply is not None + + if reply.message_type == MessageType.ERROR: + # This error is often caused by RF interference + # from other Bluetooth or Wi-Fi devices. In many + # cases, retrying will connect successfully. + # Note: this error was added in BlueZ 6.62. + if ( + reply.error_name == "org.bluez.Error.Failed" + and reply.body + and reply.body[0] == "le-connection-abort-by-local" + ): + logger.debug( + "retry due to le-connection-abort-by-local" + ) + + # When this error occurs, BlueZ actually + # connected so we get "Connected" property changes + # that we need to wait for before attempting + # to connect again. + await local_disconnect_monitor_event.wait() + + # Jump way back to the `while True:`` to retry. + continue + + if reply.error_name == ErrorType.UNKNOWN_OBJECT.value: + raise BleakDeviceNotFoundError( + self.address, + f"Device with address {self.address} was not found. It may have been removed from BlueZ when scanning stopped.", + ) - # Create a task that runs until the device is disconnected. - self._disconnect_monitor_event = local_disconnect_monitor_event - asyncio.ensure_future( - self._disconnect_monitor( - self._bus, self._device_path, local_disconnect_monitor_event - ) - ) + assert_reply(reply) - # - # We will try to use the cache if it exists and `dangerous_use_bleak_cache` - # is True. - # - await self.get_services( - dangerous_use_bleak_cache=dangerous_use_bleak_cache - ) + self._is_connected = True - return True - except BaseException: - # Calling Disconnect cancels any pending connect request. Also, - # if connection was successful but get_services() raises (e.g. - # because task was cancelled), the we still need to disconnect - # before passing on the exception. - if self._bus: - # If disconnected callback already fired, this will be a no-op - # since self._bus will be None and the _cleanup_all call will - # have already disconnected. - try: - reply = await self._bus.call( - Message( - destination=defs.BLUEZ_SERVICE, - interface=defs.DEVICE_INTERFACE, - path=self._device_path, - member="Disconnect", + # Create a task that runs until the device is disconnected. + asyncio.ensure_future( + self._disconnect_monitor( + self._bus, + self._device_path, + local_disconnect_monitor_event, ) ) - try: - assert_reply(reply) - except BleakDBusError as e: - # if the object no longer exists, then we know we - # are disconnected for sure, so don't need to log a - # warning about it - if e.dbus_error != ErrorType.UNKNOWN_OBJECT.value: - raise - except Exception as e: - logger.warning( - f"Failed to cancel connection ({self._device_path}): {e}" + + # + # We will try to use the cache if it exists and `dangerous_use_bleak_cache` + # is True. + # + await self.get_services( + dangerous_use_bleak_cache=dangerous_use_bleak_cache ) - raise - except BaseException: - # this effectively cancels the disconnect monitor in case the event - # was not triggered by a D-Bus callback - local_disconnect_monitor_event.set() - self._cleanup_all() - raise + return True + except BaseException: + # Calling Disconnect cancels any pending connect request. Also, + # if connection was successful but get_services() raises (e.g. + # because task was cancelled), the we still need to disconnect + # before passing on the exception. + if self._bus: + # If disconnected callback already fired, this will be a no-op + # since self._bus will be None and the _cleanup_all call will + # have already disconnected. + try: + reply = await self._bus.call( + Message( + destination=defs.BLUEZ_SERVICE, + interface=defs.DEVICE_INTERFACE, + path=self._device_path, + member="Disconnect", + ) + ) + try: + assert_reply(reply) + except BleakDBusError as e: + # if the object no longer exists, then we know we + # are disconnected for sure, so don't need to log a + # warning about it + if e.dbus_error != ErrorType.UNKNOWN_OBJECT.value: + raise + except Exception as e: + logger.warning( + f"Failed to cancel connection ({self._device_path}): {e}" + ) + + raise + except BaseException: + # this effectively cancels the disconnect monitor in case the event + # was not triggered by a D-Bus callback + local_disconnect_monitor_event.set() + self._cleanup_all() + raise @staticmethod async def _disconnect_monitor( From af12363072f17bd194707541b1b371d241ddb833 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 17 Mar 2023 17:03:16 -0500 Subject: [PATCH 44/45] bleak: fix disconnected_callback arg Since reworking to have a common top-level `BleakClient` class, the `disconnected_callback` function was called with the backend object rather than the top-level `BleakClient` object. This fixes the mistake by using functools.partial to bind the top-level object to the function and eliminates the need to pass an argument in the backends. --- CHANGELOG.rst | 1 + bleak/__init__.py | 8 ++++++-- bleak/backends/bluezdbus/client.py | 2 +- bleak/backends/client.py | 16 ++++------------ bleak/backends/corebluetooth/client.py | 2 +- bleak/backends/p4android/client.py | 2 +- bleak/backends/winrt/client.py | 2 +- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a4e8d44f..c62859dc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -43,6 +43,7 @@ Fixed not send a scan response. Fixes #1211. * Fixed ``BLEDevice`` name sometimes incorrectly ``None``. * Fixed unhandled exception in ``CentralManagerDelegate`` destructor on macOS. Fixes #1219. +* Fixed object passed to ``disconnected_callback`` is not ``BleakClient``. Fixes #1200. `0.19.5`_ (2022-11-19) ====================== diff --git a/bleak/__init__.py b/bleak/__init__.py index 1c6f9143..9fd5db80 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -441,7 +441,9 @@ def __init__( self._backend = PlatformBleakClient( address_or_ble_device, - disconnected_callback=disconnected_callback, + disconnected_callback=None + if disconnected_callback is None + else functools.partial(disconnected_callback, self), services=None if services is None else set(map(normalize_uuid_str, services)), @@ -507,7 +509,9 @@ def set_disconnected_callback( FutureWarning, stacklevel=2, ) - self._backend.set_disconnected_callback(callback, **kwargs) + self._backend.set_disconnected_callback( + None if callback is None else functools.partial(callback, self), **kwargs + ) async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 587f3a5f..15e6b936 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -157,7 +157,7 @@ def on_connected_changed(connected: bool) -> None: self._cleanup_all() if self._disconnected_callback is not None: - self._disconnected_callback(self) + self._disconnected_callback() disconnecting_event = self._disconnecting_event if disconnecting_event: disconnecting_event.set() diff --git a/bleak/backends/client.py b/bleak/backends/client.py index d2bf6323..5b7041c1 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -45,7 +45,9 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self.services: Optional[BleakGATTServiceCollection] = None self._timeout = kwargs.get("timeout", 10.0) - self._disconnected_callback = kwargs.get("disconnected_callback") + self._disconnected_callback: Optional[Callable[[], None]] = kwargs.get( + "disconnected_callback" + ) @property @abc.abstractmethod @@ -56,23 +58,13 @@ def mtu_size(self) -> int: # Connectivity methods def set_disconnected_callback( - self, callback: Optional[Callable[["BaseBleakClient"], None]], **kwargs + self, callback: Optional[Callable[[], None]], **kwargs ) -> None: """Set the disconnect callback. The callback will only be called on unsolicited disconnect event. - Callbacks must accept one input which is the client object itself. - Set the callback to ``None`` to remove any existing callback. - .. code-block:: python - - def callback(client): - print("Client with address {} got disconnected!".format(client.address)) - - client.set_disconnected_callback(callback) - client.connect() - Args: callback: callback to be called on disconnection. diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index e08cf094..45781a04 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -117,7 +117,7 @@ def disconnect_callback(): pass if self._disconnected_callback: - self._disconnected_callback(self) + self._disconnected_callback() manager = self._central_manager_delegate logger.debug("CentralManagerDelegate at {}".format(manager)) diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py index 00b23c4c..6eceec28 100644 --- a/bleak/backends/p4android/client.py +++ b/bleak/backends/p4android/client.py @@ -551,7 +551,7 @@ def onConnectionStateChange(self, status, new_state): new_state == defs.BluetoothProfile.STATE_DISCONNECTED and self._client._disconnected_callback is not None ): - self._client._disconnected_callback(self._client) + self._client._disconnected_callback() @java_method("(II)V") def onMtuChanged(self, mtu, status): diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index bc91d2df..61704abc 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -313,7 +313,7 @@ def handle_session_status_changed( elif args.status == GattSessionStatus.CLOSED: if self._disconnected_callback: - self._disconnected_callback(self) + self._disconnected_callback() for e in self._session_closed_events: e.set() From 1b93859a8b5cd4ef886c7e4084977a7c973a92f4 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 17 Mar 2023 18:05:06 -0500 Subject: [PATCH 45/45] v0.20.0 --- CHANGELOG.rst | 14 +++++++++----- pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c62859dc..799ddad3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,26 +10,29 @@ and this project adheres to `Semantic Versioning = 3.11. * Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. -* ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. +* ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. Merged #1117. * ``BaseBleakClient.services`` is now ``None`` instead of empty service collection until services are discovered. * Include thread name in ``BLEAK_LOGGING`` output. Merged #1144. * Updated PyObjC dependency on macOS to v9.x. -* Added automatic retry on ``le-connection-abort-by-local`` in BlueZ backend. Fixes #1220. Fixed ----- @@ -918,7 +921,8 @@ Fixed * Bleak created. -.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.19.5...develop +.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.20.0...develop +.. _0.20.0: https://github.com/hbldh/bleak/compare/v0.19.5...v0.20.0 .. _0.19.5: https://github.com/hbldh/bleak/compare/v0.19.4...v0.19.5 .. _0.19.4: https://github.com/hbldh/bleak/compare/v0.19.3...v0.19.4 .. _0.19.3: https://github.com/hbldh/bleak/compare/v0.19.2...v0.19.3 diff --git a/pyproject.toml b/pyproject.toml index 5458d965..b8fd0939 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bleak" -version = "0.20.0a1" +version = "0.20.0" description = "Bluetooth Low Energy platform Agnostic Klient" authors = ["Henrik Blidh "] license = "MIT"