1 """Support for Google Sheets."""
3 from __future__
import annotations
5 from datetime
import datetime
8 from google.auth.exceptions
import RefreshError
9 from google.oauth2.credentials
import Credentials
10 from gspread
import Client
11 from gspread.exceptions
import APIError
12 from gspread.utils
import ValueInputOption
13 import voluptuous
as vol
19 ConfigEntryAuthFailed,
25 async_get_config_entry_implementation,
30 from .const
import DEFAULT_ACCESS, DOMAIN
32 type GoogleSheetsConfigEntry = ConfigEntry[OAuth2Session]
35 DATA_CONFIG_ENTRY =
"config_entry"
36 WORKSHEET =
"worksheet"
38 SERVICE_APPEND_SHEET =
"append_sheet"
40 SHEET_SERVICE_SCHEMA = vol.All(
43 vol.Optional(WORKSHEET): cv.string,
44 vol.Required(DATA): vol.Any(cv.ensure_list, [dict]),
50 hass: HomeAssistant, entry: GoogleSheetsConfigEntry
52 """Set up Google Sheets from a config entry."""
56 await session.async_ensure_token_valid()
57 except aiohttp.ClientResponseError
as err:
58 if 400 <= err.status < 500:
60 "OAuth session is not valid, reauth required"
62 raise ConfigEntryNotReady
from err
63 except aiohttp.ClientError
as err:
64 raise ConfigEntryNotReady
from err
68 entry.runtime_data = session
76 """Verify that the config entry desired scope is present in the oauth token."""
77 return DEFAULT_ACCESS
in entry.data.get(CONF_TOKEN, {}).
get(
"scope",
"").split(
" ")
81 hass: HomeAssistant, entry: GoogleSheetsConfigEntry
83 """Unload a config entry."""
86 for entry
in hass.config_entries.async_entries(DOMAIN)
87 if entry.state == ConfigEntryState.LOADED
89 if len(loaded_entries) == 1:
90 for service_name
in hass.services.async_services_for_domain(DOMAIN):
91 hass.services.async_remove(DOMAIN, service_name)
97 """Add the services for Google Sheets."""
99 def _append_to_sheet(call: ServiceCall, entry: GoogleSheetsConfigEntry) ->
None:
100 """Run append in the executor."""
101 service = Client(Credentials(entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]))
103 sheet = service.open_by_key(entry.unique_id)
105 entry.async_start_reauth(hass)
107 except APIError
as ex:
110 worksheet = sheet.worksheet(call.data.get(WORKSHEET, sheet.sheet1.title))
111 columns: list[str] = next(iter(worksheet.get_values(
"A1:ZZ1")), [])
112 now =
str(datetime.now())
114 for d
in call.data[DATA]:
115 row_data = {
"created": now} | d
116 row = [row_data.get(column,
"")
for column
in columns]
117 for key, value
in row_data.items():
118 if key
not in columns:
120 worksheet.update_cell(1, len(columns), key)
123 worksheet.append_rows(rows, value_input_option=ValueInputOption.user_entered)
125 async
def append_to_sheet(call: ServiceCall) ->
None:
126 """Append new line of data to a Google Sheets document."""
127 entry: GoogleSheetsConfigEntry |
None = hass.config_entries.async_get_entry(
128 call.data[DATA_CONFIG_ENTRY]
130 if not entry
or not hasattr(entry,
"runtime_data"):
131 raise ValueError(f
"Invalid config entry: {call.data[DATA_CONFIG_ENTRY]}")
132 await entry.runtime_data.async_ensure_token_valid()
133 await hass.async_add_executor_job(_append_to_sheet, call, entry)
135 hass.services.async_register(
137 SERVICE_APPEND_SHEET,
139 schema=SHEET_SERVICE_SCHEMA,
web.Response get(self, web.Request request, str config_key)
None async_setup_service(HomeAssistant hass)
bool async_entry_has_scopes(HomeAssistant hass, GoogleSheetsConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, GoogleSheetsConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, GoogleSheetsConfigEntry entry)
AbstractOAuth2Implementation async_get_config_entry_implementation(HomeAssistant hass, config_entries.ConfigEntry config_entry)