Skip to content

Commit 41ab7b3

Browse files
Thomas55555frenck
authored andcommitted
Set timeout for remote calendar (home-assistant#147024)
1 parent 8334a03 commit 41ab7b3

File tree

6 files changed

+48
-16
lines changed

6 files changed

+48
-16
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Specifies the parameter for the httpx download."""
2+
3+
from httpx import AsyncClient, Response, Timeout
4+
5+
6+
async def get_calendar(client: AsyncClient, url: str) -> Response:
7+
"""Make an HTTP GET request using Home Assistant's async HTTPX client with timeout."""
8+
return await client.get(
9+
url,
10+
follow_redirects=True,
11+
timeout=Timeout(5, read=30, write=5, pool=5),
12+
)

homeassistant/components/remote_calendar/config_flow.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import logging
55
from typing import Any
66

7-
from httpx import HTTPError, InvalidURL
7+
from httpx import HTTPError, InvalidURL, TimeoutException
88
import voluptuous as vol
99

1010
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
1111
from homeassistant.const import CONF_URL
1212
from homeassistant.helpers.httpx_client import get_async_client
1313

14+
from .client import get_calendar
1415
from .const import CONF_CALENDAR_NAME, DOMAIN
1516
from .ics import InvalidIcsException, parse_calendar
1617

@@ -49,7 +50,7 @@ async def async_step_user(
4950
self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
5051
client = get_async_client(self.hass)
5152
try:
52-
res = await client.get(user_input[CONF_URL], follow_redirects=True)
53+
res = await get_calendar(client, user_input[CONF_URL])
5354
if res.status_code == HTTPStatus.FORBIDDEN:
5455
errors["base"] = "forbidden"
5556
return self.async_show_form(
@@ -58,9 +59,14 @@ async def async_step_user(
5859
errors=errors,
5960
)
6061
res.raise_for_status()
62+
except TimeoutException as err:
63+
errors["base"] = "timeout_connect"
64+
_LOGGER.debug(
65+
"A timeout error occurred: %s", str(err) or type(err).__name__
66+
)
6167
except (HTTPError, InvalidURL) as err:
6268
errors["base"] = "cannot_connect"
63-
_LOGGER.debug("An error occurred: %s", err)
69+
_LOGGER.debug("An error occurred: %s", str(err) or type(err).__name__)
6470
else:
6571
try:
6672
await parse_calendar(self.hass, res.text)

homeassistant/components/remote_calendar/coordinator.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import timedelta
44
import logging
55

6-
from httpx import HTTPError, InvalidURL
6+
from httpx import HTTPError, InvalidURL, TimeoutException
77
from ical.calendar import Calendar
88

99
from homeassistant.config_entries import ConfigEntry
@@ -12,6 +12,7 @@
1212
from homeassistant.helpers.httpx_client import get_async_client
1313
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1414

15+
from .client import get_calendar
1516
from .const import DOMAIN
1617
from .ics import InvalidIcsException, parse_calendar
1718

@@ -36,7 +37,7 @@ def __init__(
3637
super().__init__(
3738
hass,
3839
_LOGGER,
39-
name=DOMAIN,
40+
name=f"{DOMAIN}_{config_entry.title}",
4041
update_interval=SCAN_INTERVAL,
4142
always_update=True,
4243
)
@@ -46,13 +47,19 @@ def __init__(
4647
async def _async_update_data(self) -> Calendar:
4748
"""Update data from the url."""
4849
try:
49-
res = await self._client.get(self._url, follow_redirects=True)
50+
res = await get_calendar(self._client, self._url)
5051
res.raise_for_status()
52+
except TimeoutException as err:
53+
_LOGGER.debug("%s: %s", self._url, str(err) or type(err).__name__)
54+
raise UpdateFailed(
55+
translation_domain=DOMAIN,
56+
translation_key="timeout",
57+
) from err
5158
except (HTTPError, InvalidURL) as err:
59+
_LOGGER.debug("%s: %s", self._url, str(err) or type(err).__name__)
5260
raise UpdateFailed(
5361
translation_domain=DOMAIN,
5462
translation_key="unable_to_fetch",
55-
translation_placeholders={"err": str(err)},
5663
) from err
5764
try:
5865
self.ics = res.text

homeassistant/components/remote_calendar/strings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
1919
},
2020
"error": {
21+
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
2122
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
2223
"forbidden": "The server understood the request but refuses to authorize it.",
2324
"invalid_ics_file": "There was a problem reading the calendar information. See the error log for additional details."
2425
}
2526
},
2627
"exceptions": {
28+
"timeout": {
29+
"message": "The connection timed out. See the debug log for additional details."
30+
},
2731
"unable_to_fetch": {
28-
"message": "Unable to fetch calendar data: {err}"
32+
"message": "Unable to fetch calendar data. See the debug log for additional details."
2933
},
3034
"unable_to_parse": {
3135
"message": "Unable to parse calendar data: {err}"

tests/components/remote_calendar/test_config_flow.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Test the Remote Calendar config flow."""
22

3-
from httpx import ConnectError, Response, UnsupportedProtocol
3+
from httpx import HTTPError, InvalidURL, Response, TimeoutException
44
import pytest
55
import respx
66

@@ -75,17 +75,19 @@ async def test_form_import_webcal(hass: HomeAssistant, ics_content: str) -> None
7575

7676

7777
@pytest.mark.parametrize(
78-
("side_effect"),
78+
("side_effect", "base_error"),
7979
[
80-
ConnectError("Connection failed"),
81-
UnsupportedProtocol("Unsupported protocol"),
80+
(TimeoutException("Connection timed out"), "timeout_connect"),
81+
(HTTPError("Connection failed"), "cannot_connect"),
82+
(InvalidURL("Unsupported protocol"), "cannot_connect"),
8283
],
8384
)
8485
@respx.mock
8586
async def test_form_inavild_url(
8687
hass: HomeAssistant,
8788
side_effect: Exception,
8889
ics_content: str,
90+
base_error: str,
8991
) -> None:
9092
"""Test we get the import form."""
9193
result = await hass.config_entries.flow.async_init(
@@ -102,7 +104,7 @@ async def test_form_inavild_url(
102104
},
103105
)
104106
assert result2["type"] is FlowResultType.FORM
105-
assert result2["errors"] == {"base": "cannot_connect"}
107+
assert result2["errors"] == {"base": base_error}
106108
respx.get(CALENDER_URL).mock(
107109
return_value=Response(
108110
status_code=200,

tests/components/remote_calendar/test_init.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for init platform of Remote Calendar."""
22

3-
from httpx import ConnectError, Response, UnsupportedProtocol
3+
from httpx import HTTPError, InvalidURL, Response, TimeoutException
44
import pytest
55
import respx
66

@@ -56,8 +56,9 @@ async def test_raise_for_status(
5656
@pytest.mark.parametrize(
5757
"side_effect",
5858
[
59-
ConnectError("Connection failed"),
60-
UnsupportedProtocol("Unsupported protocol"),
59+
TimeoutException("Connection timed out"),
60+
HTTPError("Connection failed"),
61+
InvalidURL("Unsupported protocol"),
6162
ValueError("Invalid response"),
6263
],
6364
)

0 commit comments

Comments
 (0)