mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add ability to pass integration domain to report_usage (#130705)
* Add ability to pass integration domain to report_usage * Adjust * Fix * Add tests * Update test_frame.py * Update test_frame.py * Update test_frame.py * Update test_frame.py * Update test_frame.py * Update test_frame.py * Finish tests * Docstring * Replace logger warning with report_usage * Improve * docstring * Improve tests * Adjust docstring for exclude_integrations * Fix behavior and improve tests
This commit is contained in:
parent
42dcfae6e7
commit
9add3a6c9b
@ -2887,18 +2887,12 @@ class ConfigFlow(ConfigEntryBaseFlow):
|
|||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Finish config flow and create a config entry."""
|
"""Finish config flow and create a config entry."""
|
||||||
if self.source in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
|
if self.source in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
|
||||||
report_issue = async_suggest_report_issue(
|
report_usage(
|
||||||
self.hass, integration_domain=self.handler
|
f"creates a new entry in a '{self.source}' flow, "
|
||||||
)
|
"when it is expected to update an existing entry and abort",
|
||||||
_LOGGER.warning(
|
core_behavior=ReportBehavior.LOG,
|
||||||
(
|
breaks_in_ha_version="2025.11",
|
||||||
"Detected %s config flow creating a new entry, "
|
integration_domain=self.handler,
|
||||||
"when it is expected to update an existing entry and abort. "
|
|
||||||
"This will stop working in %s, please %s"
|
|
||||||
),
|
|
||||||
self.source,
|
|
||||||
"2025.11",
|
|
||||||
report_issue,
|
|
||||||
)
|
)
|
||||||
result = super().async_create_entry(
|
result = super().async_create_entry(
|
||||||
title=title,
|
title=title,
|
||||||
|
@ -15,9 +15,13 @@ from typing import Any, cast
|
|||||||
|
|
||||||
from propcache import cached_property
|
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.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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -186,6 +190,7 @@ def report_usage(
|
|||||||
core_integration_behavior: ReportBehavior = ReportBehavior.LOG,
|
core_integration_behavior: ReportBehavior = ReportBehavior.LOG,
|
||||||
custom_integration_behavior: ReportBehavior = ReportBehavior.LOG,
|
custom_integration_behavior: ReportBehavior = ReportBehavior.LOG,
|
||||||
exclude_integrations: set[str] | None = None,
|
exclude_integrations: set[str] | None = None,
|
||||||
|
integration_domain: str | None = None,
|
||||||
level: int = logging.WARNING,
|
level: int = logging.WARNING,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Report incorrect code usage.
|
"""Report incorrect code usage.
|
||||||
@ -194,12 +199,29 @@ def report_usage(
|
|||||||
Please create a bug report at https://..."
|
Please create a bug report at https://..."
|
||||||
:param breaks_in_ha_version: if set, the report will be adjusted to specify the
|
:param breaks_in_ha_version: if set, the report will be adjusted to specify the
|
||||||
breaking version
|
breaking version
|
||||||
|
:param exclude_integrations: skip specified integration when reviewing the stack.
|
||||||
|
If no integration is found, the core behavior will be applied
|
||||||
|
:param integration_domain: fallback for identifying the integration if the
|
||||||
|
frame is not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
integration_frame = get_integration_frame(
|
integration_frame = get_integration_frame(
|
||||||
exclude_integrations=exclude_integrations
|
exclude_integrations=exclude_integrations
|
||||||
)
|
)
|
||||||
except MissingIntegrationFrame as err:
|
except MissingIntegrationFrame as err:
|
||||||
|
if integration := async_get_issue_integration(
|
||||||
|
hass := async_get_hass_or_none(), integration_domain
|
||||||
|
):
|
||||||
|
_report_integration_domain(
|
||||||
|
hass,
|
||||||
|
what,
|
||||||
|
breaks_in_ha_version,
|
||||||
|
integration,
|
||||||
|
core_integration_behavior,
|
||||||
|
custom_integration_behavior,
|
||||||
|
level,
|
||||||
|
)
|
||||||
|
return
|
||||||
msg = f"Detected code that {what}. Please report this issue"
|
msg = f"Detected code that {what}. Please report this issue"
|
||||||
if core_behavior is ReportBehavior.ERROR:
|
if core_behavior is ReportBehavior.ERROR:
|
||||||
raise RuntimeError(msg) from err
|
raise RuntimeError(msg) from err
|
||||||
@ -217,7 +239,7 @@ def report_usage(
|
|||||||
integration_behavior = custom_integration_behavior
|
integration_behavior = custom_integration_behavior
|
||||||
|
|
||||||
if integration_behavior is not ReportBehavior.IGNORE:
|
if integration_behavior is not ReportBehavior.IGNORE:
|
||||||
_report_integration(
|
_report_integration_frame(
|
||||||
what,
|
what,
|
||||||
breaks_in_ha_version,
|
breaks_in_ha_version,
|
||||||
integration_frame,
|
integration_frame,
|
||||||
@ -226,14 +248,64 @@ def report_usage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _report_integration(
|
def _report_integration_domain(
|
||||||
|
hass: HomeAssistant | None,
|
||||||
|
what: str,
|
||||||
|
breaks_in_ha_version: str | None,
|
||||||
|
integration: Integration,
|
||||||
|
core_integration_behavior: ReportBehavior,
|
||||||
|
custom_integration_behavior: ReportBehavior,
|
||||||
|
level: int,
|
||||||
|
) -> None:
|
||||||
|
"""Report incorrect usage in an integration (identified via domain).
|
||||||
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
integration_behavior = core_integration_behavior
|
||||||
|
if not integration.is_built_in:
|
||||||
|
integration_behavior = custom_integration_behavior
|
||||||
|
|
||||||
|
if integration_behavior is ReportBehavior.IGNORE:
|
||||||
|
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. %s %s",
|
||||||
|
integration_type,
|
||||||
|
integration.domain,
|
||||||
|
what,
|
||||||
|
f"This will stop working in Home Assistant {breaks_in_ha_version}, please"
|
||||||
|
if breaks_in_ha_version
|
||||||
|
else "Please",
|
||||||
|
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,
|
what: str,
|
||||||
breaks_in_ha_version: str | None,
|
breaks_in_ha_version: str | None,
|
||||||
integration_frame: IntegrationFrame,
|
integration_frame: IntegrationFrame,
|
||||||
level: int = logging.WARNING,
|
level: int = logging.WARNING,
|
||||||
error: bool = False,
|
error: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Report incorrect usage in an integration.
|
"""Report incorrect usage in an integration (identified via frame).
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
|
@ -7,6 +7,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import frame
|
from homeassistant.helpers import frame
|
||||||
|
from homeassistant.loader import async_get_integration
|
||||||
|
|
||||||
from tests.common import extract_stack_to_frame
|
from tests.common import extract_stack_to_frame
|
||||||
|
|
||||||
@ -445,3 +446,89 @@ async def test_report(
|
|||||||
assert errored == expected_error
|
assert errored == expected_error
|
||||||
|
|
||||||
assert caplog.text.count(what) == expected_log
|
assert caplog.text.count(what) == expected_log
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("behavior", "integration_domain", "source", "logs_again"),
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
"core_behavior",
|
||||||
|
None,
|
||||||
|
"code that",
|
||||||
|
True,
|
||||||
|
id="core",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"core_behavior",
|
||||||
|
"unknown_integration",
|
||||||
|
"code that",
|
||||||
|
True,
|
||||||
|
id="unknown integration",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"core_integration_behavior",
|
||||||
|
"sensor",
|
||||||
|
"that integration 'sensor'",
|
||||||
|
False,
|
||||||
|
id="core integration",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"custom_integration_behavior",
|
||||||
|
"test_package",
|
||||||
|
"that custom integration 'test_package'",
|
||||||
|
False,
|
||||||
|
id="custom integration",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("enable_custom_integrations")
|
||||||
|
async def test_report_integration_domain(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
behavior: str,
|
||||||
|
integration_domain: str | None,
|
||||||
|
source: str,
|
||||||
|
logs_again: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Test report."""
|
||||||
|
await async_get_integration(hass, "sensor")
|
||||||
|
await async_get_integration(hass, "test_package")
|
||||||
|
|
||||||
|
what = "test_report_string"
|
||||||
|
lookup_text = f"Detected {source} {what}"
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
frame.report_usage(
|
||||||
|
what,
|
||||||
|
**{behavior: frame.ReportBehavior.IGNORE},
|
||||||
|
integration_domain=integration_domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert lookup_text not in caplog.text
|
||||||
|
|
||||||
|
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()):
|
||||||
|
frame.report_usage(
|
||||||
|
what,
|
||||||
|
**{behavior: frame.ReportBehavior.LOG},
|
||||||
|
integration_domain=integration_domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert lookup_text in caplog.text
|
||||||
|
|
||||||
|
# Check that it does not log again
|
||||||
|
caplog.clear()
|
||||||
|
frame.report_usage(
|
||||||
|
what,
|
||||||
|
**{behavior: frame.ReportBehavior.LOG},
|
||||||
|
integration_domain=integration_domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (lookup_text in caplog.text) == logs_again
|
||||||
|
|
||||||
|
# Check that it raises
|
||||||
|
with pytest.raises(RuntimeError, match=lookup_text):
|
||||||
|
frame.report_usage(
|
||||||
|
what,
|
||||||
|
**{behavior: frame.ReportBehavior.ERROR},
|
||||||
|
integration_domain=integration_domain,
|
||||||
|
)
|
||||||
|
@ -7157,7 +7157,10 @@ async def test_create_entry_reauth_reconfigure(
|
|||||||
|
|
||||||
assert len(hass.config_entries.async_entries("test")) == 1
|
assert len(hass.config_entries.async_entries("test")) == 1
|
||||||
|
|
||||||
with mock_config_flow("test", TestFlow):
|
with (
|
||||||
|
mock_config_flow("test", TestFlow),
|
||||||
|
patch.object(frame, "_REPORTED_INTEGRATIONS", set()),
|
||||||
|
):
|
||||||
result = await getattr(entry, f"start_{source}_flow")(hass)
|
result = await getattr(entry, f"start_{source}_flow")(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
@ -7169,10 +7172,10 @@ async def test_create_entry_reauth_reconfigure(
|
|||||||
assert entries[0].entry_id != entry.entry_id
|
assert entries[0].entry_id != entry.entry_id
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
f"Detected {source} config flow creating a new entry, when it is expected "
|
f"Detected that integration 'test' creates a new entry in a '{source}' flow, "
|
||||||
"to update an existing entry and abort. This will stop working in "
|
"when it is expected to update an existing entry and abort. This will stop "
|
||||||
"2025.11, please create a bug report at https://github.com/home"
|
"working in Home Assistant 2025.11, please create a bug report at "
|
||||||
"-assistant/core/issues?q=is%3Aopen+is%3Aissue+"
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+"
|
||||||
"label%3A%22integration%3A+test%22"
|
"label%3A%22integration%3A+test%22"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user