diff --git a/bumble/core.py b/bumble/core.py index 0cb9b0be..951d1099 100644 --- a/bumble/core.py +++ b/bumble/core.py @@ -468,7 +468,7 @@ BT_HDP_SINK_SERVICE = UUID.from_16_bits(0x1402, @dataclasses.dataclass class ClassOfDevice: # fmt: off - class MajorServiceClasses(enum.IntFlag): + class MajorServiceClasses(utils.CompatibleIntFlag): LIMITED_DISCOVERABLE_MODE = (1 << 0) LE_AUDIO = (1 << 1) POSITIONING = (1 << 3) @@ -1647,7 +1647,7 @@ class AdvertisingData: THREE_D_INFORMATION_DATA = 0x3D MANUFACTURER_SPECIFIC_DATA = 0xFF - class Flags(enum.IntFlag): + class Flags(utils.CompatibleIntFlag): LE_LIMITED_DISCOVERABLE_MODE = 1 << 0 LE_GENERAL_DISCOVERABLE_MODE = 1 << 1 BR_EDR_NOT_SUPPORTED = 1 << 2 @@ -2153,7 +2153,7 @@ class LeRole(enum.IntEnum): # ----------------------------------------------------------------------------- # Security Manager OOB Flag # ----------------------------------------------------------------------------- -class SecurityManagerOutOfBandFlag(enum.IntFlag): +class SecurityManagerOutOfBandFlag(utils.CompatibleIntFlag): """ See Supplement to the Bluetooth Core Specification, Part A 1.7 SECURITY MANAGER OUT OF BAND (OOB) diff --git a/bumble/data_types.py b/bumble/data_types.py index cc896e04..59c43794 100644 --- a/bumble/data_types.py +++ b/bumble/data_types.py @@ -25,7 +25,7 @@ from __future__ import annotations import dataclasses import math import struct -from typing import Any, ClassVar, Literal, Optional, TypeVar, Union, overload +from typing import Any, ClassVar, Sequence from typing_extensions import Self @@ -70,7 +70,7 @@ class ListOfServiceUUIDs(core.DataType): """Base class for complete or incomplete lists of UUIDs.""" _uuid_size: ClassVar[int] = 0 - uuids: list[core.UUID] + uuids: Sequence[core.UUID] @classmethod def from_bytes(cls, data: bytes) -> ListOfServiceUUIDs: @@ -215,7 +215,7 @@ class Flags(int, core.DataType): return self.to_bytes(length=bytes_length, byteorder="little") def value_string(self) -> str: - return core.AdvertisingData.Flags(self).name or "" + return core.AdvertisingData.Flags(self).composite_name @dataclasses.dataclass @@ -293,6 +293,13 @@ class FixedSizeBytesDataType(bytes, core.DataType): def __str__(self) -> str: return core.DataType.__str__(self) + def __bytes__(self) -> bytes: # pylint: disable=E0308 + # Python < 3.11 compatibility (before 3.11, the byte class does not have + # a __bytes__ method). + # Concatenate with an empty string to perform a direct conversion without + # calling bytes() explicity, which may cause an infinite recursion. + return b"" + self + class ClassOfDevice(core.ClassOfDevice, core.DataType): """ @@ -416,7 +423,7 @@ class SecurityManagerOutOfBandFlag(int, core.DataType): return core.DataType.__str__(self) def value_string(self) -> str: - return core.SecurityManagerOutOfBandFlag(self).name or "" + return core.SecurityManagerOutOfBandFlag(self).composite_name class SecurityManagerTKValue(FixedSizeBytesDataType): @@ -751,7 +758,7 @@ class LeSupportedFeatures(int, core.DataType): return self.to_bytes(length=bytes_length, byteorder="little") def value_string(self) -> str: - return hci.LeFeatureMask(self).name or "" + return hci.LeFeatureMask(self).composite_name @dataclasses.dataclass diff --git a/bumble/hci.py b/bumble/hci.py index cd6f9134..af3a8926 100644 --- a/bumble/hci.py +++ b/bumble/hci.py @@ -1322,7 +1322,7 @@ class LeFeature(SpecableEnum): MONITORING_ADVERTISERS = 64 FRAME_SPACE_UPDATE = 65 -class LeFeatureMask(enum.IntFlag): +class LeFeatureMask(utils.CompatibleIntFlag): LE_ENCRYPTION = 1 << LeFeature.LE_ENCRYPTION CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1 << LeFeature.CONNECTION_PARAMETERS_REQUEST_PROCEDURE EXTENDED_REJECT_INDICATION = 1 << LeFeature.EXTENDED_REJECT_INDICATION @@ -1463,7 +1463,7 @@ class LmpFeature(SpecableEnum): SLOT_AVAILABILITY_MASK = 138 TRAIN_NUDGING = 139 -class LmpFeatureMask(enum.IntFlag): +class LmpFeatureMask(utils.CompatibleIntFlag): # Page 0 (Legacy LMP features) LMP_3_SLOT_PACKETS = (1 << LmpFeature.LMP_3_SLOT_PACKETS) LMP_5_SLOT_PACKETS = (1 << LmpFeature.LMP_5_SLOT_PACKETS) diff --git a/bumble/utils.py b/bumble/utils.py index 358c9a93..4b9f0748 100644 --- a/bumble/utils.py +++ b/bumble/utils.py @@ -500,6 +500,22 @@ class OpenIntEnum(enum.IntEnum): return obj +# ----------------------------------------------------------------------------- +class CompatibleIntFlag(enum.IntFlag): + """ + Subclass of `enum.IntFlag` with a `composite_name` property that behaves like the + `name` property of the `enum.IntFlag` implementation for python vesions >= 3.11 + """ + + @property + def composite_name(self) -> str: + return '|'.join( + name + for flag in self.__class__ + if self.value & flag.value and (name := flag.name) is not None + ) + + # ----------------------------------------------------------------------------- class ByteSerializable(Protocol): """