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