Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
74b729b
Add delete service action to OneDrive integration
leodrivera Apr 12, 2026
83b2b10
Address Copilot review comments on onedrive.delete service
leodrivera Apr 12, 2026
c4a2239
Fix test_delete_service_delete_permanently: use async_update_entry
leodrivera Apr 12, 2026
5182d45
Support multiple files in onedrive.delete service
leodrivera Apr 12, 2026
848519d
Refactor onedrive.delete: pre-validate local files before remote dele…
leodrivera Apr 12, 2026
0e28d2f
Clarify delete error messages and sync translations/en.json
leodrivera Apr 12, 2026
70763ca
Address Copilot review comments
leodrivera Apr 12, 2026
a1c1747
Fix test_delete_empty_destination_path: catch vol.Invalid
leodrivera Apr 12, 2026
70d12a9
Address Copilot review comments
leodrivera Apr 12, 2026
9332097
Use text selector with multiple: true for delete service fields
leodrivera Apr 12, 2026
578c4f7
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 18, 2026
fe538fb
Remove local file deletion from onedrive delete service
leodrivera Apr 18, 2026
d67f4f6
Fix error handling in onedrive.delete service
leodrivera Apr 18, 2026
df8169f
Add delete service config entry validation tests
leodrivera Apr 18, 2026
bd4e8bc
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 18, 2026
af1ca41
Address Copilot review comments
leodrivera Apr 18, 2026
2c97657
Fix typo in DESINATION_FOLDER test constant
leodrivera Apr 18, 2026
580f3b3
Use CONF_DELETE_PERMANENTLY constant in delete_permanently test
leodrivera Apr 18, 2026
76dcd44
Add test for multi-error aggregation in onedrive.delete service
leodrivera Apr 18, 2026
d9e2a52
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 19, 2026
6125cdb
Refactor OneDrive error handling for delete operations
leodrivera Apr 26, 2026
5731c56
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 26, 2026
0624559
Enhance OneDrive delete error handling to support ExceptionGroup
leodrivera Apr 26, 2026
d60cf10
Include failed file paths in OneDrive delete error message
leodrivera Apr 26, 2026
7de7905
Apply singular/plural error key pattern to delete service
leodrivera Apr 26, 2026
07b4fbf
Update homeassistant/components/onedrive/services.py
leodrivera Apr 28, 2026
ebb4730
Rename delete error translation key to connection_error for clarity
leodrivera Apr 28, 2026
7b73675
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 28, 2026
f8f72ff
Update homeassistant/components/onedrive/strings.json
leodrivera Apr 30, 2026
8e01046
Update homeassistant/components/onedrive/strings.json
leodrivera Apr 30, 2026
5dd5787
Merge branch 'dev' into add-onedrive-delete-service
leodrivera Apr 30, 2026
f24a349
Refactor OneDrive delete error handling and update related tests
leodrivera Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions homeassistant/components/onedrive/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
}
},
"services": {
"delete": {
"service": "mdi:cloud-remove"
},
"upload": {
"service": "mdi:cloud-upload"
}
Expand Down
90 changes: 88 additions & 2 deletions homeassistant/components/onedrive/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import asyncio
from dataclasses import asdict
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import cast

from onedrive_personal_sdk.exceptions import OneDriveException
Expand All @@ -21,11 +21,12 @@
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, service

from .const import DOMAIN
from .const import CONF_DELETE_PERMANENTLY, DOMAIN
from .coordinator import OneDriveConfigEntry

CONF_CONFIG_ENTRY_ID = "config_entry_id"
CONF_DESTINATION_FOLDER = "destination_folder"
CONF_DESTINATION_PATH = "destination_path"

UPLOAD_SERVICE = "upload"
UPLOAD_SERVICE_SCHEMA = vol.Schema(
Expand All @@ -35,6 +36,17 @@
vol.Required(CONF_DESTINATION_FOLDER): cv.string,
}
)

DELETE_SERVICE = "delete"
DELETE_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONFIG_ENTRY_ID): cv.string,
vol.Required(CONF_DESTINATION_PATH): vol.All(
cv.ensure_list, vol.Length(min=1), [cv.string]
),
}
)
Comment thread
leodrivera marked this conversation as resolved.

CONTENT_SIZE_LIMIT = 250 * 1024 * 1024


Expand Down Expand Up @@ -78,6 +90,29 @@ def _read_file_contents(
return results


def _raise_invalid_destination_path(destination_path: str) -> None:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_destination_path",
translation_placeholders={"destination_path": destination_path},
)


def _validate_destination_path(destination_path: str) -> str:
"""Validate and normalize a remote destination path.

Returns the normalized path or raises HomeAssistantError.
"""
normalized = destination_path.strip("/")
if not normalized:
_raise_invalid_destination_path(destination_path)
parts = PurePosixPath(normalized).parts
for part in parts:
if part == ".." or ":" in part:
_raise_invalid_destination_path(destination_path)
return str(PurePosixPath(normalized))
Comment thread
leodrivera marked this conversation as resolved.


