1 """Provide frame helper for finding the current frame context."""
3 from __future__
import annotations
6 from collections.abc
import Callable
7 from dataclasses
import dataclass
13 from types
import FrameType
14 from typing
import Any, cast
16 from propcache
import cached_property
22 async_get_issue_integration,
23 async_suggest_report_issue,
26 _LOGGER = logging.getLogger(__name__)
29 _REPORTED_INTEGRATIONS: set[str] = set()
32 @dataclass(kw_only=True)
34 """Integration frame container."""
36 custom_integration: bool
39 relative_filename: str
44 """Return the line number of the frame."""
45 return self.frame.f_lineno
49 """Return the filename of the frame."""
50 return self.frame.f_code.co_filename
54 """Return the line of the frame."""
55 return (linecache.getline(self.
filenamefilename, self.
line_numberline_number)
or "?").strip()
59 """Return a logger by checking the current integration frame.
61 If Python is unable to access the sources files, the call stack frame
62 will be missing information, so let's guard by requiring a fallback name.
63 https://github.com/home-assistant/core/issues/24982
67 except MissingIntegrationFrame:
68 return logging.getLogger(fallback_name)
70 if integration_frame.custom_integration:
71 logger_name = f
"custom_components.{integration_frame.integration}"
73 logger_name = f
"homeassistant.components.{integration_frame.integration}"
75 return logging.getLogger(logger_name)
79 """Return the current frame."""
81 return sys._getframe(depth + 1)
85 """Return the frame, integration and integration path of the current stack frame."""
87 if not exclude_integrations:
88 exclude_integrations = set()
91 while frame
is not None:
92 filename = frame.f_code.co_filename
94 for path
in (
"custom_components/",
"homeassistant/components/"):
96 index = filename.index(path)
97 start = index + len(path)
98 end = filename.index(
"/", start)
99 integration = filename[start:end]
100 if integration
not in exclude_integrations:
107 if found_frame
is not None:
112 if found_frame
is None:
113 raise MissingIntegrationFrame
115 found_module: str |
None =
None
116 for module, module_obj
in dict(sys.modules).items():
117 if not hasattr(module_obj,
"__file__"):
119 if module_obj.__file__ == found_frame.f_code.co_filename:
120 found_module = module
124 custom_integration=path ==
"custom_components/",
125 integration=integration,
127 relative_filename=found_frame.f_code.co_filename[index:],
133 """Raised when no integration is found in the frame."""
139 exclude_integrations: set[str] |
None =
None,
140 error_if_core: bool =
True,
141 error_if_integration: bool =
False,
142 level: int = logging.WARNING,
143 log_custom_component_only: bool =
False,
145 """Report incorrect usage.
147 If error_if_core is True, raise instead of log if an integration is not found
148 when unwinding the stack frame.
149 If error_if_integration is True, raise instead of log if an integration is found
150 when unwinding the stack frame.
152 core_behavior = ReportBehavior.ERROR
if error_if_core
else ReportBehavior.LOG
153 core_integration_behavior = (
154 ReportBehavior.ERROR
if error_if_integration
else ReportBehavior.LOG
156 custom_integration_behavior = core_integration_behavior
158 if log_custom_component_only:
159 if core_behavior
is ReportBehavior.LOG:
160 core_behavior = ReportBehavior.IGNORE
161 if core_integration_behavior
is ReportBehavior.LOG:
162 core_integration_behavior = ReportBehavior.IGNORE
166 core_behavior=core_behavior,
167 core_integration_behavior=core_integration_behavior,
168 custom_integration_behavior=custom_integration_behavior,
169 exclude_integrations=exclude_integrations,
175 """Enum for behavior on code usage."""
178 """Ignore the code usage."""
180 """Log the code usage."""
182 """Raise an error on code usage."""
188 breaks_in_ha_version: str |
None =
None,
189 core_behavior: ReportBehavior = ReportBehavior.ERROR,
190 core_integration_behavior: ReportBehavior = ReportBehavior.LOG,
191 custom_integration_behavior: ReportBehavior = ReportBehavior.LOG,
192 exclude_integrations: set[str] |
None =
None,
193 integration_domain: str |
None =
None,
194 level: int = logging.WARNING,
196 """Report incorrect code usage.
198 :param what: will be wrapped with "Detected that integration 'integration' {what}.
199 Please create a bug report at https://..."
200 :param breaks_in_ha_version: if set, the report will be adjusted to specify the
202 :param exclude_integrations: skip specified integration when reviewing the stack.
203 If no integration is found, the core behavior will be applied
204 :param integration_domain: fallback for identifying the integration if the
209 exclude_integrations=exclude_integrations
211 except MissingIntegrationFrame
as err:
218 breaks_in_ha_version,
220 core_integration_behavior,
221 custom_integration_behavior,
225 msg = f
"Detected code that {what}. Please report this issue"
226 if core_behavior
is ReportBehavior.ERROR:
227 raise RuntimeError(msg)
from err
228 if core_behavior
is ReportBehavior.LOG:
229 if breaks_in_ha_version:
231 f
"Detected code that {what}. This will stop working in Home "
232 f
"Assistant {breaks_in_ha_version}, please report this issue"
234 _LOGGER.warning(msg, stack_info=
True)
237 integration_behavior = core_integration_behavior
238 if integration_frame.custom_integration:
239 integration_behavior = custom_integration_behavior
241 if integration_behavior
is not ReportBehavior.IGNORE:
244 breaks_in_ha_version,
247 integration_behavior
is ReportBehavior.ERROR,
252 hass: HomeAssistant |
None,
254 breaks_in_ha_version: str |
None,
255 integration: Integration,
256 core_integration_behavior: ReportBehavior,
257 custom_integration_behavior: ReportBehavior,
260 """Report incorrect usage in an integration (identified via domain).
264 integration_behavior = core_integration_behavior
265 if not integration.is_built_in:
266 integration_behavior = custom_integration_behavior
268 if integration_behavior
is ReportBehavior.IGNORE:
272 key = f
"{integration.domain}:{what}"
274 integration_behavior
is not ReportBehavior.ERROR
275 and key
in _REPORTED_INTEGRATIONS
278 _REPORTED_INTEGRATIONS.add(key)
281 integration_type =
"" if integration.is_built_in
else "custom "
284 "Detected that %sintegration '%s' %s. %s %s",
288 f
"This will stop working in Home Assistant {breaks_in_ha_version}, please"
289 if breaks_in_ha_version
294 if integration_behavior
is ReportBehavior.ERROR:
296 f
"Detected that {integration_type}integration "
297 f
"'{integration.domain}' {what}. Please {report_issue}"
303 breaks_in_ha_version: str |
None,
304 integration_frame: IntegrationFrame,
305 level: int = logging.WARNING,
308 """Report incorrect usage in an integration (identified via frame).
313 key = f
"{integration_frame.filename}:{integration_frame.line_number}"
314 if not error
and key
in _REPORTED_INTEGRATIONS:
316 _REPORTED_INTEGRATIONS.add(key)
320 integration_domain=integration_frame.integration,
321 module=integration_frame.module,
323 integration_type =
"custom " if integration_frame.custom_integration
else ""
326 "Detected that %sintegration '%s' %s at %s, line %s: %s. %s %s",
328 integration_frame.integration,
330 integration_frame.relative_filename,
331 integration_frame.line_number,
332 integration_frame.line,
333 f
"This will stop working in Home Assistant {breaks_in_ha_version}, please"
334 if breaks_in_ha_version
341 f
"Detected that {integration_type}integration "
342 f
"'{integration_frame.integration}' {what} at "
343 f
"{integration_frame.relative_filename}, line "
344 f
"{integration_frame.line_number}: {integration_frame.line}. "
345 f
"Please {report_issue}"
349 def warn_use[_CallableT: Callable](func: _CallableT, what: str) -> _CallableT:
350 """Mock a function to warn when it was about to be used."""
351 if asyncio.iscoroutinefunction(func):
353 @functools.wraps(func)
354 async
def report_use(*args: Any, **kwargs: Any) ->
None:
359 @functools.wraps(func)
360 def report_use(*args: Any, **kwargs: Any) ->
None:
363 return cast(_CallableT, report_use)
367 """Report a non-thread safe operation."""
369 f
"calls {what} from a thread other than the event loop, "
370 "which may cause Home Assistant to crash or data to corrupt. "
371 "For more information, see "
372 "https://developers.home-assistant.io/docs/asyncio_thread_safety/"
373 f
"#{what.replace('.', '')}",
375 error_if_integration=
True,
HomeAssistant|None async_get_hass_or_none()
None _report_integration_frame(str what, str|None breaks_in_ha_version, IntegrationFrame integration_frame, int level=logging.WARNING, bool error=False)
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
FrameType get_current_frame(int depth=0)
None report(str what, *set[str]|None exclude_integrations=None, bool error_if_core=True, bool error_if_integration=False, int level=logging.WARNING, bool log_custom_component_only=False)
None report_non_thread_safe_operation(str what)
logging.Logger get_integration_logger(str fallback_name)
IntegrationFrame get_integration_frame(set|None exclude_integrations=None)
None _report_integration_domain(HomeAssistant|None hass, str what, str|None breaks_in_ha_version, Integration integration, ReportBehavior core_integration_behavior, ReportBehavior custom_integration_behavior, int level)
Integration|None async_get_issue_integration(HomeAssistant|None hass, str|None integration_domain)
str async_suggest_report_issue(HomeAssistant|None hass, *Integration|None integration=None, str|None integration_domain=None, str|None module=None)