Speed up the frame helper (#112562)

This commit is contained in:
J. Nick Koston 2024-03-06 20:54:09 -10:00 committed by GitHub
parent 3ccbb2c87a
commit 1fb9cfe37e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 460 additions and 338 deletions

View file

@ -6,15 +6,21 @@ from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass
import functools
import linecache
import logging
import sys
from traceback import FrameSummary, extract_stack
from typing import Any, TypeVar, cast
from types import FrameType
from typing import TYPE_CHECKING, Any, TypeVar, cast
from homeassistant.core import HomeAssistant, async_get_hass
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import async_suggest_report_issue
if TYPE_CHECKING:
from functools import cached_property
else:
from homeassistant.backports.functools import cached_property
_LOGGER = logging.getLogger(__name__)
# Keep track of integrations already reported to prevent flooding
@ -28,10 +34,25 @@ class IntegrationFrame:
"""Integration frame container."""
custom_integration: bool
frame: FrameSummary
integration: str
module: str | None
relative_filename: str
_frame: FrameType
@cached_property
def line_number(self) -> int:
"""Return the line number of the frame."""
return self._frame.f_lineno
@cached_property
def filename(self) -> str:
"""Return the filename of the frame."""
return self._frame.f_code.co_filename
@cached_property
def line(self) -> str:
"""Return the line of the frame."""
return (linecache.getline(self.filename, self.line_number) or "?").strip()
def get_integration_logger(fallback_name: str) -> logging.Logger:
@ -54,19 +75,28 @@ def get_integration_logger(fallback_name: str) -> logging.Logger:
return logging.getLogger(logger_name)
def get_current_frame(depth: int = 0) -> FrameType:
"""Return the current frame."""
# Add one to depth since get_current_frame is included
return sys._getframe(depth + 1) # pylint: disable=protected-access
def get_integration_frame(exclude_integrations: set | None = None) -> IntegrationFrame:
"""Return the frame, integration and integration path of the current stack frame."""
found_frame = None
if not exclude_integrations:
exclude_integrations = set()
for frame in reversed(extract_stack()):
frame: FrameType | None = get_current_frame()
while frame is not None:
filename = frame.f_code.co_filename
for path in ("custom_components/", "homeassistant/components/"):
try:
index = frame.filename.index(path)
index = filename.index(path)
start = index + len(path)
end = frame.filename.index("/", start)
integration = frame.filename[start:end]
end = filename.index("/", start)
integration = filename[start:end]
if integration not in exclude_integrations:
found_frame = frame
@ -77,6 +107,8 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio
if found_frame is not None:
break
frame = frame.f_back
if found_frame is None:
raise MissingIntegrationFrame
@ -84,16 +116,16 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio
for module, module_obj in dict(sys.modules).items():
if not hasattr(module_obj, "__file__"):
continue
if module_obj.__file__ == found_frame.filename:
if module_obj.__file__ == found_frame.f_code.co_filename:
found_module = module
break
return IntegrationFrame(
custom_integration=path == "custom_components/",
frame=found_frame,
integration=integration,
module=found_module,
relative_filename=found_frame.filename[index:],
relative_filename=found_frame.f_code.co_filename[index:],
_frame=found_frame,
)
@ -137,9 +169,8 @@ def _report_integration(
Async friendly.
"""
found_frame = integration_frame.frame
# Keep track of integrations already reported to prevent flooding
key = f"{found_frame.filename}:{found_frame.lineno}"
key = f"{integration_frame.filename}:{integration_frame.line_number}"
if key in _REPORTED_INTEGRATIONS:
return
_REPORTED_INTEGRATIONS.add(key)
@ -160,8 +191,8 @@ def _report_integration(
integration_frame.integration,
what,
integration_frame.relative_filename,
found_frame.lineno,
(found_frame.line or "?").strip(),
integration_frame.line_number,
integration_frame.line,
report_issue,
)