diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 084a781bf62..19767c39284 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -6,6 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass import functools import logging +import sys from traceback import FrameSummary, extract_stack from typing import Any, TypeVar, cast @@ -19,14 +20,15 @@ _REPORTED_INTEGRATIONS: set[str] = set() _CallableT = TypeVar("_CallableT", bound=Callable) -@dataclass +@dataclass(kw_only=True) class IntegrationFrame: """Integration frame container.""" custom_integration: bool - filename: str frame: FrameSummary integration: str + module: str | None + relative_filename: str def get_integration_frame(exclude_integrations: set | None = None) -> IntegrationFrame: @@ -55,11 +57,20 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio if found_frame is None: raise MissingIntegrationFrame + found_module: str | None = None + for module, module_obj in dict(sys.modules).items(): + if not hasattr(module_obj, "__file__"): + continue + if module_obj.__file__ == found_frame.filename: + found_module = module + break + return IntegrationFrame( - path == "custom_components/", - found_frame.filename[index:], - found_frame, - integration, + custom_integration=path == "custom_components/", + frame=found_frame, + integration=integration, + module=found_module, + relative_filename=found_frame.filename[index:], ) @@ -121,7 +132,7 @@ def _report_integration( what, extra, integration_frame.integration, - integration_frame.filename, + integration_frame.relative_filename, found_frame.lineno, (found_frame.line or "?").strip(), ) diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 53d799a0400..6756f14bf08 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -1,10 +1,11 @@ """Test the frame helper.""" from collections.abc import Generator -from unittest.mock import Mock, patch +from unittest.mock import ANY, Mock, patch import pytest +from homeassistant.core import HomeAssistant from homeassistant.helpers import frame @@ -41,7 +42,28 @@ async def test_extract_frame_integration( """Test extracting the current frame from integration context.""" integration_frame = frame.get_integration_frame() assert integration_frame == frame.IntegrationFrame( - False, "homeassistant/components/hue/light.py", mock_integration_frame, "hue" + custom_integration=False, + frame=mock_integration_frame, + integration="hue", + module=None, + relative_filename="homeassistant/components/hue/light.py", + ) + + +async def test_extract_frame_resolve_module( + hass: HomeAssistant, enable_custom_integrations +) -> None: + """Test extracting the current frame from integration context.""" + from custom_components.test_integration_frame import call_get_integration_frame + + integration_frame = call_get_integration_frame() + + assert integration_frame == frame.IntegrationFrame( + custom_integration=True, + frame=ANY, + integration="test_integration_frame", + module="custom_components.test_integration_frame", + relative_filename="custom_components/test_integration_frame/__init__.py", ) @@ -80,7 +102,11 @@ async def test_extract_frame_integration_with_excluded_integration( ) assert integration_frame == frame.IntegrationFrame( - False, "homeassistant/components/mdns/light.py", correct_frame, "mdns" + custom_integration=False, + frame=correct_frame, + integration="mdns", + module=None, + relative_filename="homeassistant/components/mdns/light.py", ) diff --git a/tests/testing_config/custom_components/test_integration_frame/__init__.py b/tests/testing_config/custom_components/test_integration_frame/__init__.py new file mode 100644 index 00000000000..d342509d52e --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_frame/__init__.py @@ -0,0 +1,8 @@ +"""An integration which calls helpers.frame.get_integration_frame.""" + +from homeassistant.helpers import frame + + +def call_get_integration_frame() -> frame.IntegrationFrame: + """Call get_integration_frame.""" + return frame.get_integration_frame() diff --git a/tests/testing_config/custom_components/test_integration_frame/manifest.json b/tests/testing_config/custom_components/test_integration_frame/manifest.json new file mode 100644 index 00000000000..3c3eceec28d --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_frame/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "test_integration_frame", + "name": "Test Integration Frame", + "documentation": "http://example.com", + "requirements": [], + "dependencies": [], + "codeowners": [], + "version": "1.2.3" +}