mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +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 __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from gcal_sync.api import Range, SyncEventsRequest
|
from gcal_sync.api import Range, SyncEventsRequest
|
||||||
from gcal_sync.exceptions import ApiException
|
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.store import ScopedCalendarStore
|
||||||
from gcal_sync.sync import CalendarEventSyncManager
|
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.core import HomeAssistant, ServiceCall
|
||||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||||
from homeassistant.helpers import entity_platform, entity_registry as er
|
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.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@ -81,6 +83,83 @@ RRULE_PREFIX = "RRULE:"
|
|||||||
SERVICE_CREATE_EVENT = "create_event"
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
@ -117,30 +196,21 @@ async def async_setup_entry(
|
|||||||
hass, calendar_item.dict(exclude_unset=True)
|
hass, calendar_item.dict(exclude_unset=True)
|
||||||
)
|
)
|
||||||
new_calendars.append(calendar_info)
|
new_calendars.append(calendar_info)
|
||||||
# Yaml calendar config may map one calendar to multiple entities
|
|
||||||
# with extra options like offsets or search criteria.
|
for entity_description in _get_entity_descriptions(
|
||||||
num_entities = len(calendar_info[CONF_ENTITIES])
|
hass, config_entry, calendar_item, calendar_info
|
||||||
for data in calendar_info[CONF_ENTITIES]:
|
):
|
||||||
entity_enabled = data.get(CONF_TRACK, True)
|
unique_id = (
|
||||||
if not entity_enabled:
|
f"{config_entry.unique_id}-{entity_description.key}"
|
||||||
_LOGGER.warning(
|
if entity_description.key
|
||||||
"The 'track' option in google_calendars.yaml has been deprecated."
|
else None
|
||||||
" 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}"
|
|
||||||
# Migrate to new unique_id format which supports
|
# Migrate to new unique_id format which supports
|
||||||
# multiple config entries as of 2022.7
|
# 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)):
|
if not (entity_entry := entity_entry_map.get(old_unique_id)):
|
||||||
continue
|
continue
|
||||||
if unique_id:
|
if unique_id:
|
||||||
@ -163,24 +233,14 @@ async def async_setup_entry(
|
|||||||
entity_entry.entity_id,
|
entity_entry.entity_id,
|
||||||
)
|
)
|
||||||
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator
|
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator
|
||||||
# Prefer calendar sync down of resources when possible. However,
|
if not entity_description.local_sync:
|
||||||
# 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:
|
|
||||||
coordinator = CalendarQueryUpdateCoordinator(
|
coordinator = CalendarQueryUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
calendar_service,
|
calendar_service,
|
||||||
data[CONF_NAME],
|
entity_description.name,
|
||||||
calendar_id,
|
calendar_id,
|
||||||
search,
|
entity_description.search,
|
||||||
)
|
)
|
||||||
support_write = False
|
|
||||||
else:
|
else:
|
||||||
request_template = SyncEventsRequest(
|
request_template = SyncEventsRequest(
|
||||||
calendar_id=calendar_id,
|
calendar_id=calendar_id,
|
||||||
@ -188,23 +248,22 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
sync = CalendarEventSyncManager(
|
sync = CalendarEventSyncManager(
|
||||||
calendar_service,
|
calendar_service,
|
||||||
store=ScopedCalendarStore(store, unique_id or entity_name),
|
store=ScopedCalendarStore(
|
||||||
|
store, unique_id or entity_description.device_id
|
||||||
|
),
|
||||||
request_template=request_template,
|
request_template=request_template,
|
||||||
)
|
)
|
||||||
coordinator = CalendarSyncUpdateCoordinator(
|
coordinator = CalendarSyncUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
sync,
|
sync,
|
||||||
data[CONF_NAME],
|
entity_description.name,
|
||||||
)
|
)
|
||||||
entities.append(
|
entities.append(
|
||||||
GoogleCalendarEntity(
|
GoogleCalendarEntity(
|
||||||
coordinator,
|
coordinator,
|
||||||
calendar_id,
|
calendar_id,
|
||||||
data,
|
entity_description,
|
||||||
generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass),
|
|
||||||
unique_id,
|
unique_id,
|
||||||
entity_enabled,
|
|
||||||
support_write,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -238,29 +297,26 @@ class GoogleCalendarEntity(
|
|||||||
):
|
):
|
||||||
"""A calendar event entity."""
|
"""A calendar event entity."""
|
||||||
|
|
||||||
|
entity_description: GoogleCalendarEntityDescription
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator,
|
coordinator: CalendarSyncUpdateCoordinator | CalendarQueryUpdateCoordinator,
|
||||||
calendar_id: str,
|
calendar_id: str,
|
||||||
data: dict[str, Any],
|
entity_description: GoogleCalendarEntityDescription,
|
||||||
entity_id: str,
|
|
||||||
unique_id: str | None,
|
unique_id: str | None,
|
||||||
entity_enabled: bool,
|
|
||||||
supports_write: bool,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create the Calendar event device."""
|
"""Create the Calendar event device."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.calendar_id = calendar_id
|
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._event: CalendarEvent | None = None
|
||||||
self._attr_name = data[CONF_NAME].capitalize()
|
self.entity_id = entity_description.entity_id
|
||||||
self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET)
|
|
||||||
self.entity_id = entity_id
|
|
||||||
self._attr_unique_id = unique_id
|
self._attr_unique_id = unique_id
|
||||||
self._attr_entity_registry_enabled_default = entity_enabled
|
if not entity_description.read_only:
|
||||||
if supports_write:
|
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = (
|
||||||
CalendarEntityFeature.CREATE_EVENT | CalendarEntityFeature.DELETE_EVENT
|
CalendarEntityFeature.CREATE_EVENT | CalendarEntityFeature.DELETE_EVENT
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user