mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Set timeout for remote calendar (#147024)
This commit is contained in:
parent
01b4a5ceed
commit
4a937d2452
12
homeassistant/components/remote_calendar/client.py
Normal file
12
homeassistant/components/remote_calendar/client.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""Specifies the parameter for the httpx download."""
|
||||
|
||||
from httpx import AsyncClient, Response, Timeout
|
||||
|
||||
|
||||
async def get_calendar(client: AsyncClient, url: str) -> Response:
|
||||
"""Make an HTTP GET request using Home Assistant's async HTTPX client with timeout."""
|
||||
return await client.get(
|
||||
url,
|
||||
follow_redirects=True,
|
||||
timeout=Timeout(5, read=30, write=5, pool=5),
|
||||
)
|
@ -4,13 +4,14 @@ from http import HTTPStatus
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from httpx import HTTPError, InvalidURL
|
||||
from httpx import HTTPError, InvalidURL, TimeoutException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .client import get_calendar
|
||||
from .const import CONF_CALENDAR_NAME, DOMAIN
|
||||
from .ics import InvalidIcsException, parse_calendar
|
||||
|
||||
@ -49,7 +50,7 @@ class RemoteCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
|
||||
client = get_async_client(self.hass)
|
||||
try:
|
||||
res = await client.get(user_input[CONF_URL], follow_redirects=True)
|
||||
res = await get_calendar(client, user_input[CONF_URL])
|
||||
if res.status_code == HTTPStatus.FORBIDDEN:
|
||||
errors["base"] = "forbidden"
|
||||
return self.async_show_form(
|
||||
@ -58,9 +59,14 @@ class RemoteCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
res.raise_for_status()
|
||||
except TimeoutException as err:
|
||||
errors["base"] = "timeout_connect"
|
||||
_LOGGER.debug(
|
||||
"A timeout error occurred: %s", str(err) or type(err).__name__
|
||||
)
|
||||
except (HTTPError, InvalidURL) as err:
|
||||
errors["base"] = "cannot_connect"
|
||||
_LOGGER.debug("An error occurred: %s", err)
|
||||
_LOGGER.debug("An error occurred: %s", str(err) or type(err).__name__)
|
||||
else:
|
||||
try:
|
||||
await parse_calendar(self.hass, res.text)
|
||||
|
@ -3,7 +3,7 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from httpx import HTTPError, InvalidURL
|
||||
from httpx import HTTPError, InvalidURL, TimeoutException
|
||||
from ical.calendar import Calendar
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .client import get_calendar
|
||||
from .const import DOMAIN
|
||||
from .ics import InvalidIcsException, parse_calendar
|
||||
|
||||
@ -36,7 +37,7 @@ class RemoteCalendarDataUpdateCoordinator(DataUpdateCoordinator[Calendar]):
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
name=f"{DOMAIN}_{config_entry.title}",
|
||||
update_interval=SCAN_INTERVAL,
|
||||
always_update=True,
|
||||
)
|
||||
@ -46,13 +47,19 @@ class RemoteCalendarDataUpdateCoordinator(DataUpdateCoordinator[Calendar]):
|
||||
async def _async_update_data(self) -> Calendar:
|
||||
"""Update data from the url."""
|
||||
try:
|
||||
res = await self._client.get(self._url, follow_redirects=True)
|
||||
res = await get_calendar(self._client, self._url)
|
||||
res.raise_for_status()
|
||||
except TimeoutException as err:
|
||||
_LOGGER.debug("%s: %s", self._url, str(err) or type(err).__name__)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="timeout",
|
||||
) from err
|
||||
except (HTTPError, InvalidURL) as err:
|
||||
_LOGGER.debug("%s: %s", self._url, str(err) or type(err).__name__)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unable_to_fetch",
|
||||
translation_placeholders={"err": str(err)},
|
||||
) from err
|
||||
try:
|
||||
self.ics = res.text
|
||||
|
@ -18,14 +18,18 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||
},
|
||||
"error": {
|
||||
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"forbidden": "The server understood the request but refuses to authorize it.",
|
||||
"invalid_ics_file": "There was a problem reading the calendar information. See the error log for additional details."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"timeout": {
|
||||
"message": "The connection timed out. See the debug log for additional details."
|
||||
},
|
||||
"unable_to_fetch": {
|
||||
"message": "Unable to fetch calendar data: {err}"
|
||||
"message": "Unable to fetch calendar data. See the debug log for additional details."
|
||||
},
|
||||
"unable_to_parse": {
|
||||
"message": "Unable to parse calendar data: {err}"
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Test the Remote Calendar config flow."""
|
||||
|
||||
from httpx import ConnectError, Response, UnsupportedProtocol
|
||||
from httpx import HTTPError, InvalidURL, Response, TimeoutException
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
@ -75,10 +75,11 @@ async def test_form_import_webcal(hass: HomeAssistant, ics_content: str) -> None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect"),
|
||||
("side_effect", "base_error"),
|
||||
[
|
||||
ConnectError("Connection failed"),
|
||||
UnsupportedProtocol("Unsupported protocol"),
|
||||
(TimeoutException("Connection timed out"), "timeout_connect"),
|
||||
(HTTPError("Connection failed"), "cannot_connect"),
|
||||
(InvalidURL("Unsupported protocol"), "cannot_connect"),
|
||||
],
|
||||
)
|
||||
@respx.mock
|
||||
@ -86,6 +87,7 @@ async def test_form_inavild_url(
|
||||
hass: HomeAssistant,
|
||||
side_effect: Exception,
|
||||
ics_content: str,
|
||||
base_error: str,
|
||||
) -> None:
|
||||
"""Test we get the import form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@ -102,7 +104,7 @@ async def test_form_inavild_url(
|
||||
},
|
||||
)
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
assert result2["errors"] == {"base": base_error}
|
||||
respx.get(CALENDER_URL).mock(
|
||||
return_value=Response(
|
||||
status_code=200,
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Tests for init platform of Remote Calendar."""
|
||||
|
||||
from httpx import ConnectError, Response, UnsupportedProtocol
|
||||
from httpx import HTTPError, InvalidURL, Response, TimeoutException
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
@ -56,8 +56,9 @@ async def test_raise_for_status(
|
||||
@pytest.mark.parametrize(
|
||||
"side_effect",
|
||||
[
|
||||
ConnectError("Connection failed"),
|
||||
UnsupportedProtocol("Unsupported protocol"),
|
||||
TimeoutException("Connection timed out"),
|
||||
HTTPError("Connection failed"),
|
||||
InvalidURL("Unsupported protocol"),
|
||||
ValueError("Invalid response"),
|
||||
],
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user