@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Register OneDrive services."""
Expand Down Expand Up @@ -124,6 +159,50 @@ async def async_handle_upload(call: ServiceCall) -> ServiceResponse:
return {"files": [asdict(item_result) for item_result in upload_results]}
return None

async def async_handle_delete(call: ServiceCall) -> None:
"""Delete one or more files from OneDrive."""
config_entry: OneDriveConfigEntry = service.async_get_config_entry(
hass, DOMAIN, call.data[CONF_CONFIG_ENTRY_ID]
)
client = config_entry.runtime_data.client
delete_permanently = config_entry.options.get(CONF_DELETE_PERMANENTLY, False)
Comment thread
leodrivera marked this conversation as resolved.
file_paths = [
_validate_destination_path(p)
for p in cast(list[str], call.data[CONF_DESTINATION_PATH])
]
Comment thread
leodrivera marked this conversation as resolved.

try:
approot_id = (await client.get_approot()).id
except OneDriveException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
) from err
Comment thread
leodrivera marked this conversation as resolved.

results = await asyncio.gather(
*[
client.delete_drive_item(
f"{approot_id}:/{file_path}:", delete_permanently
)
for file_path in file_paths
Comment thread
leodrivera marked this conversation as resolved.
],
return_exceptions=True,
)
failures: list[tuple[str, OneDriveException]] = []
for file_path, result in zip(file_paths, results, strict=True):
if isinstance(result, OneDriveException):
failures.append((file_path, result))
Comment thread
leodrivera marked this conversation as resolved.
Comment thread
leodrivera marked this conversation as resolved.
if failures:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="delete_error",
translation_placeholders={
"paths": ", ".join(f"`{path}`" for path, _ in failures)
},
) from ExceptionGroup(
"OneDrive delete errors", [err for _, err in failures]
)

hass.services.async_register(
DOMAIN,
UPLOAD_SERVICE,
Expand All @@ -132,3 +211,10 @@ async def async_handle_upload(call: ServiceCall) -> ServiceResponse:
supports_response=SupportsResponse.OPTIONAL,
description_placeholders={"example_image_path": "/config/www/image.jpg"},
)

hass.services.async_register(
DOMAIN,
DELETE_SERVICE,
async_handle_delete,
schema=DELETE_SERVICE_SCHEMA,
)
13 changes: 13 additions & 0 deletions homeassistant/components/onedrive/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@ upload:
required: true
selector:
text:

delete:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: onedrive
destination_path:
required: true
selector:
text:
multiple: true
26 changes: 25 additions & 1 deletion homeassistant/components/onedrive/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,15 @@
"authentication_failed": {
"message": "Authentication failed"
},
"connection_error": {
"message": "[%key:component::onedrive::config::abort::connection_error%]"
},
"create_folder_error": {
"message": "Failed to create folder: {message}"
},
"delete_error": {
"message": "Failed to delete from OneDrive: {paths}"
},
"failed_to_get_folder": {
"message": "Failed to get {folder} folder"
},
Expand All @@ -105,6 +111,9 @@
"filenames_do_not_exist": {
"message": "The following files do not exist: {filenames}"
},
"invalid_destination_path": {
"message": "Invalid destination path `{destination_path}`: must be non-empty, must not contain `:` or `..` path segments"
},
"no_access_to_path": {
"message": "Cannot read {filename}, no access to path; `allowlist_external_dirs` may need to be adjusted in `configuration.yaml`"
},
Expand Down Expand Up @@ -142,6 +151,21 @@
}
},
"services": {
"delete": {
"description": "Deletes one or more files from OneDrive.",
"fields": {
"config_entry_id": {
"description": "The config entry representing the OneDrive you want to delete from.",
"name": "Config entry ID"
},
"destination_path": {
"description": "One or more paths to files inside the OneDrive app folder (Apps/Home Assistant) to delete.",
"example": "[\"photos/snapshots/image.jpg\", \"photos/snapshots/image2.jpg\"]",
"name": "Destination paths"
}
},
"name": "Delete files"
},
Comment thread
leodrivera marked this conversation as resolved.
"upload": {
"description": "Uploads one or more files to OneDrive.",
"fields": {
Expand All @@ -150,7 +174,7 @@
"name": "Config entry ID"
},
"destination_folder": {
"description": "Folder inside the Home Assistant app folder (Apps/Home Assistant) you want to upload the files to. Will be created if it does not exist.",
"description": "Folder inside the OneDrive app folder (Apps/Home Assistant) you want to upload the files to. Will be created if it does not exist.",
"example": "photos/snapshots",
"name": "Destination folder"
},
Expand Down
Loading