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.""" """Support for Google - Calendar Event Devices."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Mapping from collections.abc import Mapping
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
@ -9,8 +8,7 @@ from typing import Any
import aiohttp import aiohttp
from gcal_sync.api import GoogleCalendarService from gcal_sync.api import GoogleCalendarService
from gcal_sync.exceptions import ApiException from gcal_sync.model import DateOrDatetime, Event
from gcal_sync.model import Calendar, DateOrDatetime, Event
from oauth2client.file import Storage from oauth2client.file import Storage
import voluptuous as vol import voluptuous as vol
from voluptuous.error import Error as VoluptuousError from voluptuous.error import Error as VoluptuousError
@ -31,15 +29,10 @@ from homeassistant.const import (
CONF_OFFSET, CONF_OFFSET,
) )
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ( from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv 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.entity import generate_entity_id
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -49,7 +42,6 @@ from .const import (
DATA_CONFIG, DATA_CONFIG,
DATA_SERVICE, DATA_SERVICE,
DEVICE_AUTH_IMPL, DEVICE_AUTH_IMPL,
DISCOVER_CALENDAR,
DOMAIN, DOMAIN,
FeatureAccess, FeatureAccess,
) )
@ -86,7 +78,6 @@ NOTIFICATION_ID = "google_calendar_notification"
NOTIFICATION_TITLE = "Google Calendar Setup" NOTIFICATION_TITLE = "Google Calendar Setup"
GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors" GROUP_NAME_ALL_CALENDARS = "Google Calendar Sensors"
SERVICE_SCAN_CALENDARS = "scan_for_calendars"
SERVICE_ADD_EVENT = "add_event" SERVICE_ADD_EVENT = "add_event"
YAML_DEVICES = f"{DOMAIN}_calendars.yaml" 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 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 # Only expose the add event service if we have the correct permissions
if get_feature_access(hass, entry) is FeatureAccess.read_write: if get_feature_access(hass, entry) is FeatureAccess.read_write:
await async_setup_add_event_service(hass, calendar_service) 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) 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( async def async_setup_add_event_service(
hass: HomeAssistant, hass: HomeAssistant,
calendar_service: GoogleCalendarService, calendar_service: GoogleCalendarService,

View File

@ -20,24 +20,24 @@ from homeassistant.components.calendar import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_ENTITIES, CONF_NAME, CONF_OFFSET from homeassistant.const import CONF_DEVICE_ID, CONF_ENTITIES, CONF_NAME, CONF_OFFSET
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle from homeassistant.util import Throttle
from . import ( from . import (
CONF_CAL_ID,
CONF_IGNORE_AVAILABILITY, CONF_IGNORE_AVAILABILITY,
CONF_SEARCH, CONF_SEARCH,
CONF_TRACK, CONF_TRACK,
DATA_SERVICE, DATA_SERVICE,
DEFAULT_CONF_OFFSET, DEFAULT_CONF_OFFSET,
DOMAIN, DOMAIN,
SERVICE_SCAN_CALENDARS, YAML_DEVICES,
get_calendar_info,
load_config,
update_config,
) )
from .const import DISCOVER_CALENDAR
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -59,66 +59,63 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the google calendar platform.""" """Set up the google calendar platform."""
calendar_service = hass.data[DOMAIN][DATA_SERVICE]
@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
try: try:
await hass.services.async_call(DOMAIN, SERVICE_SCAN_CALENDARS, blocking=True) result = await calendar_service.async_list_calendars()
except HomeAssistantError as err: except ApiException as err:
# This can happen if there's a connection error during setup.
raise PlatformNotReady(str(err)) from err raise PlatformNotReady(str(err)) from err
# Yaml configuration may override objects from the API
@callback calendars = await hass.async_add_executor_job(
def _async_setup_entities( load_config, hass.config.path(YAML_DEVICES)
hass: HomeAssistant, )
entry: ConfigEntry, new_calendars = []
async_add_entities: AddEntitiesCallback,
disc_info: dict[str, Any],
) -> None:
calendar_service = hass.data[DOMAIN][DATA_SERVICE]
entities = [] entities = []
num_entities = len(disc_info[CONF_ENTITIES]) for calendar_item in result.items:
for data in disc_info[CONF_ENTITIES]: calendar_id = calendar_item.id
entity_enabled = data.get(CONF_TRACK, True) if calendars and calendar_id in calendars:
if not entity_enabled: calendar_info = calendars[calendar_id]
_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}"
else: else:
unique_id = calendar_id calendar_info = get_calendar_info(
entity = GoogleCalendarEntity( hass, calendar_item.dict(exclude_unset=True)
calendar_service, )
disc_info[CONF_CAL_ID], new_calendars.append(calendar_info)
data,
entity_id, # Yaml calendar config may map one calendar to multiple entities with extra options like
unique_id, # offsets or search criteria.
entity_enabled, num_entities = len(calendar_info[CONF_ENTITIES])
) for data in calendar_info[CONF_ENTITIES]:
entities.append(entity) 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) 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): class GoogleCalendarEntity(CalendarEntity):
"""A calendar event device.""" """A calendar event device."""

View File

@ -11,8 +11,6 @@ DATA_CALENDARS = "calendars"
DATA_SERVICE = "service" DATA_SERVICE = "service"
DATA_CONFIG = "config" DATA_CONFIG = "config"
DISCOVER_CALENDAR = "google_discover_calendar"
class FeatureAccess(Enum): class FeatureAccess(Enum):
"""Class to represent different access scopes.""" """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: add_event:
name: Add event name: Add event
description: Add a new calendar event. description: Add a new calendar event.

View File

@ -14,11 +14,7 @@ from homeassistant.components.application_credentials import (
ClientCredential, ClientCredential,
async_import_client_credential, async_import_client_credential,
) )
from homeassistant.components.google import ( from homeassistant.components.google import DOMAIN, SERVICE_ADD_EVENT
DOMAIN,
SERVICE_ADD_EVENT,
SERVICE_SCAN_CALENDARS,
)
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_OFF from homeassistant.const import STATE_OFF
@ -140,17 +136,24 @@ async def test_invalid_calendar_yaml(
component_setup: ComponentSetup, component_setup: ComponentSetup,
calendars_config: list[dict[str, Any]], calendars_config: list[dict[str, Any]],
mock_calendars_yaml: None, mock_calendars_yaml: None,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
setup_config_entry: MockConfigEntry, setup_config_entry: MockConfigEntry,
) -> None: ) -> 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() assert await component_setup()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1 assert len(entries) == 1
entry = entries[0] 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_YAML_ENTITY)
assert not hass.states.get(TEST_API_ENTITY)
async def test_calendar_yaml_error( 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( @pytest.mark.parametrize(
"config_entry_token_expiry", [datetime.datetime.max.timestamp() + 1] "config_entry_token_expiry", [datetime.datetime.max.timestamp() + 1]
) )