Coverage for src / bluetooth_sig / registry / core / formattypes.py: 80%
64 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""Format Types registry for Bluetooth SIG characteristic format type definitions."""
3from __future__ import annotations
5import logging
7import msgspec
9from bluetooth_sig.registry.base import BaseGenericRegistry
10from bluetooth_sig.registry.utils import find_bluetooth_sig_path
11from bluetooth_sig.types.registry.formattypes import FormatTypeInfo
13logger = logging.getLogger(__name__)
16class FormatTypesRegistry(BaseGenericRegistry[FormatTypeInfo]):
17 """Registry for Bluetooth characteristic format types with lazy loading.
19 This registry loads format type definitions from the official Bluetooth SIG
20 assigned_numbers YAML file, providing authoritative format type information
21 from the specification.
22 """
24 def __init__(self) -> None:
25 """Initialize the format types registry."""
26 super().__init__()
27 self._format_types: dict[int, FormatTypeInfo] = {}
28 self._format_types_by_name: dict[str, FormatTypeInfo] = {}
30 def _load(self) -> None:
31 """Perform the actual loading of format types data."""
32 base_path = find_bluetooth_sig_path()
33 if not base_path:
34 logger.warning("Bluetooth SIG path not found. Format types registry will be empty.")
35 self._loaded = True
36 return
38 yaml_path = base_path.parent / "core" / "formattypes.yaml"
39 if not yaml_path.exists():
40 logger.warning(
41 "Format types YAML file not found at %s. Registry will be empty.",
42 yaml_path,
43 )
44 self._loaded = True
45 return
47 try:
48 with yaml_path.open("r", encoding="utf-8") as f:
49 data = msgspec.yaml.decode(f.read())
51 if not data or "formattypes" not in data:
52 logger.warning("Invalid format types YAML format. Registry will be empty.")
53 self._loaded = True
54 return
56 for item in data["formattypes"]:
57 value = item.get("value")
58 short_name = item.get("short_name")
59 description = item.get("description")
60 exponent = item.get("exponent")
61 size = item.get("size")
63 if value is None or not short_name:
64 continue
66 # Handle hex values in YAML (e.g., 0x01)
67 if isinstance(value, str):
68 value = int(value, 16)
70 format_type_info = FormatTypeInfo(
71 value=value,
72 short_name=short_name,
73 description=description,
74 exponent=exponent,
75 size=size,
76 )
78 self._format_types[value] = format_type_info
79 self._format_types_by_name[short_name.lower()] = format_type_info
81 logger.info("Loaded %d format types from specification", len(self._format_types))
82 except (FileNotFoundError, OSError, msgspec.DecodeError, KeyError) as e:
83 logger.warning(
84 "Failed to load format types from YAML: %s. Registry will be empty.",
85 e,
86 )
88 self._loaded = True
90 def get_format_type_info(self, value: int) -> FormatTypeInfo | None:
91 """Get format type info by value (lazy loads on first call).
93 Args:
94 value: The format type value (e.g., 0x01 for boolean)
96 Returns:
97 FormatTypeInfo object, or None if not found
98 """
99 self._ensure_loaded()
100 with self._lock:
101 return self._format_types.get(value)
103 def get_format_type_by_name(self, name: str) -> FormatTypeInfo | None:
104 """Get format type info by short name (lazy loads on first call).
106 Args:
107 name: Format type short name (case-insensitive, e.g., "boolean", "utf8s")
109 Returns:
110 FormatTypeInfo object, or None if not found
111 """
112 self._ensure_loaded()
113 with self._lock:
114 return self._format_types_by_name.get(name.lower())
116 def is_known_format_type(self, value: int) -> bool:
117 """Check if format type is known (lazy loads on first call).
119 Args:
120 value: The format type value to check
122 Returns:
123 True if the format type is registered, False otherwise
124 """
125 self._ensure_loaded()
126 with self._lock:
127 return value in self._format_types
129 def get_all_format_types(self) -> dict[int, FormatTypeInfo]:
130 """Get all registered format types (lazy loads on first call).
132 Returns:
133 Dictionary mapping format type values to FormatTypeInfo objects
134 """
135 self._ensure_loaded()
136 with self._lock:
137 return self._format_types.copy()
140# Global singleton instance
141format_types_registry = FormatTypesRegistry()