Skip to content

IDrive e2 backup provider#144910

Merged
zweckj merged 60 commits intohome-assistant:devfrom
patrickvorgers:idrive-e2-backup-provider
Feb 12, 2026
Merged

IDrive e2 backup provider#144910
zweckj merged 60 commits intohome-assistant:devfrom
patrickvorgers:idrive-e2-backup-provider

Conversation

@patrickvorgers
Copy link
Copy Markdown
Contributor

@patrickvorgers patrickvorgers commented May 14, 2025

Proposed change

Based on the feedback earlier that S3 backup providers should be specific. I started developing the IDrive e2 backup provider based on the AWS S3 backup integration. This implementation for instance uses a specific config_flow for IDrive e2 which automatically determines the endpoint based on the provided credentials and provides the user a dropdown selection box to select the bucket. The iDrive E2 backup provider has been running for the last month on my production HA machine with no issues.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request: #39042
  • Link to developer documentation pull request:
  • Link to frontend pull request:

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Comment on lines +38 to +44
# NOTE: We use boto3 instead of aiobotocore.AioSession because AioSession
# causes blocking operations inside the event loop.
# Two examples include:
# - os.listdir
# - ssl.SSLContext.load_verify_locations
# These lead to 'Detected blocking call` warnings. Using boto3 inside
# async_add_executor_job avoids these issues by running blocking code off the event loop.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't other integrations do the same?

Copy link
Copy Markdown
Contributor Author

@patrickvorgers patrickvorgers Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late response but I was on holiday 😄

The AWS S3 implementation uses the aiobotocore.AioSession and because I reused that code I ran into this issue. I will raise an issue for the AWS S3 integration describing the issue, close?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there are also ways to load something async in the executor, but I am not 100% sure on that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know either, my suggestion would be to leave it like this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with all the similarities (and the open discussion about how to avoid it), I don't think we should use different libraries.
Something like

        client = hass.async_add_executor_job(
            session.create_client(
                service_name="s3",
                endpoint_url=data.get(CONF_ENDPOINT_URL),
                aws_secret_access_key=data[CONF_SECRET_ACCESS_KEY],
                aws_access_key_id=data[CONF_ACCESS_KEY_ID],
            )
        )
        await client.__aenter__() # pylint: disable-next=unnecessary-dunder-call

should get rid of the blocking call warning (and ideally it gets fixed in the library afterwards)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, glad to see there’s already an issue and that it’s getting some attention again, very happy you jumped in there.

I totally understand the concern about ending up with two different S3 approaches, and I’m aligned on aiming for a single, non-blocking pattern (ideally via aiobotocore once the blocking bits are sorted out).

From my side I’m happy to:

  • keep an eye on the aiobotocore issue and its timeline, and
  • prepare a switch for this integration to the agreed aiobotocore pattern if it looks like the fix will land soon.

If you and the team feel it makes more sense to hold this PR until that’s clearer, I’m OK with that; if not, I’m also fine to get this in now and follow up with a switch once the upstream situation is resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated this PR to use aiobotocore, so it aligns with the existing aws_s3 backup integration approach in Core.

