Remove google scan_for_calendars service and simplify platform setup (#73010)

* Remove google scan_for_calendars service and simplify platform setup

* Update invalid calendar yaml test
pull/73029/head
Allen Porter 2022-06-03 21:56:53 -07:00 committed by GitHub
parent b5fe4e8474
commit 9d933e732b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 184 deletions

View File

@ -1,7 +1,6 @@
"""Support for Google - Calendar Event Devices."""
from __future__ import annotations
import asyncio
from collections.abc import Mapping
from datetime import datetime, timedelta
import logging
@ -9,8 +8,7 @@ from typing import Any
import aiohttp
from gcal_sync.api import GoogleCalendarService
from gcal_sync.exceptions import ApiException
from gcal_sync.model import Calendar, DateOrDatetime, Event
from gcal_sync.model import DateOrDatetime, Event
from oauth2client.file import Storage
import voluptuous as vol
from voluptuous.error import Error as VoluptuousError
@ -31,15 +29,10 @@ from homeassistant.const import (
CONF_OFFSET,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.typing import ConfigType
@ -49,7 +42,6 @@ from .const import (
DATA_CONFIG,
DATA_SERVICE,
DEVICE_AUTH_IMPL,
DISCOVER_CALENDAR,
DOMAIN,
FeatureAccess,
)
@ -86,7 +78,6 @@ NOTIFICATION_ID = "google_calendar_notification"
NOTIFICATION_TITLE = "Google Calendar Setup"
GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors"
SERVICE_SCAN_CALENDARS = "scan_for_calendars"
SERVICE_ADD_EVENT = "add_event"
YAML_DEVICES = f"{DOMAIN}_calendars.yaml"
@ -248,7 +239,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
hass.data[DOMAIN][DATA_SERVICE] = calendar_service
await async_setup_services(hass, calendar_service)
# Only expose the add event service if we have the correct permissions
if get_feature_access(hass, entry) is FeatureAccess.read_write:
await async_setup_add_event_service(hass, calendar_service)
@ -278,57 +268,6 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hass.config_entries.async_reload(entry.entry_id)
async def async_setup_services(
hass: HomeAssistant,
calendar_service: GoogleCalendarService,
) -> None:
"""Set up the service listeners."""
calendars = await hass.async_add_executor_job(
load_config, hass.config.path(YAML_DEVICES)
)
calendars_file_lock = asyncio.Lock()
async def _found_calendar(calendar_item: Calendar) -> None:
calendar = get_calendar_info(
hass,
calendar_item.dict(exclude_unset=True),
)
calendar_id = calendar_item.id
# If the google_calendars.yaml file already exists, populate it for
# backwards compatibility, but otherwise do not create it if it does
# not exist.
if calendars:
if calendar_id not in calendars:
calendars[calendar_id] = calendar
async with calendars_file_lock:
await hass.async_add_executor_job(
update_config, hass.config.path(YAML_DEVICES), calendar
)
else:
# Prefer entity/name information from yaml, overriding api
calendar = calendars[calendar_id]
async_dispatcher_send(hass, DISCOVER_CALENDAR, calendar)
created_calendars = set()
async def _scan_for_calendars(call: ServiceCall) -> None:
"""Scan for new calendars."""
try:
result = await calendar_service.async_list_calendars()
except ApiException as err:
raise HomeAssistantError(str(err)) from err
tasks = []
for calendar_item in result.items:
if calendar_item.id in created_calendars:
continue
created_calendars.add(calendar_item.id)
tasks.append(_found_calendar(calendar_item))
await asyncio.gather(*tasks)
hass.services.async_register(DOMAIN, SERVICE_SCAN_CALENDARS, _scan_for_calendars)
async def async_setup_add_event_service(
hass: HomeAssistant,
calendar_service: GoogleCalendarService,

View File

@ -20,24 +20,24 @@ from homeassistant.components.calendar import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_ENTITIES, CONF_NAME, CONF_OFFSET
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from . import (
CONF_CAL_ID,
CONF_IGNORE_AVAILABILITY,
CONF_SEARCH,
CONF_TRACK,
DATA_SERVICE,
DEFAULT_CONF_OFFSET,
DOMAIN,
SERVICE_SCAN_CALENDARS,
YAML_DEVICES,
get_calendar_info,
load_config,
update_config,
)
from .const import DISCOVER_CALENDAR
_LOGGER = logging.getLogger(__name__)
@ -59,66 +59,63 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the google calendar platform."""
@callback
def async_discover(discovery_info: dict[str, Any]) -> None:
_async_setup_entities(
hass,
entry,
async_add_entities,
discovery_info,
)
entry.async_on_unload(
async_dispatcher_connect(hass, DISCOVER_CALENDAR, async_discover)
)
# Look for any new calendars
calendar_service = hass.data[DOMAIN][DATA_SERVICE]
try:
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, blocking=True)
except HomeAssistantError as err:
# This can happen if there's a connection error during setup.
result = await calendar_service.async_list_calendars()
except ApiException as err:
raise PlatformNotReady(str(err)) from err
@callback
def _async_setup_entities(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
disc_info: dict[str, Any],
) -> None:
calendar_service = hass.data[DOMAIN][DATA_SERVICE]
# Yaml configuration may override objects from the API
calendars = await hass.async_add_executor_job(
load_config, hass.config.path(YAML_DEVICES)
)
new_calendars = []
entities = []
num_entities = len(disc_info[CONF_ENTITIES])
for data in disc_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]
entity_id = generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass)
calendar_id = disc_info[CONF_CAL_ID]
if num_entities > 1:
# The google_calendars.yaml file lets users add multiple entities for
# the same calendar id and needs additional disambiguation
unique_id = f"{calendar_id}-{entity_name}"
for calendar_item in result.items:
calendar_id = calendar_item.id
if calendars and calendar_id in calendars:
calendar_info = calendars[calendar_id]
else:
unique_id = calendar_id
entity = GoogleCalendarEntity(
calendar_service,
disc_info[CONF_CAL_ID],
data,
entity_id,
unique_id,
entity_enabled,
)
entities.append(entity)
calendar_info = get_calendar_info(
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]
entities.append(
GoogleCalendarEntity(
calendar_service,
calendar_id,
data,
generate_entity_id(ENTITY_ID_FORMAT, entity_name, hass=hass),
# The google_calendars.yaml file lets users add multiple entities for
# the same calendar id and needs additional disambiguation
f"{calendar_id}-{entity_name}" if num_entities > 1 else calendar_id,
entity_enabled,
)
)
async_add_entities(entities, True)
if calendars and new_calendars:
def append_calendars_to_config() -> None:
path = hass.config.path(YAML_DEVICES)
for calendar in new_calendars:
update_config(path, calendar)
await hass.async_add_executor_job(append_calendars_to_config)
class GoogleCalendarEntity(CalendarEntity):
"""A calendar event device."""

View File

@ -11,8 +11,6 @@ DATA_CALENDARS = "calendars"
DATA_SERVICE = "service"
DATA_CONFIG = "config"
DISCOVER_CALENDAR = "google_discover_calendar"
class FeatureAccess(Enum):
"""Class to represent different access scopes."""

View File

@ -1,6 +1,3 @@
scan_for_calendars:
name: Scan for calendars
description: Scan for new calendars.
add_event:
name: Add event
description: Add a new calendar event.

View File

@ -14,11 +14,7 @@ from homeassistant.components.application_credentials import (
ClientCredential,
async_import_client_credential,
)
from homeassistant.components.google import (
DOMAIN,
SERVICE_ADD_EVENT,
SERVICE_SCAN_CALENDARS,
)
from homeassistant.components.google import DOMAIN, SERVICE_ADD_EVENT
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_OFF
@ -140,17 +136,24 @@ async def test_invalid_calendar_yaml(
component_setup: ComponentSetup,
calendars_config: list[dict[str, Any]],
mock_calendars_yaml: None,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
setup_config_entry: MockConfigEntry,
) -> None:
"""Test setup with missing entity id fields fails to setup the config entry."""
"""Test setup with missing entity id fields fails to load the platform."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.state is ConfigEntryState.SETUP_ERROR
assert entry.state is ConfigEntryState.LOADED
assert not hass.states.get(TEST_YAML_ENTITY)
assert not hass.states.get(TEST_API_ENTITY)
async def test_calendar_yaml_error(
@ -470,57 +473,6 @@ async def test_add_event_date_time(
}
async def test_scan_calendars(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_list: ApiResult,
mock_events_list: ApiResult,
setup_config_entry: MockConfigEntry,
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test finding a calendar from the API."""
mock_calendars_list({"items": []})
assert await component_setup()
calendar_1 = {
"id": "calendar-id-1",
"summary": "Calendar 1",
}
calendar_2 = {
"id": "calendar-id-2",
"summary": "Calendar 2",
}
aioclient_mock.clear_requests()
mock_calendars_list({"items": [calendar_1]})
mock_events_list({}, calendar_id="calendar-id-1")
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, {}, blocking=True)
await hass.async_block_till_done()
state = hass.states.get("calendar.calendar_1")
assert state
assert state.name == "Calendar 1"
assert state.state == STATE_OFF
assert not hass.states.get("calendar.calendar_2")
aioclient_mock.clear_requests()
mock_calendars_list({"items": [calendar_1, calendar_2]})
mock_events_list({}, calendar_id="calendar-id-1")
mock_events_list({}, calendar_id="calendar-id-2")
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, {}, blocking=True)
await hass.async_block_till_done()
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
@pytest.mark.parametrize(
"config_entry_token_expiry", [datetime.datetime.max.timestamp() + 1]
)