Home Assistant Unofficial Reference 2024.12.1
config.py
Go to the documentation of this file.
1 """Config helpers for Alexa."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 import asyncio
7 import logging
8 from typing import Any
9 
10 from yarl import URL
11 
12 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
13 from homeassistant.helpers.storage import Store
14 
15 from .const import DOMAIN
16 from .entities import TRANSLATION_TABLE
17 from .state_report import async_enable_proactive_mode
18 
19 STORE_AUTHORIZED = "authorized"
20 
21 _LOGGER = logging.getLogger(__name__)
22 
23 
24 class AbstractConfig(ABC):
25  """Hold the configuration for Alexa."""
26 
27  _store: AlexaConfigStore
28  _unsub_proactive_report: CALLBACK_TYPE | None = None
29 
30  def __init__(self, hass: HomeAssistant) -> None:
31  """Initialize abstract config."""
32  self.hasshass = hass
33  self._enable_proactive_mode_lock_enable_proactive_mode_lock = asyncio.Lock()
34  self._on_deinitialize: list[CALLBACK_TYPE] = []
35 
36  async def async_initialize(self) -> None:
37  """Perform async initialization of config."""
38  self._store_store = AlexaConfigStore(self.hasshass)
39  await self._store_store.async_load()
40 
41  @callback
42  def async_deinitialize(self) -> None:
43  """Remove listeners."""
44  _LOGGER.debug("async_deinitialize")
45  while self._on_deinitialize:
46  self._on_deinitialize.pop()()
47 
48  @property
49  def supports_auth(self) -> bool:
50  """Return if config supports auth."""
51  return False
52 
53  @property
54  def should_report_state(self) -> bool:
55  """Return if states should be proactively reported."""
56  return False
57 
58  @property
59  @abstractmethod
60  def endpoint(self) -> str | URL | None:
61  """Endpoint for report state."""
62 
63  @property
64  @abstractmethod
65  def locale(self) -> str | None:
66  """Return config locale."""
67 
68  @property
69  def entity_config(self) -> dict[str, Any]:
70  """Return entity config."""
71  return {}
72 
73  @property
74  def is_reporting_states(self) -> bool:
75  """Return if proactive mode is enabled."""
76  return self._unsub_proactive_report_unsub_proactive_report is not None
77 
78  @callback
79  @abstractmethod
80  def user_identifier(self) -> str:
81  """Return an identifier for the user that represents this config."""
82 
83  async def async_enable_proactive_mode(self) -> None:
84  """Enable proactive mode."""
85  _LOGGER.debug("Enable proactive mode")
86  async with self._enable_proactive_mode_lock:
87  if self._unsub_proactive_report is not None:
88  return
89  self._unsub_proactive_report_unsub_proactive_report = await async_enable_proactive_mode(
90  self.hasshass, self
91  )
92 
93  async def async_disable_proactive_mode(self) -> None:
94  """Disable proactive mode."""
95  _LOGGER.debug("Disable proactive mode")
96  if unsub_func := self._unsub_proactive_report_unsub_proactive_report:
97  unsub_func()
98  self._unsub_proactive_report_unsub_proactive_report = None
99 
100  @callback
101  def should_expose(self, entity_id: str) -> bool:
102  """If an entity should be exposed."""
103  return False
104 
105  def generate_alexa_id(self, entity_id: str) -> str:
106  """Return the alexa ID for an entity ID."""
107  return entity_id.replace(".", "#").translate(TRANSLATION_TABLE)
108 
109  @callback
110  def async_invalidate_access_token(self) -> None:
111  """Invalidate access token."""
112  raise NotImplementedError
113 
114  async def async_get_access_token(self) -> str | None:
115  """Get an access token."""
116  raise NotImplementedError
117 
118  async def async_accept_grant(self, code: str) -> str | None:
119  """Accept a grant."""
120  raise NotImplementedError
121 
122  @property
123  def authorized(self) -> bool:
124  """Return authorization status."""
125  return self._store_store.authorized
126 
127  async def set_authorized(self, authorized: bool) -> None:
128  """Set authorization status.
129 
130  - Set when an incoming message is received from Alexa.
131  - Unset if state reporting fails
132  """
133  self._store_store.set_authorized(authorized)
134  if self.should_report_stateshould_report_state != self.is_reporting_statesis_reporting_states:
135  if self.should_report_stateshould_report_state:
136  try:
137  await self.async_enable_proactive_modeasync_enable_proactive_mode()
138  except Exception:
139  # We failed to enable proactive mode, unset authorized flag
140  self._store_store.set_authorized(False)
141  raise
142  else:
143  await self.async_disable_proactive_modeasync_disable_proactive_mode()
144 
145 
147  """A configuration store for Alexa."""
148 
149  _STORAGE_VERSION = 1
150  _STORAGE_KEY = DOMAIN
151 
152  def __init__(self, hass: HomeAssistant) -> None:
153  """Initialize a configuration store."""
154  self._data_data: dict[str, Any] | None = None
155  self._hass_hass = hass
156  self._store: Store = Store(hass, self._STORAGE_VERSION_STORAGE_VERSION, self._STORAGE_KEY_STORAGE_KEY)
157 
158  @property
159  def authorized(self) -> bool:
160  """Return authorization status."""
161  assert self._data_data is not None
162  return bool(self._data_data[STORE_AUTHORIZED])
163 
164  @callback
165  def set_authorized(self, authorized: bool) -> None:
166  """Set authorization status."""
167  if self._data_data is not None and authorized != self._data_data[STORE_AUTHORIZED]:
168  self._data_data[STORE_AUTHORIZED] = authorized
169  self._store.async_delay_save(lambda: self._data_data, 1.0)
170 
171  async def async_load(self) -> None:
172  """Load saved configuration from disk."""
173  if data := await self._store.async_load():
174  self._data_data = data
175  else:
176  self._data_data = {STORE_AUTHORIZED: False}
None __init__(self, HomeAssistant hass)
Definition: config.py:30
None set_authorized(self, bool authorized)
Definition: config.py:127
None __init__(self, HomeAssistant hass)
Definition: config.py:152
None async_load(HomeAssistant hass)
None async_delay_save(self, Callable[[], _T] data_func, float delay=0)
Definition: storage.py:444