For next steps, I’m happy to follow whatever the Home Assistant maintainers prefer. We can either merge this as-is, or hold off until there’s more clarity on the upstream preload proposal (aio-libs/aiobotocore#1462). That PR still needs review (@bdraco / @thehesiod / @webknjaz).

Please let me know how you’d like to proceed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really quallified to review the aiobotocore use — I'm mostly enabling people to maintain it (and many other things under @aio-libs), but not intimately familiar with it specifically. Maybe, @bdraco would have a better context to look into it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickvorgers we're waiting on feedback on aio-libs/aiobotocore#1451 to see which strategy people prefer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thehesiod, I saw that the "warmup" functionality is now included in the latest release and that partly resolves the issue (see Unblocking creation of SSL context. I have tested that with the idrive_e2 integration

The preference is to have aiobotocore completely async but as far as I understand it, that is not possible in the shortterm. The possibility for specifying an executor as described in your PR, would that enable us to resolve the Unblocking creation of SSL context issue and how would that work?

Maybe @zweckj can also help with the test cases as suggested by @bdraco.

async def async_setup_entry(hass: HomeAssistant, entry: IDriveE2ConfigEntry) -> bool:
"""Set up IDrive e2 from a config entry."""

data = cast(dict, entry.data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we cast this?

Copy link
Copy Markdown
Contributor Author

@patrickvorgers patrickvorgers Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is there for stricter type checker hints. If needed I can also for instance directly use entry.data[CONF_ENDPOINT_URL]. The orignal AWS S3 integration also uses this construct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is fine to use without casting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the cast

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

Comment on lines +74 to +79
except ParamValidationError as err:
if "Invalid bucket name" in str(err):
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="invalid_bucket_name",
) from err
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if this is not the case, what should we do then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically we could request the user to recreate the bucket. During configuration of the integration the bucket existed (user selected the bucket in the configflow) but now it doesn't anymore.
So instead of the current message: "Invalid bucket name" we could change the message to: "Bucket %s doesn't exist, please recreate the bucket."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit


async def async_unload_entry(hass: HomeAssistant, entry: IDriveE2ConfigEntry) -> bool:
"""Unload a config entry."""
client: boto3.client = entry.runtime_data
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to retype the runtime data, as that is already part of the typeslot in IDriveE2ConfigEntry

Copy link
Copy Markdown
Contributor Author

@patrickvorgers patrickvorgers Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it into: client = entry.runtime_data

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

Comment on lines +44 to +63
def _build_user_step_schema(
access_key: str | None = None, secret_key: str | None = None
) -> vol.Schema:
"""Build the user step schema."""
if access_key is None or secret_key is None:
return STEP_USER_DATA_SCHEMA

# Return a schema with prefilled values
return vol.Schema(
{
vol.Required(
CONF_ACCESS_KEY_ID,
default=access_key,
): cv.string,
vol.Required(
CONF_SECRET_ACCESS_KEY,
default=secret_key,
): cv.string,
}
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a function for this in the ConfigFlow, something like self.add_suggested_values_to_schema or something along those lines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

Comment on lines +108 to +115
resp = await http.post(
GET_REGION_ENDPOINT_URL,
json={"access_key": user_input[CONF_ACCESS_KEY_ID]},
)
resp.raise_for_status()
payload = await resp.json()
if payload.get("resp_code") == GET_REGION_INVALID_CREDENTIALS_CODE:
_raise_invalid_credentials(resp)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is device or service specific code and should exist in a library hosted on Pypi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

@home-assistant home-assistant Bot marked this pull request as draft July 15, 2025 20:34
@home-assistant
Copy link
Copy Markdown
Contributor

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@patrickvorgers patrickvorgers marked this pull request as ready for review August 21, 2025 18:56
@home-assistant home-assistant Bot requested a review from joostlek August 21, 2025 18:56
Comment on lines +38 to +44
# NOTE: We use boto3 instead of aiobotocore.AioSession because AioSession
# causes blocking operations inside the event loop.
# Two examples include:
# - os.listdir
# - ssl.SSLContext.load_verify_locations
# These lead to 'Detected blocking call` warnings. Using boto3 inside
# async_add_executor_job avoids these issues by running blocking code off the event loop.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there are also ways to load something async in the executor, but I am not 100% sure on that

async def async_setup_entry(hass: HomeAssistant, entry: IDriveE2ConfigEntry) -> bool:
"""Set up IDrive e2 from a config entry."""

data = cast(dict, entry.data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is fine to use without casting

Comment on lines +297 to +307
# Delete both the backup file and its metadata file
await self._hass.async_add_executor_job(
functools.partial(
self._client.delete_object, Bucket=self._bucket, Key=tar_filename
)
)
await self._hass.async_add_executor_job(
functools.partial(
self._client.delete_object, Bucket=self._bucket, Key=metadata_filename
)
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from the executor to the event loop is expensive, so ideally we don't switch often, can we group these?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes can be grouped into one delete_objects call

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit


return self.async_show_form(
step_id="user",
data_schema=self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, {}),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have to call to add suggested values to schema if the dict we use to fill it with is empty

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, is an unnecessary call

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

Comment on lines +129 to +141
try:
# Information should be available from the previous step
assert self._endpoint_url is not None
assert self._access_key is not None
assert self._secret_key is not None
# List buckets using the provided credentials
buckets = await self.hass.async_add_executor_job(
_list_buckets,
self._endpoint_url,
self._access_key,
self._secret_key,
)
except ClientError:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only have things in the try block that can raise

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the assertion outside of the try-block

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

Comment on lines +149 to +150
# Go back to the user step if there are errors getting buckets
# Prefill the access key and secret key fields with the current values
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to have the user step responsible for fetching the buckets?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d suggest keeping the _list_buckets call in the bucket step rather than the user step. Each step in a config flow should manage its own data and initialization: the user step is responsible for collecting credentials, while the bucket step is responsible for selecting a bucket. Having the bucket step fetch the list keeps responsibilities clear, avoids mixing concerns, and makes the flow easier to maintain.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I would argue that validating the input is a part of the concern of the user step. I don't expect the bucket list to change at this point so we can just fetch it once and display it here right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully I interpreted your remark/question correctly.

You are correct that the bucket list itself doesn't change in between steps. The would be one edge case where the user deletes the bucket while inbetween steps.

The bucket fetch is done based on the provided credentials. In theory it is possible that the provided access-key is correct but no buckets are assigned to this access-key. While creating the access-key the user is forced to assign one or more buckets to this access-key but is free to remove that bucket afterwards. In that case you would have an access-key with no buckets.

You cannot add buckets to existing access-keys so it is usefull for the user to be presented again with the credential screen so he/she can provide new access-key information, when there are no buckets associated with the provided access-key. In case of a hiccup in the _list_buckets call, the user can use the existing credentials and try again.

"bucket": "Bucket name"
},
"data_description": {
"bucket": "Bucket must be writable by the provided credentials."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data description should explain the data that is needed, not give extra hints on how the user should set up the integration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree and changed the description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was fixed in commit

@home-assistant home-assistant Bot marked this pull request as draft September 12, 2025 09:44
@patrickvorgers patrickvorgers marked this pull request as ready for review September 14, 2025 14:06
@patrickvorgers patrickvorgers marked this pull request as ready for review February 8, 2026 22:12
@home-assistant home-assistant Bot requested a review from zweckj February 8, 2026 22:12
@emontnemery emontnemery reopened this Feb 11, 2026
@emontnemery
Copy link
Copy Markdown
Contributor

(Close + reopen to restart CI against current tip of dev)

Comment on lines +47 to +48
cm: Any | None = None
client: S3Client | None = None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these | None I would consider that we'd know the type of cm and that we don't have to type it Any

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, cm is gone now so no Any type needed. client is still | None because it’s created inside the try and may not exist if create_client().__aenter__() fails, It is used in the exception handling in the _async_safe_client_close.

Fixed in this commit

Comment on lines +47 to +51
with patch(
"aiobotocore.session.AioSession.create_client",
autospec=True,
return_value=AsyncMock(),
) as create_client:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we patch the library where we use it, so homeassistant.components.idrive_e2..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I’ll patch AioSession.create_client at the Home Assistant integration import path (homeassistant.components.idrive_e2...) instead of the library path to match where it’s used.

Fixed in this commit

Comment on lines +31 to +39
@pytest.fixture
def mock_aiohttp_session() -> Generator[AsyncMock]:
"""Patch async_get_clientsession to return a mocked aiohttp session."""
mock_session = AsyncMock()
with patch(
"homeassistant.components.idrive_e2.config_flow.async_get_clientsession",
return_value=mock_session,
):
yield mock_session
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have to mock this one really

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and has been removed.

Fixed in this commit

Comment on lines +67 to +69
mock_client.list_buckets.return_value = {
"Buckets": [{"Name": USER_INPUT[CONF_BUCKET]}]
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this the default in the mock? You'll likely use it in more tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the default list_buckets return value into the shared mock_client fixture and drop the repeated assignments in the tests.

Fixed in this commit

Comment on lines +136 to +138
mock_client.list_buckets.return_value = {
"Buckets": [{"Name": USER_INPUT[CONF_BUCKET]}]
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case you do followup on the last comment, if this is set as default, you don't have to set it again when removing a side_effect

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed assignment because of previous fix.

Fixed in this commit

Comment on lines +326 to +333
MockConfigEntry(
domain=DOMAIN,
data={
CONF_BUCKET: USER_INPUT[CONF_BUCKET],
CONF_ENDPOINT_URL: USER_INPUT[CONF_ENDPOINT_URL],
},
unique_id="existing",
).add_to_hass(hass)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use mock_config_entry fixture

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the code and now use the mock_config_entry as a template because MockConfigEntry.data is immutable.

Fixed in this commit

Comment thread tests/components/idrive_e2/test_init.py Outdated
Comment on lines +38 to +40
# If close() masks the original error, this would raise RuntimeError instead
with pytest.raises(ConfigEntryError):
await async_setup_entry(hass, mock_config_entry)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we shouldn't touch async_setup_entry directly, instead we should use await hass.config_entries.async_setup() and then we can assert that the mock_config_entry.state isSETUP_ERROR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched the test to set up the entry via hass.config_entries.async_setup().

Fixed in this commit

Comment on lines +81 to +84
with patch(
"aiobotocore.session.AioSession.create_client",
side_effect=exception,
):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patch where we use it, and preferrably use the shared mock

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted patch where used

Fixed in this commit

Comment thread tests/components/idrive_e2/test_init.py Outdated
Comment on lines +89 to +114
async def test_setup_entry_head_bucket_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: AsyncMock,
) -> None:
"""Test setup_entry error when calling head_bucket."""
mock_client.head_bucket.side_effect = ClientError(
error_response={"Error": {"Code": "InvalidAccessKeyId"}},
operation_name="head_bucket",
)
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR


async def test_setup_entry_head_bucket_not_found_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: AsyncMock,
) -> None:
"""Test setup_entry error when head_bucket returns: Not Found."""
mock_client.head_bucket.side_effect = ClientError(
error_response={"Error": {"Code": "404", "Message": "Not Found"}},
operation_name="head_bucket",
)
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be parametrized

Copy link
Copy Markdown
Contributor Author

@patrickvorgers patrickvorgers Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, these head_bucket error tests need to be parametrized.

Fixed in this commit

Copy link
Copy Markdown
Member

@zweckj zweckj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to also get the improved buffer handling to the other S3 integrations if you don’t mind

@zweckj
Copy link
Copy Markdown
Member

zweckj commented Feb 12, 2026

Please DM your Discord user, so we can add you to the appropriate channels, thanks!

@zweckj zweckj merged commit d930755 into home-assistant:dev Feb 12, 2026
46 checks passed
@patrickvorgers
Copy link
Copy Markdown
Contributor Author

It'd be nice to also get the improved buffer handling to the other S3 integrations if you don’t mind

Created PR #162955 for S3
Created PR #162958 for Cloudfare R2

@github-actions github-actions Bot locked and limited conversation to collaborators Feb 14, 2026
@patrickvorgers patrickvorgers deleted the idrive-e2-backup-provider branch March 8, 2026 21:26
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants