mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add an entity description for Google Calendar (#125469)
This commit is contained in:
parent
31d722f1ef
commit
d6e34e0984
@ -2,13 +2,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from gcal_sync.api import Range, SyncEventsRequest
|
||||
from gcal_sync.exceptions import ApiException
|
||||
from gcal_sync.model import AccessRole, DateOrDatetime, Event
|
||||
from gcal_sync.model import AccessRole, Calendar, DateOrDatetime, Event
|
||||
from gcal_sync.store import ScopedCalendarStore
|
||||
from gcal_sync.sync import CalendarEventSyncManager
|
||||
|
||||
@ -32,7 +34,7 @@ from homeassistant.const import CONF_DEVICE_ID, CONF_ENTITIES, CONF_NAME, CONF_O
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||
from homeassistant.helpers import entity_platform, entity_registry as er
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.entity import EntityDescription, generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
@ -81,6 +83,83 @@ RRULE_PREFIX = "RRULE:"
|
||||
SERVICE_CREATE_EVENT = "create_event"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class GoogleCalendarEntityDescription(EntityDescription):
|
||||
"""Google calendar entity description."""
|
||||
|
||||
name: str
|
||||
entity_id: str
|
||||
read_only: bool
|
||||
ignore_availability: bool
|
||||
offset: str | None
|
||||
search: str | None
|
||||
local_sync: bool
|
||||
device_id: str
|
||||
|
||||
|
||||
def _get_entity_descriptions(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
calendar_item: Calendar,
|
||||
calendar_info: Mapping[str, Any],
|
||||
) -> list[GoogleCalendarEntityDescription]:
|
||||
"""Create entity descriptions for the calendar.
|
||||
|
||||
The entity descriptions are based on the type of Calendar from the API
|
||||
and optional calendar_info yaml configuration that is the older way to
|
||||
configure calendars before they supported UI based config.
|
||||
|
||||
The yaml config may map one calendar to multiple entities and they do not
|
||||
have a unique id. The yaml config also supports additional options like
|
||||
offsets or search.
|
||||
"""
|
||||
calendar_id = calendar_item.id
|
||||
num_entities = len(calendar_info[CONF_ENTITIES])
|
||||
entity_descriptions = []
|
||||
for data in calendar_info[CONF_ENTITIES]:
|
||||
if num_entities > 1:
|
||||
key = ""
|
||||
else:
|
||||
key = calendar_id
|
||||
entity_enabled = data.get(CONF_TRACK, True)
|
||||
if not entity_enabled:
|
||||
_LOGGER.warning(
|
||||
"The 'track' option in google_calendars.yaml has been deprecated."
|
||||
" The setting has been imported to the UI, and should now be"
|
||||
" removed from google_calendars.yaml"
|
||||
)
|
||||
read_only = not (
|
||||
calendar_item.access_role.is_writer
|
||||
and get_feature_access(hass, config_entry) is FeatureAccess.read_write
|
||||
)
|
||||
# Prefer calendar sync down of resources when possible. However,
|
||||
# sync does not work for search. Also free-busy calendars denormalize
|
||||
# recurring events as individual events which is not efficient for sync
|
||||
local_sync = True
|
||||
if (
|
||||
search := data.get(CONF_SEARCH)
|
||||
) or calendar_item.access_role == AccessRole.FREE_BUSY_READER:
|
||||
read_only = True
|
||||
local_sync = False
|
||||
entity_descriptions.append(
|
||||
GoogleCalendarEntityDescription(
|
||||
key=key,
|
||||
name=data[CONF_NAME].capitalize(),
|
||||
entity_id=generate_entity_id(
|
||||
ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass
|
||||
),
|
||||
read_only=read_only,
|
||||
ignore_availability=data.get(CONF_IGNORE_AVAILABILITY, False),
|
||||
offset=data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET),
|
||||
search=search,
|
||||
local_sync=local_sync,
|
||||
entity_registry_enabled_default=entity_enabled,
|
||||
device_id=data[CONF_DEVICE_ID],
|
||||
)
|
||||
)
|
||||
return entity_descriptions
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
@ -117,30 +196,21 @@ async def async_setup_entry(
|
||||
hass, calendar_item.dict(exclude_unset=True)
|
||||
)
|
||||
new_calendars.append(calendar_info)
|
||||
# Yaml calendar config may map one calendar to multiple entities
|
||||
# with extra options like offsets or search criteria.
|
||||
num_entities = len(calendar_info[CONF_ENTITIES])
|
||||
for data in calendar_info[CONF_ENTITIES]:
|
||||
entity_enabled = data.get(CONF_TRACK, True)
|
||||
if not entity_enabled:
|
||||
_LOGGER.warning(
|
||||
"The 'track' option in google_calendars.yaml has been deprecated."
|
||||
" The setting has been imported to the UI, and should now be"
|
||||
" removed from google_calendars.yaml"
|
||||
)
|
||||
entity_name = data[CONF_DEVICE_ID]
|
||||
# The unique id is based on the config entry and calendar id since
|
||||
# multiple accounts can have a common calendar id
|
||||
# (e.g. `en.usa#holiday@group.v.calendar.google.com`).
|
||||
# When using google_calendars.yaml with multiple entities for a
|
||||
# single calendar, we have no way to set a unique id.
|
||||
if num_entities > 1:
|
||||
unique_id = None
|
||||
else:
|
||||
unique_id = f"{config_entry.unique_id}-{calendar_id}"
|
||||
|
||||
for entity_description in _get_entity_descriptions(
|
||||
hass, config_entry, calendar_item, calendar_info
|
||||
):
|
||||
unique_id = (
|
||||
f"{config_entry.unique_id}-{entity_description.key}"
|
||||
if entity_description.key
|
||||
else None
|
||||
)
|
||||
# Migrate to new unique_id format which supports
|
||||
# multiple config entries as of 2022.7
|
||||
for old_unique_id in (calendar_id, f"{calendar_id}-{entity_name}"):
|
||||
for old_unique_id in (
|
||||
calendar_id,
|
||||
f"{calendar_id}-{entity_description.device_id}",
|
||||
):
|
||||
if not (entity_entry := entity_entry_map.get(old_unique_id)):
|
||||
continue
|
||||
if unique_id:
|
||||
@ -163,24 +233,14 @@ async def async_setup_entry(
|
||||
entity_entry.entity_id,
|
||||
)
|
||||
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator
|
||||
# Prefer calendar sync down of resources when possible. However,
|
||||
# sync does not work for search. Also free-busy calendars denormalize
|
||||
# recurring events as individual events which is not efficient for sync
|
||||
support_write = (
|
||||
calendar_item.access_role.is_writer
|
||||
and get_feature_access(hass, config_entry) is FeatureAccess.read_write
|
||||
)
|
||||
if (
|
||||
search := data.get(CONF_SEARCH)
|
||||
) or calendar_item.access_role == AccessRole.FREE_BUSY_READER:
|
||||
if not entity_description.local_sync:
|
||||
coordinator = CalendarQueryUpdateCoordinator(
|
||||
hass,
|
||||
calendar_service,
|
||||
data[CONF_NAME],
|
||||
entity_description.name,
|
||||
calendar_id,
|
||||
search,
|
||||
entity_description.search,
|
||||
)
|
||||
support_write = False
|
||||
else:
|
||||
request_template = SyncEventsRequest(
|
||||
calendar_id=calendar_id,
|
||||
@ -188,23 +248,22 @@ async def async_setup_entry(
|
||||
)
|
||||
sync = CalendarEventSyncManager(
|
||||
calendar_service,
|
||||
store=ScopedCalendarStore(store, unique_id or entity_name),
|
||||
store=ScopedCalendarStore(
|
||||
store, unique_id or entity_description.device_id
|
||||
),
|
||||
request_template=request_template,
|
||||
)
|
||||
coordinator = CalendarSyncUpdateCoordinator(
|
||||
hass,
|
||||
sync,
|
||||
data[CONF_NAME],
|
||||
entity_description.name,
|
||||
)
|
||||
entities.append(
|
||||
GoogleCalendarEntity(
|
||||
coordinator,
|
||||
calendar_id,
|
||||
data,
|
||||
generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass),
|
||||
entity_description,
|
||||
unique_id,
|
||||
entity_enabled,
|
||||
support_write,
|
||||
)
|
||||
)
|
||||
|
||||
@ -238,29 +297,26 @@ class GoogleCalendarEntity(
|
||||
):
|
||||
"""A calendar event entity."""
|
||||
|
||||
entity_description: GoogleCalendarEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator,
|
||||
calendar_id: str,
|
||||
data: dict[str, Any],
|
||||
entity_id: str,
|
||||
entity_description: GoogleCalendarEntityDescription,
|
||||
unique_id: str | None,
|
||||
entity_enabled: bool,
|
||||
supports_write: bool,
|
||||
) -> None:
|
||||
"""Create the Calendar event device."""
|
||||
super().__init__(coordinator)
|
||||
self.calendar_id = calendar_id
|
||||
self._ignore_availability: bool = data.get(CONF_IGNORE_AVAILABILITY, False)
|
||||
self.entity_description = entity_description
|
||||
self._ignore_availability = entity_description.ignore_availability
|
||||
self._offset = entity_description.offset
|
||||
self._event: CalendarEvent | None = None
|
||||
self._attr_name = data[CONF_NAME].capitalize()
|
||||
self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET)
|
||||
self.entity_id = entity_id
|
||||
self.entity_id = entity_description.entity_id
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_entity_registry_enabled_default = entity_enabled
|
||||
if supports_write:
|
||||
if not entity_description.read_only:
|
||||
self._attr_supported_features = (
|
||||
CalendarEntityFeature.CREATE_EVENT | CalendarEntityFeature.DELETE_EVENT
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user