From fa95a56e763876a3848f31cbfa707b98a031266b Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 29 Jun 2023 05:45:08 +0000 Subject: [PATCH 1/3] Limit fields returned for the list events service --- homeassistant/components/calendar/__init__.py | 14 ++++++++++---- homeassistant/components/calendar/const.py | 9 +++++++++ tests/components/calendar/test_init.py | 4 ++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 5d0d2526bf2d76..d216dff112ff6f 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -60,6 +60,7 @@ EVENT_TIME_FIELDS, EVENT_TYPES, EVENT_UID, + LIST_EVENT_FIELDS, CalendarEntityFeature, ) @@ -415,6 +416,13 @@ def _api_event_dict_factory(obj: Iterable[tuple[str, Any]]) -> dict[str, Any]: return result +def _list_events_dict_factory( + obj: Iterable[tuple[str, Any]] +) -> dict[str, JsonValueType]: + """Convert CalendarEvent dataclass items to dictionary of attributes.""" + return {name: value for name, value in obj if name in LIST_EVENT_FIELDS} + + def _get_datetime_local( dt_or_d: datetime.datetime | datetime.date, ) -> datetime.datetime: @@ -782,9 +790,7 @@ async def async_list_events_service( else: end = service_call.data[EVENT_END_DATETIME] calendar_event_list = await calendar.async_get_events(calendar.hass, start, end) - events: list[JsonValueType] = [ - dataclasses.asdict(event) for event in calendar_event_list - ] return { - "events": events, + "events": dataclasses.asdict(event, dict_factory=_list_events_dict_factory) + for event in calendar_event_list } diff --git a/homeassistant/components/calendar/const.py b/homeassistant/components/calendar/const.py index 2d4f0dfe0ba3ca..db8f3891391d9c 100644 --- a/homeassistant/components/calendar/const.py +++ b/homeassistant/components/calendar/const.py @@ -41,3 +41,12 @@ class CalendarEntityFeature(IntFlag): } EVENT_TYPES = "event_types" EVENT_DURATION = "duration" + +# Fields for the list events service +LIST_EVENT_FIELDS = { + EVENT_START, + EVENT_END, + EVENT_SUMMARY, + EVENT_DESCRIPTION, + EVENT_LOCATION, +} diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index 972922218195df..5b4bfca6401d39 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -11,6 +11,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components.calendar import DOMAIN, SERVICE_LIST_EVENTS +from homeassistant.components.calendar.const import LIST_EVENT_FIELDS from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util @@ -447,6 +448,9 @@ async def test_list_events_service_duration( assert "events" in response events = response["events"] assert [event["summary"] for event in events] == expected_events + for event in events: + for key in event: + assert key in LIST_EVENT_FIELDS async def test_list_events_positive_duration(hass: HomeAssistant) -> None: From 5aefcf07e0aa22180fdce66d9bf9ea8f69e5052e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 29 Jun 2023 14:33:08 +0000 Subject: [PATCH 2/3] Update websocket tests and fix bugs in response fields --- homeassistant/components/calendar/__init__.py | 6 +++-- homeassistant/components/calendar/const.py | 4 ++-- tests/components/calendar/test_init.py | 22 ++++++++++--------- .../components/websocket_api/test_commands.py | 3 --- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index d216dff112ff6f..bbc883eb4cbc89 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -791,6 +791,8 @@ async def async_list_events_service( end = service_call.data[EVENT_END_DATETIME] calendar_event_list = await calendar.async_get_events(calendar.hass, start, end) return { - "events": dataclasses.asdict(event, dict_factory=_list_events_dict_factory) - for event in calendar_event_list + "events": [ + dataclasses.asdict(event, dict_factory=_list_events_dict_factory) + for event in calendar_event_list + ] } diff --git a/homeassistant/components/calendar/const.py b/homeassistant/components/calendar/const.py index db8f3891391d9c..e667510325bcd5 100644 --- a/homeassistant/components/calendar/const.py +++ b/homeassistant/components/calendar/const.py @@ -44,8 +44,8 @@ class CalendarEntityFeature(IntFlag): # Fields for the list events service LIST_EVENT_FIELDS = { - EVENT_START, - EVENT_END, + "start", + "end", EVENT_SUMMARY, EVENT_DESCRIPTION, EVENT_LOCATION, diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index 5b4bfca6401d39..9fdc76abe03f49 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -4,14 +4,13 @@ from datetime import timedelta from http import HTTPStatus from typing import Any -from unittest.mock import patch +from unittest.mock import ANY, patch import pytest import voluptuous as vol from homeassistant.bootstrap import async_setup_component from homeassistant.components.calendar import DOMAIN, SERVICE_LIST_EVENTS -from homeassistant.components.calendar.const import LIST_EVENT_FIELDS from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util @@ -406,11 +405,17 @@ async def test_list_events_service(hass: HomeAssistant) -> None: blocking=True, return_response=True, ) - assert response - assert "events" in response - events = response["events"] - assert len(events) == 1 - assert events[0]["summary"] == "Future Event" + assert response == { + "events": [ + { + "start": ANY, + "end": ANY, + "summary": "Future Event", + "description": "Future Description", + "location": "Future Location", + } + ] + } @pytest.mark.parametrize( @@ -448,9 +453,6 @@ async def test_list_events_service_duration( assert "events" in response events = response["events"] assert [event["summary"] for event in events] == expected_events - for event in events: - for key in event: - assert key in LIST_EVENT_FIELDS async def test_list_events_positive_duration(hass: HomeAssistant) -> None: diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index c8f494a0071be6..7e46dc0d0bdc45 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1769,9 +1769,6 @@ async def test_execute_script_complex_response( "summary": "Future Event", "description": "Future Description", "location": "Future Location", - "uid": None, - "recurrence_id": None, - "rrule": None, } ] } From c87553469e8ea79f2fb2a87245120ad4bf199f65 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 29 Jun 2023 15:06:15 +0000 Subject: [PATCH 3/3] Omit 'None' fields in the list events response --- homeassistant/components/calendar/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index bbc883eb4cbc89..86f61f0ed872e5 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -420,7 +420,11 @@ def _list_events_dict_factory( obj: Iterable[tuple[str, Any]] ) -> dict[str, JsonValueType]: """Convert CalendarEvent dataclass items to dictionary of attributes.""" - return {name: value for name, value in obj if name in LIST_EVENT_FIELDS} + return { + name: value + for name, value in obj + if name in LIST_EVENT_FIELDS and value is not None + } def _get_datetime_local(