From 60678419a0e27a8b7bc1dc41ece5d43a57e579f8 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Thu, 27 Jul 2023 14:55:28 -0700 Subject: [PATCH 1/2] compatibility with python 11 --- .github/workflows/python-build-test.yml | 2 +- bumble/gatt.py | 36 ++++++++++--------------- tests/gatt_test.py | 29 +++++++++++--------- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/.github/workflows/python-build-test.yml b/.github/workflows/python-build-test.yml index 72c7b43..0bf1472 100644 --- a/.github/workflows/python-build-test.yml +++ b/.github/workflows/python-build-test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] fail-fast: false steps: diff --git a/bumble/gatt.py b/bumble/gatt.py index ea2b690..6b740ce 100644 --- a/bumble/gatt.py +++ b/bumble/gatt.py @@ -283,8 +283,7 @@ class IncludedServiceDeclaration(Attribute): f'IncludedServiceDefinition(handle=0x{self.handle:04X}, ' f'group_starting_handle=0x{self.service.handle:04X}, ' f'group_ending_handle=0x{self.service.end_group_handle:04X}, ' - f'uuid={self.service.uuid}, ' - f'{self.service.properties!s})' + f'uuid={self.service.uuid})' ) @@ -309,29 +308,22 @@ class Characteristic(Attribute): AUTHENTICATED_SIGNED_WRITES = 0x40 EXTENDED_PROPERTIES = 0x80 - @staticmethod - def from_string(properties_str: str) -> Characteristic.Properties: - property_names: List[str] = [] - for property in Characteristic.Properties: - if property.name is None: - raise TypeError() - property_names.append(property.name) - - def string_to_property(property_string) -> Characteristic.Properties: - for property in zip(Characteristic.Properties, property_names): - if property_string == property[1]: - return property[0] - raise TypeError(f"Unable to convert {property_string} to Property") - + @classmethod + def from_string(cls, properties_str: str) -> Characteristic.Properties: try: return functools.reduce( - lambda x, y: x | string_to_property(y), - properties_str.split(","), + lambda x, y: x | cls[y], + properties_str.replace("|", ",").split(","), Characteristic.Properties(0), ) - except TypeError: + except (TypeError, KeyError): + # The check for `p.name is not None` here is needed because for InFlag + # enums, the .name property can be None, when the enum value is 0, + # so the type hint for .name is Optional[str]. + enum_list: List[str] = [p.name for p in cls if p.name is not None] + enum_list_str = ",".join(enum_list) raise TypeError( - f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by commas: {','.join(property_names)}\nGot: {properties_str}" + f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}" ) # For backwards compatibility these are defined here @@ -373,7 +365,7 @@ class Characteristic(Attribute): f'Characteristic(handle=0x{self.handle:04X}, ' f'end=0x{self.end_group_handle:04X}, ' f'uuid={self.uuid}, ' - f'{self.properties!s})' + f'{self.properties.name})' ) @@ -401,7 +393,7 @@ class CharacteristicDeclaration(Attribute): f'CharacteristicDeclaration(handle=0x{self.handle:04X}, ' f'value_handle=0x{self.value_handle:04X}, ' f'uuid={self.characteristic.uuid}, ' - f'{self.characteristic.properties!s})' + f'{self.characteristic.properties.name})' ) diff --git a/tests/gatt_test.py b/tests/gatt_test.py index 0652197..d33df98 100644 --- a/tests/gatt_test.py +++ b/tests/gatt_test.py @@ -803,15 +803,14 @@ async def test_mtu_exchange(): # ----------------------------------------------------------------------------- def test_char_property_to_string(): # single - assert str(Characteristic.Properties(0x01)) == "Properties.BROADCAST" - assert str(Characteristic.Properties.BROADCAST) == "Properties.BROADCAST" + assert Characteristic.Properties(0x01).name == "BROADCAST" + assert Characteristic.Properties.BROADCAST.name == "BROADCAST" # double - assert str(Characteristic.Properties(0x03)) == "Properties.READ|BROADCAST" + assert Characteristic.Properties(0x03).name == "BROADCAST|READ" assert ( - str(Characteristic.Properties.BROADCAST | Characteristic.Properties.READ) - == "Properties.READ|BROADCAST" - ) + Characteristic.Properties.BROADCAST | Characteristic.Properties.READ + ).name == "BROADCAST|READ" # ----------------------------------------------------------------------------- @@ -831,6 +830,10 @@ def test_characteristic_property_from_string(): Characteristic.Properties.from_string("READ,BROADCAST") == Characteristic.Properties.BROADCAST | Characteristic.Properties.READ ) + assert ( + Characteristic.Properties.from_string("BROADCAST|READ") + == Characteristic.Properties.BROADCAST | Characteristic.Properties.READ + ) # ----------------------------------------------------------------------------- @@ -841,7 +844,7 @@ def test_characteristic_property_from_string_assert(): assert ( str(e_info.value) == """Characteristic.Properties::from_string() error: -Expected a string containing any of the keys, separated by commas: BROADCAST,READ,WRITE_WITHOUT_RESPONSE,WRITE,NOTIFY,INDICATE,AUTHENTICATED_SIGNED_WRITES,EXTENDED_PROPERTIES +Expected a string containing any of the keys, separated by , or |: BROADCAST,READ,WRITE_WITHOUT_RESPONSE,WRITE,NOTIFY,INDICATE,AUTHENTICATED_SIGNED_WRITES,EXTENDED_PROPERTIES Got: BROADCAST,HELLO""" ) @@ -866,13 +869,13 @@ async def test_server_string(): assert ( str(server.gatt_server) == """Service(handle=0x0001, end=0x0005, uuid=UUID-16:1800 (Generic Access)) -CharacteristicDeclaration(handle=0x0002, value_handle=0x0003, uuid=UUID-16:2A00 (Device Name), Properties.READ) -Characteristic(handle=0x0003, end=0x0003, uuid=UUID-16:2A00 (Device Name), Properties.READ) -CharacteristicDeclaration(handle=0x0004, value_handle=0x0005, uuid=UUID-16:2A01 (Appearance), Properties.READ) -Characteristic(handle=0x0005, end=0x0005, uuid=UUID-16:2A01 (Appearance), Properties.READ) +CharacteristicDeclaration(handle=0x0002, value_handle=0x0003, uuid=UUID-16:2A00 (Device Name), READ) +Characteristic(handle=0x0003, end=0x0003, uuid=UUID-16:2A00 (Device Name), READ) +CharacteristicDeclaration(handle=0x0004, value_handle=0x0005, uuid=UUID-16:2A01 (Appearance), READ) +Characteristic(handle=0x0005, end=0x0005, uuid=UUID-16:2A01 (Appearance), READ) Service(handle=0x0006, end=0x0009, uuid=3A657F47-D34F-46B3-B1EC-698E29B6B829) -CharacteristicDeclaration(handle=0x0007, value_handle=0x0008, uuid=FDB159DB-036C-49E3-B3DB-6325AC750806, Properties.NOTIFY|WRITE|READ) -Characteristic(handle=0x0008, end=0x0009, uuid=FDB159DB-036C-49E3-B3DB-6325AC750806, Properties.NOTIFY|WRITE|READ) +CharacteristicDeclaration(handle=0x0007, value_handle=0x0008, uuid=FDB159DB-036C-49E3-B3DB-6325AC750806, READ|WRITE|NOTIFY) +Characteristic(handle=0x0008, end=0x0009, uuid=FDB159DB-036C-49E3-B3DB-6325AC750806, READ|WRITE|NOTIFY) Descriptor(handle=0x0009, type=UUID-16:2902 (Client Characteristic Configuration), value=0000)""" ) From 4ffc050eedbace825553eb9f6ad7e1fc8bd7559c Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Thu, 27 Jul 2023 16:37:27 -0700 Subject: [PATCH 2/2] restore python < 11 compat --- bumble/gatt.py | 13 +++++++++++-- tests/gatt_test.py | 11 ++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/bumble/gatt.py b/bumble/gatt.py index 6b740ce..067f31d 100644 --- a/bumble/gatt.py +++ b/bumble/gatt.py @@ -326,6 +326,15 @@ class Characteristic(Attribute): f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}" ) + def __str__(self): + # NOTE: we override this method to offer a consistent result between python + # versions: the value returned by IntFlag.__str__() changed in version 11. + return '|'.join( + flag.name + for flag in Characteristic.Properties + if self.value & flag.value and flag.name is not None + ) + # For backwards compatibility these are defined here # For new code, please use Characteristic.Properties.X BROADCAST = Properties.BROADCAST @@ -365,7 +374,7 @@ class Characteristic(Attribute): f'Characteristic(handle=0x{self.handle:04X}, ' f'end=0x{self.end_group_handle:04X}, ' f'uuid={self.uuid}, ' - f'{self.properties.name})' + f'{self.properties})' ) @@ -393,7 +402,7 @@ class CharacteristicDeclaration(Attribute): f'CharacteristicDeclaration(handle=0x{self.handle:04X}, ' f'value_handle=0x{self.value_handle:04X}, ' f'uuid={self.characteristic.uuid}, ' - f'{self.characteristic.properties.name})' + f'{self.characteristic.properties})' ) diff --git a/tests/gatt_test.py b/tests/gatt_test.py index d33df98..dd0277e 100644 --- a/tests/gatt_test.py +++ b/tests/gatt_test.py @@ -803,14 +803,15 @@ async def test_mtu_exchange(): # ----------------------------------------------------------------------------- def test_char_property_to_string(): # single - assert Characteristic.Properties(0x01).name == "BROADCAST" - assert Characteristic.Properties.BROADCAST.name == "BROADCAST" + assert str(Characteristic.Properties(0x01)) == "BROADCAST" + assert str(Characteristic.Properties.BROADCAST) == "BROADCAST" # double - assert Characteristic.Properties(0x03).name == "BROADCAST|READ" + assert str(Characteristic.Properties(0x03)) == "BROADCAST|READ" assert ( - Characteristic.Properties.BROADCAST | Characteristic.Properties.READ - ).name == "BROADCAST|READ" + str(Characteristic.Properties.BROADCAST | Characteristic.Properties.READ) + == "BROADCAST|READ" + ) # -----------------------------------------------------------------------------