Skip to content

Commit

Permalink
Fix CalDAV supported components check when configured from the UI (#1…
Browse files Browse the repository at this point in the history
…03411)

* Fix CalDAV supported components check when configured from the UI

* Move async_get_calendars to a seprate file

* Simplify return value for async_get_calendars
  • Loading branch information
allenporter committed Nov 5, 2023
1 parent d3bbcbe commit 936956a
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 31 deletions.
25 changes: 25 additions & 0 deletions homeassistant/components/caldav/api.py
@@ -0,0 +1,25 @@
"""Library for working with CalDAV api."""

import asyncio

import caldav

from homeassistant.core import HomeAssistant


async def async_get_calendars(
hass: HomeAssistant, client: caldav.DAVClient, component: str
) -> list[caldav.Calendar]:
"""Get all calendars that support the specified component."""
calendars = await hass.async_add_executor_job(client.principal().calendars)
components_results = await asyncio.gather(
*[
hass.async_add_executor_job(calendar.get_supported_components)
for calendar in calendars
]
)
return [
calendar
for calendar, supported_components in zip(calendars, components_results)
if component in supported_components
]
38 changes: 15 additions & 23 deletions homeassistant/components/caldav/calendar.py
Expand Up @@ -24,11 +24,12 @@
)
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .api import async_get_calendars
from .const import DOMAIN
from .coordinator import CalDavUpdateCoordinator

Expand All @@ -43,7 +44,8 @@
# Number of days to look ahead for next event when configured by ConfigEntry
CONFIG_ENTRY_DEFAULT_DAYS = 7

OFFSET = "!!"
# Only allow VCALENDARs that support this component type
SUPPORTED_COMPONENT = "VEVENT"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
Expand All @@ -69,10 +71,10 @@
)


def setup_platform(
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
async_add_entities: AddEntitiesCallback,
disc_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the WebDav Calendar platform."""
Expand All @@ -85,9 +87,9 @@ def setup_platform(
url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL]
)

calendars = client.principal().calendars()
calendars = await async_get_calendars(hass, client, SUPPORTED_COMPONENT)

calendar_devices = []
entities = []
device_id: str | None
for calendar in list(calendars):
# If a calendar name was given in the configuration,
Expand All @@ -104,46 +106,36 @@ def setup_platform(

name = cust_calendar[CONF_NAME]
device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}"
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
coordinator = CalDavUpdateCoordinator(
hass,
calendar=calendar,
days=days,
include_all_day=True,
search=cust_calendar[CONF_SEARCH],
)
calendar_devices.append(
entities.append(
WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
)

# Create a default calendar if there was no custom one for all calendars
# that support events.
if not config[CONF_CUSTOM_CALENDARS]:
if (
supported_components := calendar.get_supported_components()
) and "VEVENT" not in supported_components:
_LOGGER.debug(
"Ignoring calendar '%s' (components=%s)",
calendar.name,
supported_components,
)
continue

name = calendar.name
device_id = calendar.name
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
coordinator = CalDavUpdateCoordinator(
hass,
calendar=calendar,
days=days,
include_all_day=False,
search=None,
)
calendar_devices.append(
entities.append(
WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
)

add_entities(calendar_devices, True)
async_add_entities(entities, True)


async def async_setup_entry(
Expand All @@ -153,12 +145,12 @@ async def async_setup_entry(
) -> None:
"""Set up the CalDav calendar platform for a config entry."""
client: caldav.DAVClient = hass.data[DOMAIN][entry.entry_id]
calendars = await hass.async_add_executor_job(client.principal().calendars)
calendars = await async_get_calendars(hass, client, SUPPORTED_COMPONENT)
async_add_entities(
(
WebDavCalendarEntity(
calendar.name,
generate_entity_id(ENTITY_ID_FORMAT, calendar.name, hass=hass),
async_generate_entity_id(ENTITY_ID_FORMAT, calendar.name, hass=hass),
CalDavUpdateCoordinator(
hass,
calendar=calendar,
Expand Down
43 changes: 35 additions & 8 deletions tests/components/caldav/test_calendar.py
Expand Up @@ -1056,7 +1056,6 @@ async def test_get_events_custom_calendars(
_mock_calendar("Calendar 1", supported_components=["VEVENT"]),
_mock_calendar("Calendar 2", supported_components=["VEVENT", "VJOURNAL"]),
_mock_calendar("Calendar 3", supported_components=["VTODO"]),
# Fallback to allow when no components are supported to be conservative
_mock_calendar("Calendar 4", supported_components=[]),
]
],
Expand All @@ -1069,22 +1068,17 @@ async def test_calendar_components(hass: HomeAssistant) -> None:

state = hass.states.get("calendar.calendar_1")
assert state
assert state.name == "Calendar 1"
assert state.state == STATE_OFF

state = hass.states.get("calendar.calendar_2")
assert state
assert state.name == "Calendar 2"
assert state.state == STATE_OFF

# No entity created for To-do only component
state = hass.states.get("calendar.calendar_3")
assert not state

# No entity created when no components exist
state = hass.states.get("calendar.calendar_4")
assert state
assert state.name == "Calendar 4"
assert state.state == STATE_OFF
assert not state


@pytest.mark.parametrize("tz", [UTC])
Expand All @@ -1109,3 +1103,36 @@ async def test_setup_config_entry(
"location": "Hamburg",
"description": "What a beautiful day",
}


@pytest.mark.parametrize(
("calendars"),
[
[
_mock_calendar("Calendar 1", supported_components=["VEVENT"]),
_mock_calendar("Calendar 2", supported_components=["VEVENT", "VJOURNAL"]),
_mock_calendar("Calendar 3", supported_components=["VTODO"]),
_mock_calendar("Calendar 4", supported_components=[]),
]
],
)
async def test_config_entry_supported_components(
hass: HomeAssistant,
setup_integration: Callable[[], Awaitable[bool]],
) -> None:
"""Test that calendars are only created for VEVENT types when using a config entry."""
assert await setup_integration()

state = hass.states.get("calendar.calendar_1")
assert state

state = hass.states.get("calendar.calendar_2")
assert state

# No entity created for To-do only component
state = hass.states.get("calendar.calendar_3")
assert not state

# No entity created when no components exist
state = hass.states.get("calendar.calendar_4")
assert not state

0 comments on commit 936956a

Please sign in to comment.