diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index dd298ae3786..56931fe289d 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1195,9 +1195,9 @@ def _report_non_awaited_platform_forwards(entry: ConfigEntry, what: str) -> None f"calls {what} for integration {entry.domain} with " f"title: {entry.title} and entry_id: {entry.entry_id}, " f"during setup without awaiting {what}, which can cause " - "the setup lock to be released before the setup is done. " - "This will stop working in Home Assistant 2025.1", + "the setup lock to be released before the setup is done", core_behavior=ReportBehavior.LOG, + breaks_in_ha_version="2025.1", ) @@ -1267,6 +1267,7 @@ class ConfigEntriesFlowManager( # Deprecated in 2024.12, should fail in 2025.12 report_usage( f"initialises a {source} flow without a link to the config entry", + breaks_in_ha_version="2025.12", ) flow_id = ulid_util.ulid_now() @@ -2321,10 +2322,10 @@ class ConfigEntries: report_usage( "calls async_forward_entry_setup for " f"integration, {entry.domain} with title: {entry.title} " - f"and entry_id: {entry.entry_id}, which is deprecated and " - "will stop working in Home Assistant 2025.6, " + f"and entry_id: {entry.entry_id}, which is deprecated, " "await async_forward_entry_setups instead", core_behavior=ReportBehavior.LOG, + breaks_in_ha_version="2025.6", ) if not entry.setup_lock.locked(): async with entry.setup_lock: @@ -3155,11 +3156,11 @@ class OptionsFlow(ConfigEntryBaseFlow): def config_entry(self, value: ConfigEntry) -> None: """Set the config entry value.""" report_usage( - "sets option flow config_entry explicitly, which is deprecated " - "and will stop working in 2025.12", + "sets option flow config_entry explicitly, which is deprecated", core_behavior=ReportBehavior.ERROR, core_integration_behavior=ReportBehavior.ERROR, custom_integration_behavior=ReportBehavior.LOG, + breaks_in_ha_version="2025.12", ) self._config_entry = value diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index eda98099713..3ebe6fdba29 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -181,6 +181,7 @@ class ReportBehavior(enum.Enum): def report_usage( what: str, *, + breaks_in_ha_version: str | None = None, core_behavior: ReportBehavior = ReportBehavior.ERROR, core_integration_behavior: ReportBehavior = ReportBehavior.LOG, custom_integration_behavior: ReportBehavior = ReportBehavior.LOG, @@ -189,17 +190,25 @@ def report_usage( ) -> None: """Report incorrect code usage. - Similar to `report` but allows more fine-grained reporting. + :param what: will be wrapped with "Detected that integration 'integration' {what}. + Please create a bug report at https://..." + :param breaks_in_ha_version: if set, the report will be adjusted to specify the + breaking version """ try: integration_frame = get_integration_frame( exclude_integrations=exclude_integrations ) except MissingIntegrationFrame as err: - 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: raise RuntimeError(msg) from err if core_behavior is ReportBehavior.LOG: + if breaks_in_ha_version: + msg = ( + f"Detected code that {what}. This will stop working in Home " + f"Assistant {breaks_in_ha_version}, please report this issue" + ) _LOGGER.warning(msg, stack_info=True) return @@ -209,12 +218,17 @@ def report_usage( if integration_behavior is not ReportBehavior.IGNORE: _report_integration( - what, integration_frame, level, integration_behavior is ReportBehavior.ERROR + what, + breaks_in_ha_version, + integration_frame, + level, + integration_behavior is ReportBehavior.ERROR, ) def _report_integration( what: str, + breaks_in_ha_version: str | None, integration_frame: IntegrationFrame, level: int = logging.WARNING, error: bool = False, @@ -237,13 +251,16 @@ def _report_integration( integration_type = "custom " if integration_frame.custom_integration else "" _LOGGER.log( level, - "Detected that %sintegration '%s' %s at %s, line %s: %s, please %s", + "Detected that %sintegration '%s' %s at %s, line %s: %s. %s %s", integration_type, integration_frame.integration, what, integration_frame.relative_filename, integration_frame.line_number, integration_frame.line, + f"This will stop working in Home Assistant {breaks_in_ha_version}, please" + if breaks_in_ha_version + else "Please", report_issue, ) if not error: @@ -253,7 +270,7 @@ def _report_integration( f"'{integration_frame.integration}' {what} at " f"{integration_frame.relative_filename}, line " f"{integration_frame.line_number}: {integration_frame.line}. " - f"Please {report_issue}." + f"Please {report_issue}" ) diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 126ed3f9287..1788da74c3b 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -286,8 +286,8 @@ async def test_warning_close_session_integration( await session.close() assert ( "Detected that integration 'hue' closes the Home Assistant aiohttp session at " - "homeassistant/components/hue/light.py, line 23: await session.close(), " - "please create a bug report at https://github.com/home-assistant/core/issues?" + "homeassistant/components/hue/light.py, line 23: await session.close(). " + "Please create a bug report at https://github.com/home-assistant/core/issues?" "q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" ) in caplog.text @@ -330,8 +330,8 @@ async def test_warning_close_session_custom( await session.close() assert ( "Detected that custom integration 'hue' closes the Home Assistant aiohttp " - "session at custom_components/hue/light.py, line 23: await session.close(), " - "please report it to the author of the 'hue' custom integration" + "session at custom_components/hue/light.py, line 23: await session.close(). " + "Please report it to the author of the 'hue' custom integration" ) in caplog.text diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index a45b418c526..69e3a10d4d4 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -4895,7 +4895,7 @@ async def test_track_state_change_deprecated( assert ( "Detected code that calls `async_track_state_change` instead " "of `async_track_state_change_event` which is deprecated and " - "will be removed in Home Assistant 2025.5. Please report this issue." + "will be removed in Home Assistant 2025.5. Please report this issue" ) in caplog.text @@ -4946,7 +4946,7 @@ async def test_async_track_template_no_hass_deprecated( """Test async_track_template with a template without hass is deprecated.""" message = ( "Detected code that calls async_track_template_result with template without " - "hass, which will stop working in HA Core 2025.10. Please report this issue." + "hass, which will stop working in HA Core 2025.10. Please report this issue" ) async_track_template(hass, Template("blah"), lambda x, y, z: None) @@ -4964,7 +4964,7 @@ async def test_async_track_template_result_no_hass_deprecated( """Test async_track_template_result with a template without hass is deprecated.""" message = ( "Detected code that calls async_track_template_result with template without " - "hass, which will stop working in HA Core 2025.10. Please report this issue." + "hass, which will stop working in HA Core 2025.10. Please report this issue" ) async_track_template_result( diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index a2a4890810b..1c1dc8eb71e 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -261,8 +261,8 @@ async def test_prevent_flooding( expected_message = ( f"Detected that integration '{integration}' {what} at {filename}, line " - f"{mock_integration_frame.lineno}: {mock_integration_frame.line}, " - f"please create a bug report at https://github.com/home-assistant/core/issues?" + f"{mock_integration_frame.lineno}: {mock_integration_frame.line}. " + f"Please create a bug report at https://github.com/home-assistant/core/issues?" f"q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+{integration}%22" ) @@ -279,6 +279,28 @@ async def test_prevent_flooding( assert len(frame._REPORTED_INTEGRATIONS) == 1 +@patch.object(frame, "_REPORTED_INTEGRATIONS", set()) +async def test_breaks_in_ha_version( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock +) -> None: + """Test to ensure a report is only written once to the log.""" + + what = "accessed hi instead of hello" + integration = "hue" + filename = "homeassistant/components/hue/light.py" + + expected_message = ( + f"Detected that integration '{integration}' {what} at {filename}, line " + f"{mock_integration_frame.lineno}: {mock_integration_frame.line}. " + f"This will stop working in Home Assistant 2024.11, please create a bug " + "report at https://github.com/home-assistant/core/issues?" + f"q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+{integration}%22" + ) + + frame.report_usage(what, breaks_in_ha_version="2024.11") + assert expected_message in caplog.text + + async def test_report_missing_integration_frame( caplog: pytest.LogCaptureFixture, ) -> None: diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index ccfccb3d698..684778fe1b1 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -138,8 +138,8 @@ async def test_warning_close_session_integration( assert ( "Detected that integration 'hue' closes the Home Assistant httpx client at " - "homeassistant/components/hue/light.py, line 23: await session.aclose(), " - "please create a bug report at https://github.com/home-assistant/core/issues?" + "homeassistant/components/hue/light.py, line 23: await session.aclose(). " + "Please create a bug report at https://github.com/home-assistant/core/issues?" "q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" ) in caplog.text @@ -182,6 +182,6 @@ async def test_warning_close_session_custom( await httpx_session.aclose() assert ( "Detected that custom integration 'hue' closes the Home Assistant httpx client " - "at custom_components/hue/light.py, line 23: await session.aclose(), " - "please report it to the author of the 'hue' custom integration" + "at custom_components/hue/light.py, line 23: await session.aclose(). " + "Please report it to the author of the 'hue' custom integration" ) in caplog.text diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 50da0ab6332..70a95c6bec8 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -629,7 +629,7 @@ async def test_async_config_entry_first_refresh_invalid_state( match="Detected code that uses `async_config_entry_first_refresh`, which " "is only supported when entry state is ConfigEntryState.SETUP_IN_PROGRESS, " "but it is in state ConfigEntryState.NOT_LOADED. This will stop working " - "in Home Assistant 2025.11. Please report this issue.", + "in Home Assistant 2025.11. Please report this issue", ): await crd.async_config_entry_first_refresh() @@ -666,7 +666,7 @@ async def test_async_config_entry_first_refresh_no_entry(hass: HomeAssistant) -> RuntimeError, match="Detected code that uses `async_config_entry_first_refresh`, " "which is only supported for coordinators with a config entry and will " - "stop working in Home Assistant 2025.11. Please report this issue.", + "stop working in Home Assistant 2025.11. Please report this issue", ): await crd.async_config_entry_first_refresh() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 41af8af3f21..44f741242aa 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1115,8 +1115,8 @@ async def test_async_forward_entry_setup_deprecated( assert ( "Detected code that calls async_forward_entry_setup for integration, " f"original with title: Mock Title and entry_id: {entry_id}, " - "which is deprecated and will stop working in Home Assistant 2025.6, " - "await async_forward_entry_setups instead. Please report this issue." + "which is deprecated, await async_forward_entry_setups instead. " + "This will stop working in Home Assistant 2025.6, please report this issue" ) in caplog.text @@ -4802,7 +4802,7 @@ async def test_reauth_reconfigure_missing_entry( with pytest.raises( RuntimeError, match=f"Detected code that initialises a {source} flow without a link " - "to the config entry. Please report this issue.", + "to the config entry. Please report this issue", ): await manager.flow.async_init("test", context={"source": source}) await hass.async_block_till_done() @@ -6244,7 +6244,7 @@ async def test_non_awaited_async_forward_entry_setups( "test with title: Mock Title and entry_id: test2, during setup without " "awaiting async_forward_entry_setups, which can cause the setup lock " "to be released before the setup is done. This will stop working in " - "Home Assistant 2025.1. Please report this issue." + "Home Assistant 2025.1, please report this issue" ) in caplog.text @@ -6316,7 +6316,7 @@ async def test_non_awaited_async_forward_entry_setup( "test with title: Mock Title and entry_id: test2, during setup without " "awaiting async_forward_entry_setup, which can cause the setup lock " "to be released before the setup is done. This will stop working in " - "Home Assistant 2025.1. Please report this issue." + "Home Assistant 2025.1, please report this issue" ) in caplog.text @@ -7560,8 +7560,10 @@ async def test_options_flow_deprecated_config_entry_setter( assert ( "Detected that custom integration 'my_integration' sets option flow " - "config_entry explicitly, which is deprecated and will stop working " - "in 2025.12" in caplog.text + "config_entry explicitly, which is deprecated at " + "custom_components/my_integration/light.py, line 23: " + "self.light.is_on. This will stop working in Home Assistant 2025.12, please " + "create a bug report at " in caplog.text ) diff --git a/tests/test_core.py b/tests/test_core.py index 67ed99daa09..ed1aad15a2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3310,7 +3310,7 @@ async def test_thread_safety_message(hass: HomeAssistant) -> None: "which may cause Home Assistant to crash or data to corrupt. For more " "information, see " "https://developers.home-assistant.io/docs/asyncio_thread_safety/#test" - ". Please report this issue.", + ". Please report this issue", ), ): await hass.async_add_executor_job(hass.verify_event_loop_thread, "test") diff --git a/tests/test_core_config.py b/tests/test_core_config.py index 3e0c0999ad3..4de7ab1e078 100644 --- a/tests/test_core_config.py +++ b/tests/test_core_config.py @@ -1077,7 +1077,7 @@ async def test_set_time_zone_deprecated(hass: HomeAssistant) -> None: match=re.escape( "Detected code that set the time zone using set_time_zone instead of " "async_set_time_zone which will stop working in Home Assistant 2025.6. " - "Please report this issue.", + "Please report this issue", ), ): await hass.config.set_time_zone("America/New_York") diff --git a/tests/util/test_async.py b/tests/util/test_async.py index cda10b69c3f..cfa78228f0c 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -140,7 +140,7 @@ async def test_create_eager_task_from_thread(hass: HomeAssistant) -> None: with pytest.raises( RuntimeError, match=( - "Detected code that attempted to create an asyncio task from a thread. Please report this issue." + "Detected code that attempted to create an asyncio task from a thread. Please report this issue" ), ): await hass.async_add_executor_job(create_task)