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 testpull/73029/head
parent
b5fe4e8474
commit
9d933e732b
|
@ -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,
|
||||
|
|
|
@ -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,39 +59,32 @@ 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]:
|
||||
for calendar_item in result.items:
|
||||
calendar_id = calendar_item.id
|
||||
if calendars and calendar_id in calendars:
|
||||
calendar_info = calendars[calendar_id]
|
||||
else:
|
||||
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(
|
||||
|
@ -99,26 +92,30 @@ def _async_setup_entities(
|
|||
"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:
|
||||
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
|
||||
unique_id = f"{calendar_id}-{entity_name}"
|
||||
else:
|
||||
unique_id = calendar_id
|
||||
entity = GoogleCalendarEntity(
|
||||
calendar_service,
|
||||
disc_info[CONF_CAL_ID],
|
||||
data,
|
||||
entity_id,
|
||||
unique_id,
|
||||
f"{calendar_id}-{entity_name}" if num_entities > 1 else calendar_id,
|
||||
entity_enabled,
|
||||
)
|
||||
entities.append(entity)
|
||||
)
|
||||
|
||||
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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue