diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 2be0bd9a04b..5b98d372220 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -37,6 +37,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.event import async_track_point_in_time +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util @@ -261,8 +262,10 @@ CALENDAR_EVENT_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -SERVICE_LIST_EVENTS: Final = "list_events" -SERVICE_LIST_EVENTS_SCHEMA: Final = vol.All( +LEGACY_SERVICE_LIST_EVENTS: Final = "list_events" +"""Deprecated: please use SERVICE_LIST_EVENTS.""" +SERVICE_GET_EVENTS: Final = "get_events" +SERVICE_GET_EVENTS_SCHEMA: Final = vol.All( cv.has_at_least_one_key(EVENT_END_DATETIME, EVENT_DURATION), cv.has_at_most_one_key(EVENT_END_DATETIME, EVENT_DURATION), cv.make_entity_service_schema( @@ -301,11 +304,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: required_features=[CalendarEntityFeature.CREATE_EVENT], ) component.async_register_legacy_entity_service( - SERVICE_LIST_EVENTS, - SERVICE_LIST_EVENTS_SCHEMA, + LEGACY_SERVICE_LIST_EVENTS, + SERVICE_GET_EVENTS_SCHEMA, async_list_events_service, supports_response=SupportsResponse.ONLY, ) + component.async_register_entity_service( + SERVICE_GET_EVENTS, + SERVICE_GET_EVENTS_SCHEMA, + async_get_events_service, + supports_response=SupportsResponse.ONLY, + ) await component.async_setup(config) return True @@ -850,6 +859,32 @@ async def async_create_event(entity: CalendarEntity, call: ServiceCall) -> None: async def async_list_events_service( calendar: CalendarEntity, service_call: ServiceCall +) -> ServiceResponse: + """List events on a calendar during a time range. + + Deprecated: please use async_get_events_service. + """ + _LOGGER.warning( + "Detected use of service 'calendar.list_events'. " + "This is deprecated and will stop working in Home Assistant 2024.6. " + "Use 'calendar.get_events' instead which supports multiple entities", + ) + async_create_issue( + calendar.hass, + DOMAIN, + "deprecated_service_calendar_list_events", + breaks_in_ha_version="2024.6.0", + is_fixable=True, + is_persistent=False, + issue_domain=calendar.platform.platform_name, + severity=IssueSeverity.WARNING, + translation_key="deprecated_service_calendar_list_events", + ) + return await async_get_events_service(calendar, service_call) + + +async def async_get_events_service( + calendar: CalendarEntity, service_call: ServiceCall ) -> ServiceResponse: """List events on a calendar during a time range.""" start = service_call.data.get(EVENT_START_DATETIME, dt_util.now()) diff --git a/homeassistant/components/calendar/services.yaml b/homeassistant/components/calendar/services.yaml index 712d6ad8823..2e926fbdeed 100644 --- a/homeassistant/components/calendar/services.yaml +++ b/homeassistant/components/calendar/services.yaml @@ -52,3 +52,19 @@ list_events: duration: selector: duration: +get_events: + target: + entity: + domain: calendar + fields: + start_date_time: + example: "2022-03-22 20:00:00" + selector: + datetime: + end_date_time: + example: "2022-03-22 22:00:00" + selector: + datetime: + duration: + selector: + duration: diff --git a/homeassistant/components/calendar/strings.json b/homeassistant/components/calendar/strings.json index 20679ed09b2..57450000199 100644 --- a/homeassistant/components/calendar/strings.json +++ b/homeassistant/components/calendar/strings.json @@ -72,9 +72,9 @@ } } }, - "list_events": { - "name": "List event", - "description": "Lists events on a calendar within a time range.", + "get_events": { + "name": "Get event", + "description": "Get events on a calendar within a time range.", "fields": { "start_date_time": { "name": "Start time", @@ -89,6 +89,37 @@ "description": "Returns active events from start_date_time until the specified duration." } } + }, + "list_events": { + "name": "List event", + "description": "Lists events on a calendar within a time range.", + "fields": { + "start_date_time": { + "name": "[%key:component::calendar::services::get_events::fields::start_date_time::name%]", + "description": "[%key:component::calendar::services::get_events::fields::start_date_time::description%]" + }, + "end_date_time": { + "name": "[%key:component::calendar::services::get_events::fields::end_date_time::name%]", + "description": "[%key:component::calendar::services::get_events::fields::end_date_time::description%]" + }, + "duration": { + "name": "[%key:component::calendar::services::get_events::fields::duration::name%]", + "description": "[%key:component::calendar::services::get_events::fields::duration::description%]" + } + } + } + }, + "issues": { + "deprecated_service_calendar_list_events": { + "title": "Detected use of deprecated service `calendar.list_events`", + "fix_flow": { + "step": { + "confirm": { + "title": "[%key:component::calendar::issues::deprecated_service_calendar_list_events::title%]", + "description": "Use `calendar.get_events` instead which supports multiple entities.\n\nPlease replace this service and adjust your automations and scripts and select **submit** to close this issue." + } + } + } } } } diff --git a/tests/components/calendar/snapshots/test_init.ambr b/tests/components/calendar/snapshots/test_init.ambr index 7d48228193a..67e8839f7a5 100644 --- a/tests/components/calendar/snapshots/test_init.ambr +++ b/tests/components/calendar/snapshots/test_init.ambr @@ -1,11 +1,34 @@ # serializer version: 1 -# name: test_list_events_service_duration[calendar.calendar_1-00:15:00] +# name: test_list_events_service_duration[calendar.calendar_1-00:15:00-get_events] + dict({ + 'calendar.calendar_1': dict({ + 'events': list([ + ]), + }), + }) +# --- +# name: test_list_events_service_duration[calendar.calendar_1-00:15:00-list_events] dict({ 'events': list([ ]), }) # --- -# name: test_list_events_service_duration[calendar.calendar_1-01:00:00] +# name: test_list_events_service_duration[calendar.calendar_1-01:00:00-get_events] + dict({ + 'calendar.calendar_1': dict({ + 'events': list([ + dict({ + 'description': 'Future Description', + 'end': '2023-10-19T08:20:05-07:00', + 'location': 'Future Location', + 'start': '2023-10-19T07:20:05-07:00', + 'summary': 'Future Event', + }), + ]), + }), + }) +# --- +# name: test_list_events_service_duration[calendar.calendar_1-01:00:00-list_events] dict({ 'events': list([ dict({ @@ -18,7 +41,20 @@ ]), }) # --- -# name: test_list_events_service_duration[calendar.calendar_2-00:15:00] +# name: test_list_events_service_duration[calendar.calendar_2-00:15:00-get_events] + dict({ + 'calendar.calendar_2': dict({ + 'events': list([ + dict({ + 'end': '2023-10-19T07:20:05-07:00', + 'start': '2023-10-19T06:20:05-07:00', + 'summary': 'Current Event', + }), + ]), + }), + }) +# --- +# name: test_list_events_service_duration[calendar.calendar_2-00:15:00-list_events] dict({ 'events': list([ dict({ diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index ad83d039d73..25804287172 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -12,9 +12,14 @@ from syrupy.assertion import SnapshotAssertion import voluptuous as vol from homeassistant.bootstrap import async_setup_component -from homeassistant.components.calendar import DOMAIN, SERVICE_LIST_EVENTS +from homeassistant.components.calendar import ( + DOMAIN, + LEGACY_SERVICE_LIST_EVENTS, + SERVICE_GET_EVENTS, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.issue_registry import IssueRegistry import homeassistant.util.dt as dt_util from tests.typing import ClientSessionGenerator, WebSocketGenerator @@ -389,6 +394,41 @@ async def test_create_event_service_invalid_params( @freeze_time("2023-06-22 10:30:00+00:00") +@pytest.mark.parametrize( + ("service", "expected"), + [ + ( + LEGACY_SERVICE_LIST_EVENTS, + { + "events": [ + { + "start": "2023-06-22T05:00:00-06:00", + "end": "2023-06-22T06:00:00-06:00", + "summary": "Future Event", + "description": "Future Description", + "location": "Future Location", + } + ] + }, + ), + ( + SERVICE_GET_EVENTS, + { + "calendar.calendar_1": { + "events": [ + { + "start": "2023-06-22T05:00:00-06:00", + "end": "2023-06-22T06:00:00-06:00", + "summary": "Future Event", + "description": "Future Description", + "location": "Future Location", + } + ] + } + }, + ), + ], +) @pytest.mark.parametrize( ("start_time", "end_time"), [ @@ -402,6 +442,8 @@ async def test_list_events_service( set_time_zone: None, start_time: str, end_time: str, + service: str, + expected: dict[str, Any], ) -> None: """Test listing events from the service call using exlplicit start and end time. @@ -414,8 +456,9 @@ async def test_list_events_service( response = await hass.services.async_call( DOMAIN, - SERVICE_LIST_EVENTS, - { + service, + target={"entity_id": ["calendar.calendar_1"]}, + service_data={ "entity_id": "calendar.calendar_1", "start_date_time": start_time, "end_date_time": end_time, @@ -423,19 +466,16 @@ async def test_list_events_service( blocking=True, return_response=True, ) - assert response == { - "events": [ - { - "start": "2023-06-22T05:00:00-06:00", - "end": "2023-06-22T06:00:00-06:00", - "summary": "Future Event", - "description": "Future Description", - "location": "Future Location", - } - ] - } + assert response == expected +@pytest.mark.parametrize( + ("service"), + [ + (LEGACY_SERVICE_LIST_EVENTS), + SERVICE_GET_EVENTS, + ], +) @pytest.mark.parametrize( ("entity", "duration"), [ @@ -452,6 +492,7 @@ async def test_list_events_service_duration( hass: HomeAssistant, entity: str, duration: str, + service: str, snapshot: SnapshotAssertion, ) -> None: """Test listing events using a time duration.""" @@ -460,7 +501,7 @@ async def test_list_events_service_duration( response = await hass.services.async_call( DOMAIN, - SERVICE_LIST_EVENTS, + service, { "entity_id": entity, "duration": duration, @@ -479,7 +520,7 @@ async def test_list_events_positive_duration(hass: HomeAssistant) -> None: with pytest.raises(vol.Invalid, match="should be positive"): await hass.services.async_call( DOMAIN, - SERVICE_LIST_EVENTS, + SERVICE_GET_EVENTS, { "entity_id": "calendar.calendar_1", "duration": "-01:00:00", @@ -499,7 +540,7 @@ async def test_list_events_exclusive_fields(hass: HomeAssistant) -> None: with pytest.raises(vol.Invalid, match="at most one of"): await hass.services.async_call( DOMAIN, - SERVICE_LIST_EVENTS, + SERVICE_GET_EVENTS, { "entity_id": "calendar.calendar_1", "end_date_time": end, @@ -518,10 +559,47 @@ async def test_list_events_missing_fields(hass: HomeAssistant) -> None: with pytest.raises(vol.Invalid, match="at least one of"): await hass.services.async_call( DOMAIN, - SERVICE_LIST_EVENTS, + SERVICE_GET_EVENTS, { "entity_id": "calendar.calendar_1", }, blocking=True, return_response=True, ) + + +async def test_issue_deprecated_service_calendar_list_events( + hass: HomeAssistant, + issue_registry: IssueRegistry, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test the issue is raised on deprecated service weather.get_forecast.""" + + await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}}) + await hass.async_block_till_done() + + _ = await hass.services.async_call( + DOMAIN, + LEGACY_SERVICE_LIST_EVENTS, + target={"entity_id": ["calendar.calendar_1"]}, + service_data={ + "entity_id": "calendar.calendar_1", + "duration": "01:00:00", + }, + blocking=True, + return_response=True, + ) + + issue = issue_registry.async_get_issue( + "calendar", "deprecated_service_calendar_list_events" + ) + assert issue + assert issue.issue_domain == "demo" + assert issue.issue_id == "deprecated_service_calendar_list_events" + assert issue.translation_key == "deprecated_service_calendar_list_events" + + assert ( + "Detected use of service 'calendar.list_events'. " + "This is deprecated and will stop working in Home Assistant 2024.6. " + "Use 'calendar.get_events' instead which supports multiple entities" + ) in caplog.text