-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add sensors to Google Drive #156167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add sensors to Google Drive #156167
Changes from 1 commit
0773bb8
013c474
c46baf1
005ad44
f02f4df
880abda
eb0905a
9bb083e
9dd2f24
d537ca3
e75bbed
2b3887c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| """DataUpdateCoordinator for WLED.""" | ||
|
mik-laj marked this conversation as resolved.
Outdated
|
||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| import logging | ||
|
|
||
| from google_drive_api.exceptions import GoogleDriveApiError | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
|
||
| from .api import DriveClient, StorageQuotaData | ||
| from .const import DOMAIN, SCAN_INTERVAL | ||
|
|
||
| type GoogleDriveConfigEntry = ConfigEntry[GoogleDriveDataUpdateCoordinator] | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| @dataclass | ||
| class GoogleDriveCoordinatorData: | ||
| """Class to hold coordinator data.""" | ||
|
|
||
| storage_quota: StorageQuotaData | ||
| email_address: str | ||
|
|
||
|
|
||
| class GoogleDriveDataUpdateCoordinator( | ||
| DataUpdateCoordinator[GoogleDriveCoordinatorData] | ||
| ): | ||
| """Class to manage fetching Google Drive data from single endpoint.""" | ||
|
|
||
| client: DriveClient | ||
| config_entry: GoogleDriveConfigEntry | ||
| _email_address: str | ||
| backup_folder_id: str | ||
|
|
||
| def __init__( | ||
| self, | ||
| hass: HomeAssistant, | ||
| *, | ||
| client: DriveClient, | ||
| backup_folder_id: str, | ||
| entry: GoogleDriveConfigEntry, | ||
| ) -> None: | ||
| """Initialize Google Drive data updater.""" | ||
| self.client = client | ||
| self.backup_folder_id = backup_folder_id | ||
|
|
||
| super().__init__( | ||
| hass, | ||
| _LOGGER, | ||
| config_entry=entry, | ||
| name=DOMAIN, | ||
| update_interval=SCAN_INTERVAL, | ||
| ) | ||
|
|
||
| async def _async_setup(self) -> None: | ||
| """Do initialization logic.""" | ||
| self._email_address = await self.client.async_get_email_address() | ||
|
|
||
| async def _async_update_data(self) -> GoogleDriveCoordinatorData: | ||
| """Fetch data from Google Drive.""" | ||
| try: | ||
| storage_quota = await self.client.async_get_storage_quota() | ||
| return GoogleDriveCoordinatorData( | ||
| storage_quota=storage_quota, | ||
| email_address=self._email_address, | ||
| ) | ||
|
mik-laj marked this conversation as resolved.
Outdated
|
||
| except GoogleDriveApiError as error: | ||
| raise UpdateFailed( | ||
| translation_domain=DOMAIN, | ||
| translation_key="invalid_response_google_drive_error", | ||
| translation_placeholders={"error": str(error)}, | ||
| ) from error | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| """Define the Google Drive entity.""" | ||
|
|
||
| from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo | ||
| from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
|
||
| from .const import DOMAIN | ||
| from .coordinator import GoogleDriveDataUpdateCoordinator | ||
|
|
||
|
|
||
| class GoogleDriveEntity(CoordinatorEntity[GoogleDriveDataUpdateCoordinator]): | ||
| """Defines a base Google Drive entity.""" | ||
|
|
||
| _attr_has_entity_name = True | ||
|
|
||
| @property | ||
| def device_info(self) -> DeviceInfo: | ||
| """Return device information about this Google Drive device.""" | ||
| return DeviceInfo( | ||
| identifiers={(DOMAIN, str(self.coordinator.config_entry.unique_id))}, | ||
| name=self.coordinator.data.email_address, | ||
| manufacturer="Google", | ||
| model="Google Drive", | ||
| configuration_url=f"https://drive.google.com/drive/folders/{self.coordinator.backup_folder_id}", | ||
| entry_type=DeviceEntryType.SERVICE, | ||
| ) |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to update quality_scale.yaml
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated it, but now we have a silver badge. I'll add diagnostics as a follow-up to get back to platinum. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||
| """Support for GoogleDrive sensors.""" | ||||||
|
|
||||||
| from __future__ import annotations | ||||||
|
|
||||||
| from collections.abc import Callable | ||||||
| from dataclasses import dataclass | ||||||
| from datetime import datetime | ||||||
|
|
||||||
| from homeassistant.components.sensor import ( | ||||||
| SensorDeviceClass, | ||||||
| SensorEntity, | ||||||
| SensorEntityDescription, | ||||||
| ) | ||||||
| from homeassistant.const import EntityCategory, UnitOfInformation | ||||||
| from homeassistant.core import HomeAssistant | ||||||
| from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback | ||||||
| from homeassistant.helpers.typing import StateType | ||||||
| from homeassistant.util import slugify | ||||||
|
|
||||||
| from .coordinator import ( | ||||||
| GoogleDriveConfigEntry, | ||||||
| GoogleDriveCoordinatorData, | ||||||
| GoogleDriveDataUpdateCoordinator, | ||||||
| ) | ||||||
| from .entity import GoogleDriveEntity | ||||||
|
|
||||||
| # Coordinator is used to centralize the data updates | ||||||
| PARALLEL_UPDATES = 0 | ||||||
|
|
||||||
|
|
||||||
| @dataclass(frozen=True, kw_only=True) | ||||||
| class GoogleDriveSensorEntityDescription(SensorEntityDescription): | ||||||
| """Describes GoogleDrive sensor entity.""" | ||||||
|
|
||||||
| value_fn: Callable[[GoogleDriveCoordinatorData], datetime | StateType] | ||||||
|
mik-laj marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
|
|
||||||
| SENSORS: tuple[GoogleDriveSensorEntityDescription, ...] = ( | ||||||
| GoogleDriveSensorEntityDescription( | ||||||
| key="storage_total", | ||||||
| translation_key="storage_total", | ||||||
| native_unit_of_measurement=UnitOfInformation.BYTES, | ||||||
| suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, | ||||||
| suggested_display_precision=0, | ||||||
| device_class=SensorDeviceClass.DATA_SIZE, | ||||||
| entity_category=EntityCategory.DIAGNOSTIC, | ||||||
| value_fn=lambda data: data.storage_quota.limit, | ||||||
| ), | ||||||
| GoogleDriveSensorEntityDescription( | ||||||
| key="storage_used", | ||||||
| translation_key="storage_used", | ||||||
| native_unit_of_measurement=UnitOfInformation.BYTES, | ||||||
| suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, | ||||||
| suggested_display_precision=0, | ||||||
| device_class=SensorDeviceClass.DATA_SIZE, | ||||||
| entity_category=EntityCategory.DIAGNOSTIC, | ||||||
| value_fn=lambda data: data.storage_quota.usage, | ||||||
| ), | ||||||
| GoogleDriveSensorEntityDescription( | ||||||
| key="storage_used_in_drive", | ||||||
| translation_key="storage_used_in_drive", | ||||||
| native_unit_of_measurement=UnitOfInformation.BYTES, | ||||||
| suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, | ||||||
| suggested_display_precision=0, | ||||||
| device_class=SensorDeviceClass.DATA_SIZE, | ||||||
| entity_category=EntityCategory.DIAGNOSTIC, | ||||||
| value_fn=lambda data: data.storage_quota.usage_in_drive, | ||||||
| entity_registry_enabled_default=False, | ||||||
| ), | ||||||
| GoogleDriveSensorEntityDescription( | ||||||
| key="storage_used_in_drive_trash", | ||||||
| translation_key="storage_used_in_drive_trash", | ||||||
| native_unit_of_measurement=UnitOfInformation.BYTES, | ||||||
| suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, | ||||||
| suggested_display_precision=0, | ||||||
| device_class=SensorDeviceClass.DATA_SIZE, | ||||||
| entity_category=EntityCategory.DIAGNOSTIC, | ||||||
| value_fn=lambda data: data.storage_quota.usage_in_trash, | ||||||
| entity_registry_enabled_default=False, | ||||||
| ), | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| async def async_setup_entry( | ||||||
| hass: HomeAssistant, | ||||||
| entry: GoogleDriveConfigEntry, | ||||||
| async_add_entities: AddConfigEntryEntitiesCallback, | ||||||
| ) -> None: | ||||||
| """Set up GoogleDrive sensor based on a config entry.""" | ||||||
| coordinator = entry.runtime_data | ||||||
| async_add_entities( | ||||||
| GoogleDriveSensorEntity(coordinator, description) for description in SENSORS | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| class GoogleDriveSensorEntity(GoogleDriveEntity, SensorEntity): | ||||||
|
mik-laj marked this conversation as resolved.
|
||||||
| """Defines a Google Drive sensor entity.""" | ||||||
|
|
||||||
| entity_description: GoogleDriveSensorEntityDescription | ||||||
|
|
||||||
| def __init__( | ||||||
| self, | ||||||
| coordinator: GoogleDriveDataUpdateCoordinator, | ||||||
| description: GoogleDriveSensorEntityDescription, | ||||||
| ) -> None: | ||||||
| """Initialize a Google Drive sensor entity.""" | ||||||
| super().__init__(coordinator=coordinator) | ||||||
|
mik-laj marked this conversation as resolved.
Outdated
|
||||||
| self.entity_description = description | ||||||
| self._attr_unique_id = ( | ||||||
| f"{slugify(coordinator.config_entry.unique_id)}_{description.key}" | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we slugify? The danger here is that slugify also is an external library, so once that changes its behavior (what can happen), the unique id would change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For other place in this integration, we use slugify to provide a unique id. For consistency, I used the same implementation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The more I think about this line, the less comfortable I feel with how the
backup integration include agent ID in diagnostics, so it also export e-mail address), logs, or even entity registry dumps. That’s not ideal from a privacy or data-leak perspective.
It would probably be worth migrate the config entry to use a different identifier e.g. hash of e-mail address or
https://developers.google.com/identity/openid-connect/openid-connect I would be happy for any advice on which approach to choose.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now, i updated code to use local variant of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use of sub field may be problematic as it requires new scope 'openid', but in this case, we can use |
||||||
| ) | ||||||
|
|
||||||
| @property | ||||||
| def native_value(self) -> datetime | StateType: | ||||||
|
mik-laj marked this conversation as resolved.
Outdated
|
||||||
| """Return the state of the sensor.""" | ||||||
| return self.entity_description.value_fn(self.coordinator.data) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,5 +42,21 @@ | |
| "title": "[%key:common::config_flow::title::reauth%]" | ||
| } | ||
| } | ||
| }, | ||
| "entity": { | ||
| "sensor": { | ||
| "storage_total": { | ||
| "name": "Total available storage" | ||
| }, | ||
| "storage_used": { | ||
| "name": "Used storage" | ||
| }, | ||
| "storage_used_in_drive": { | ||
| "name": "Used storage in Drive" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this for files uploaded by HA?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This applies to the entire Google Drive, but it's a good idea. I've added a new sensor that counts the size of backups for our HA instance. |
||
| }, | ||
| "storage_used_in_drive_trash": { | ||
| "name": "Used storage in Drive Trash" | ||
| } | ||
| } | ||
| } | ||
| } | ||

Uh oh!
There was an error while loading. Please reload this page.