Coverage for custom_components/supernotify/common.py: 98%

56 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-18 09:29 +0000

1"""Miscellaneous helper functions. 

2 

3No dependencies permitted 

4""" 

5 

6import time 

7from dataclasses import dataclass, field 

8from typing import Any 

9 

10 

11def format_timestamp(v: float | None) -> str | None: 

12 return time.strftime("%H:%M:%S", time.localtime(v)) if v else None 

13 

14 

15def safe_get(probably_a_dict: dict[Any, Any] | None, key: Any, default: Any = None) -> Any: 

16 probably_a_dict = probably_a_dict or {} 

17 return probably_a_dict.get(key, default) 

18 

19 

20def safe_extend(target: list[Any], extension: list[Any] | tuple[Any] | Any) -> list[Any]: 

21 if isinstance(extension, list | tuple): 

22 target.extend(extension) 

23 elif extension: 

24 target.append(extension) 

25 return target 

26 

27 

28def ensure_list(v: Any) -> list[Any]: 

29 if v is None: 

30 return [] 

31 if isinstance(v, list): 

32 return v 

33 if isinstance(v, tuple): 

34 return list(v) 

35 return [v] 

36 

37 

38def ensure_dict(v: Any, default: Any = None) -> dict[Any, Any]: 

39 if v is None: 

40 return {} 

41 if isinstance(v, dict): 

42 return v 

43 if isinstance(v, set | list): 

44 return dict.fromkeys(v, default) 

45 return {v: default} 

46 

47 

48def update_dict_list( 

49 target: list[dict[Any, Any]], to_add: list[dict[Any, Any]], to_remove: list[dict[Any, Any]] 

50) -> list[dict[Any, Any]]: 

51 updated = [d for d in target if d not in to_remove] 

52 updated.extend(to_add) 

53 return updated 

54 

55 

56@dataclass 

57class CallRecord: 

58 elapsed: float = field() 

59 domain: str | None = field(default=None) 

60 service: str | None = field(default=None) 

61 action_data: dict[str, Any] | None = field(default=None) 

62 target_data: dict[str, Any] | None = field(default=None) 

63 exception: str | None = field(default=None) 

64 

65 def contents(self) -> tuple[str | dict[str, Any] | float | None, ...]: 

66 if self.exception: 

67 return (self.domain, self.service, self.action_data, self.target_data, self.exception, self.elapsed) 

68 return (self.domain, self.service, self.action_data, self.target_data, self.elapsed) 

69 

70 

71@dataclass 

72class DebugTrace: 

73 message: str | None = field(default=None) 

74 title: str | None = field(default=None) 

75 data: dict[str, Any] | None = field(default_factory=lambda: {}) 

76 target: list[str] | str | None = field(default=None) 

77 resolved: dict[str, dict[str, Any]] = field(init=False, default_factory=lambda: {}) 

78 delivery_selection: dict[str, list[str]] = field(default_factory=lambda: {}) 

79 

80 def contents( 

81 self, 

82 ) -> tuple[ 

83 str | None, str | None, dict[str, Any] | None, list[str] | str | None, dict[str, dict[str, Any]], dict[str, list[str]] 

84 ]: 

85 return (self.message, self.title, self.data, self.target, self.resolved, self.delivery_selection)