Coverage for src / bluetooth_sig / core / async_context.py: 85%
33 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"""Async context manager for device parsing sessions."""
3from __future__ import annotations
5from collections.abc import Mapping
6from types import TracebackType
7from typing import Any, TypeVar, cast, overload
9from ..gatt.characteristics.base import BaseCharacteristic
10from ..types import CharacteristicContext
11from ..types.uuid import BluetoothUUID
12from .translator import BluetoothSIGTranslator
14# Type variable for generic characteristic return types
15T = TypeVar("T")
18class AsyncParsingSession:
19 """Async context manager for parsing sessions.
21 Maintains parsing context across multiple async operations.
23 Example::
25 async with AsyncParsingSession() as session:
26 result1 = await session.parse("2A19", data1)
27 result2 = await session.parse("2A6E", data2)
28 # Context automatically shared between parses
29 """
31 def __init__(
32 self,
33 translator: BluetoothSIGTranslator,
34 ctx: CharacteristicContext | None = None,
35 ) -> None:
36 """Initialize parsing session.
38 Args:
39 translator: Translator instance to use for parsing
40 ctx: Optional initial context
41 """
42 self.translator = translator
43 self.context = ctx
44 # Store parsed characteristic values for context sharing
45 self.results: dict[str, Any] = {}
47 async def __aenter__(self) -> AsyncParsingSession:
48 """Enter async context."""
49 return self
51 async def __aexit__(
52 self,
53 exc_type: type[BaseException] | None,
54 exc_val: BaseException | None,
55 exc_tb: TracebackType | None,
56 ) -> bool:
57 """Exit async context."""
58 # Cleanup if needed
59 return False
61 @overload
62 async def parse(
63 self,
64 char: type[BaseCharacteristic[T]],
65 data: bytes,
66 ) -> T: ...
68 @overload
69 async def parse(
70 self,
71 char: str | BluetoothUUID,
72 data: bytes,
73 ) -> Any: ... # noqa: ANN401 # Runtime UUID dispatch cannot be type-safe
75 async def parse(
76 self,
77 char: str | BluetoothUUID | type[BaseCharacteristic[T]],
78 data: bytes,
79 ) -> T | Any: # noqa: ANN401 # Runtime UUID dispatch cannot be type-safe
80 """Parse characteristic with accumulated context.
82 Args:
83 char: Characteristic class (type-safe) or UUID string/BluetoothUUID (not type-safe).
84 data: Raw bytes
86 Returns:
87 Parsed characteristic value. Return type is inferred when passing characteristic class.
88 """
89 # Update context with previous results
90 # Cast dict to Mapping to satisfy CharacteristicContext type requirements
91 results_as_mapping = cast(Mapping[str, Any], self.results)
93 if self.context is None:
94 self.context = CharacteristicContext(other_characteristics=results_as_mapping)
95 else:
96 self.context = CharacteristicContext(
97 device_info=self.context.device_info,
98 advertisement=self.context.advertisement,
99 other_characteristics=results_as_mapping,
100 raw_service=self.context.raw_service,
101 )
103 # Handle characteristic class input (type-safe path)
104 if isinstance(char, type) and issubclass(char, BaseCharacteristic):
105 result = await self.translator.parse_characteristic_async(char, data, self.context)
106 # Store result using UUID string key
107 char_instance = char()
108 uuid_str = str(char_instance.uuid)
109 self.results[uuid_str] = result
110 return result
112 # Parse with context (not type-safe path)
113 uuid_str = str(char) if isinstance(char, BluetoothUUID) else char
114 result = await self.translator.parse_characteristic_async(uuid_str, data, self.context)
116 # Store result for future context using string UUID key
117 self.results[uuid_str] = result
119 return result