Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to pass integration domain to report_usage #130705

Draft
wants to merge 11 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions homeassistant/helpers/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@

from propcache import cached_property

from homeassistant.core import async_get_hass_or_none
from homeassistant.core import HomeAssistant, async_get_hass_or_none
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import async_suggest_report_issue
from homeassistant.loader import (
Integration,
async_get_issue_integration,
async_suggest_report_issue,
)

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -185,6 +189,7 @@ def report_usage(
core_integration_behavior: ReportBehavior = ReportBehavior.LOG,
custom_integration_behavior: ReportBehavior = ReportBehavior.LOG,
exclude_integrations: set[str] | None = None,
integration_domain: str | None = None,
level: int = logging.WARNING,
) -> None:
"""Report incorrect code usage.
Expand All @@ -196,6 +201,19 @@ def report_usage(
exclude_integrations=exclude_integrations
)
except MissingIntegrationFrame as err:
if integration := async_get_issue_integration(
hass := async_get_hass_or_none(), integration_domain
):
_report_integration_domain(
hass,
what,
integration,
core_integration_behavior=core_integration_behavior,
custom_integration_behavior=custom_integration_behavior,
exclude_integrations=exclude_integrations,
level=level,
)
return
msg = f"Detected code that {what}. Please report this issue."
if core_behavior is ReportBehavior.ERROR:
raise RuntimeError(msg) from err
Expand All @@ -208,12 +226,57 @@ def report_usage(
integration_behavior = custom_integration_behavior

if integration_behavior is not ReportBehavior.IGNORE:
_report_integration(
_report_integration_frame(
what, integration_frame, level, integration_behavior is ReportBehavior.ERROR
)


def _report_integration(
def _report_integration_domain(
hass: HomeAssistant | None,
what: str,
integration: Integration,
*,
core_integration_behavior: ReportBehavior,
custom_integration_behavior: ReportBehavior,
exclude_integrations: set[str] | None,
level: int,
) -> None:
integration_behavior = core_integration_behavior
if integration.is_built_in:
integration_behavior = custom_integration_behavior

if integration_behavior is ReportBehavior.IGNORE or (
exclude_integrations and integration.domain in exclude_integrations
):
return

# Keep track of integrations already reported to prevent flooding
key = f"{integration.domain}:{what}"
if (
integration_behavior is not ReportBehavior.ERROR
and key in _REPORTED_INTEGRATIONS
):
return
_REPORTED_INTEGRATIONS.add(key)

report_issue = async_suggest_report_issue(hass, integration=integration)
integration_type = "" if integration.is_built_in else "custom "
_LOGGER.log(
level,
"Detected that %sintegration '%s' %s, please %s",
integration_type,
integration.domain,
what,
report_issue,
)
if integration_behavior is ReportBehavior.ERROR:
raise RuntimeError(
f"Detected that {integration_type}integration "
f"'{integration.domain}' {what}, please {report_issue}."
)


def _report_integration_frame(
what: str,
integration_frame: IntegrationFrame,
level: int = logging.WARNING,
Expand Down
38 changes: 26 additions & 12 deletions homeassistant/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1685,6 +1685,29 @@ def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool:
return module in hass.data[DATA_COMPONENTS]


@callback
def async_get_issue_integration(
hass: HomeAssistant | None,
integration_domain: str | None,
) -> Integration | None:
"""Return details of an integration for issue reporting."""
integration: Integration | None = None
if not hass or not integration_domain:
# We are unable to get the integration
return None

if (comps_or_future := hass.data.get(DATA_CUSTOM_COMPONENTS)) and not isinstance(
comps_or_future, asyncio.Future
):
integration = comps_or_future.get(integration_domain)

if not integration:
with suppress(IntegrationNotLoaded):
integration = async_get_loaded_integration(hass, integration_domain)

return integration


@callback
def async_get_issue_tracker(
hass: HomeAssistant | None,
Expand All @@ -1698,20 +1721,11 @@ def async_get_issue_tracker(
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
)
if not integration and not integration_domain and not module:
# If we know nothing about the entity, suggest opening an issue on HA core
# If we know nothing about the integration, suggest opening an issue on HA core
return issue_tracker

if (
not integration
and (hass and integration_domain)
and (comps_or_future := hass.data.get(DATA_CUSTOM_COMPONENTS))
and not isinstance(comps_or_future, asyncio.Future)
):
integration = comps_or_future.get(integration_domain)

if not integration and (hass and integration_domain):
with suppress(IntegrationNotLoaded):
integration = async_get_loaded_integration(hass, integration_domain)
if not integration:
integration = async_get_issue_integration(hass, integration_domain)

if integration and not integration.is_built_in:
return integration.issue_tracker
Expand Down
47 changes: 47 additions & 0 deletions tests/helpers/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from homeassistant.core import HomeAssistant
from homeassistant.helpers import frame
from homeassistant.loader import Integration

from tests.common import extract_stack_to_frame

Expand Down Expand Up @@ -423,3 +424,49 @@
assert errored == expected_error

assert caplog.text.count(what) == expected_log


@pytest.mark.parametrize(
("integration", "integration_domain", "source"),
[
pytest.param(
None,
None,
"code that",
id="core",
),
pytest.param(
None,
"sensor",
"that integration 'sensor'",
id="core integration",
),
pytest.param(
None,
"hue",
"that custom integration 'hue'",
id="custom integration",
),
],
)
async def test_report_integration_domain(
caplog: pytest.LogCaptureFixture,
integration: Integration | None,
integration_domain: str | None,
source: str,
) -> None:
"""Test report."""
what = "test_report_string"

with (
patch.object(frame, "_REPORTED_INTEGRATIONS", set()),
patch("homeassistant.loader.async_get_issue_integration", integration),
):
frame.report_usage(
what,
core_behavior=frame.ReportBehavior.LOG,
exclude_integrations={"mobile_app"},
integration_domain=integration_domain,
)

assert f"Detected {source} {what}" in caplog.text

Check failure on line 472 in tests/helpers/test_frame.py

View workflow job for this annotation

GitHub Actions / Run tests Python 3.12 (3)

test_report_integration_domain[core integration] assert "Detected that integration 'sensor' test_report_string" in '2024-11-15 17:21:11.354 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' + where '2024-11-15 17:21:11.354 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' = <_pytest.logging.LogCaptureFixture object at 0x7fb431d6bec0>.text

Check failure on line 472 in tests/helpers/test_frame.py

View workflow job for this annotation

GitHub Actions / Run tests Python 3.12 (3)

test_report_integration_domain[custom integration] assert "Detected that custom integration 'hue' test_report_string" in '2024-11-15 17:21:11.391 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' + where '2024-11-15 17:21:11.391 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' = <_pytest.logging.LogCaptureFixture object at 0x7fb431d53560>.text

Check failure on line 472 in tests/helpers/test_frame.py

View workflow job for this annotation

GitHub Actions / Run tests Python 3.13 (3)

test_report_integration_domain[core integration] assert "Detected that integration 'sensor' test_report_string" in '2024-11-15 17:21:31.728 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' + where '2024-11-15 17:21:31.728 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' = <_pytest.logging.LogCaptureFixture object at 0x7f4b9b54f150>.text

Check failure on line 472 in tests/helpers/test_frame.py

View workflow job for this annotation

GitHub Actions / Run tests Python 3.13 (3)

test_report_integration_domain[custom integration] assert "Detected that custom integration 'hue' test_report_string" in '2024-11-15 17:21:31.767 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' + where '2024-11-15 17:21:31.767 WARNING MainThread homeassistant.helpers.frame:frame.py:221 Detected code that test_report_s...work/core/core/homeassistant/helpers/frame.py", line 221, in report_usage\n _LOGGER.warning(msg, stack_info=True)\n' = <_pytest.logging.LogCaptureFixture object at 0x7f4b9ae91710>.text
Loading