Coverage for src / bluetooth_sig / registry / utils.py: 86%

42 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Common utilities for registry modules.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import Any, cast 

7 

8import msgspec 

9 

10from bluetooth_sig.gatt.constants import UINT16_MAX 

11from bluetooth_sig.types.uuid import BluetoothUUID 

12 

13 

14def load_yaml_uuids(file_path: Path) -> list[dict[str, Any]]: 

15 """Load UUID entries from a YAML file. 

16 

17 Args: 

18 file_path: Path to the YAML file 

19 

20 Returns: 

21 List of UUID entry dictionaries 

22 """ 

23 if not file_path.exists(): 

24 return [] 

25 

26 with file_path.open("r", encoding="utf-8") as file_handle: 

27 data = msgspec.yaml.decode(file_handle.read()) 

28 

29 if not isinstance(data, dict): 

30 return [] 

31 

32 data_dict = cast("dict[str, Any]", data) 

33 uuid_entries = data_dict.get("uuids") 

34 if not isinstance(uuid_entries, list): 

35 return [] 

36 

37 typed_entries: list[dict[str, Any]] = [] 

38 for entry in uuid_entries: 

39 if isinstance(entry, dict): 

40 typed_entries.append(cast("dict[str, Any]", entry)) 

41 

42 return typed_entries 

43 

44 

45def normalize_uuid_string(uuid: str | int) -> str: 

46 """Normalize a UUID string or int to uppercase hex without 0x prefix. 

47 

48 Args: 

49 uuid: UUID as string (with or without 0x) or int 

50 

51 Returns: 

52 Normalized UUID string 

53 """ 

54 return uuid.replace("0x", "").replace("0X", "") if isinstance(uuid, str) else hex(uuid)[2:].upper() 

55 

56 

57def find_bluetooth_sig_path() -> Path | None: 

58 """Find the Bluetooth SIG assigned_numbers directory. 

59 

60 Returns: 

61 Path to the uuids directory, or None if not found 

62 """ 

63 # Try development location first (git submodule) 

64 project_root = Path(__file__).parent.parent.parent.parent 

65 base_path = project_root / "bluetooth_sig" / "assigned_numbers" / "uuids" 

66 

67 if base_path.exists(): 

68 return base_path 

69 

70 # Try installed package location 

71 pkg_root = Path(__file__).parent.parent 

72 base_path = pkg_root / "bluetooth_sig" / "assigned_numbers" / "uuids" 

73 

74 return base_path if base_path.exists() else None 

75 

76 

77def parse_bluetooth_uuid(uuid: str | int | BluetoothUUID) -> BluetoothUUID: 

78 """Parse various UUID formats into a BluetoothUUID. 

79 

80 Args: 

81 uuid: UUID as string (with or without 0x), int, or BluetoothUUID 

82 

83 Returns: 

84 BluetoothUUID instance 

85 

86 Raises: 

87 ValueError: If UUID format is invalid 

88 """ 

89 if isinstance(uuid, BluetoothUUID): 

90 return uuid 

91 if isinstance(uuid, int): 

92 uuid_str = hex(uuid)[2:].upper() 

93 # Pad to 4 characters only for 16-bit UUIDs (0x0000 - 0xFFFF) 

94 if 0 <= uuid <= UINT16_MAX: 

95 uuid_str = uuid_str.zfill(4) 

96 return BluetoothUUID(uuid_str) 

97 uuid_str = str(uuid).replace("0x", "").replace("0X", "") 

98 return BluetoothUUID(uuid_str)