Bump pytrafikverket to 0.3.1 (#92425)

* Bump pytrafikverket 0.3.0

* 0.3.1

* mypy

* Fix exceptions
This commit is contained in:
G Johansson 2023-05-05 21:19:16 +02:00 committed by GitHub
parent 85dcd4007c
commit 82b4368d1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 154 additions and 106 deletions

View File

@ -5,6 +5,7 @@ from collections.abc import Mapping
from typing import Any
from pytrafikverket import TrafikverketFerry
from pytrafikverket.exceptions import InvalidAuthentication, NoFerryFound
import voluptuous as vol
from homeassistant import config_entries
@ -16,9 +17,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN
from .util import create_unique_id
ERROR_INVALID_AUTH = "Source: Security, message: Invalid authentication"
ERROR_INVALID_ROUTE = "No FerryAnnouncement found"
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): selector.TextSelector(
@ -81,13 +79,12 @@ class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await self.validate_input(
api_key, self.entry.data[CONF_FROM], self.entry.data[CONF_TO]
)
except ValueError as err:
if str(err) == ERROR_INVALID_AUTH:
errors["base"] = "invalid_auth"
elif str(err) == ERROR_INVALID_ROUTE:
errors["base"] = "invalid_route"
else:
errors["base"] = "cannot_connect"
except InvalidAuthentication:
errors["base"] = "invalid_auth"
except NoFerryFound:
errors["base"] = "invalid_route"
except Exception: # pylint: disable=broad-exception-caught
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
@ -126,13 +123,12 @@ class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
await self.validate_input(api_key, ferry_from, ferry_to)
except ValueError as err:
if str(err) == ERROR_INVALID_AUTH:
errors["base"] = "invalid_auth"
elif str(err) == ERROR_INVALID_ROUTE:
errors["base"] = "invalid_route"
else:
errors["base"] = "cannot_connect"
except InvalidAuthentication:
errors["base"] = "invalid_auth"
except NoFerryFound:
errors["base"] = "invalid_route"
except Exception: # pylint: disable=broad-exception-caught
errors["base"] = "cannot_connect"
else:
if not errors:
unique_id = create_unique_id(

View File

@ -6,11 +6,13 @@ import logging
from typing import Any
from pytrafikverket import TrafikverketFerry
from pytrafikverket.exceptions import InvalidAuthentication, NoFerryFound
from pytrafikverket.trafikverket_ferry import FerryStop
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_WEEKDAY, WEEKDAYS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt
@ -82,10 +84,12 @@ class TVDataUpdateCoordinator(DataUpdateCoordinator):
] = await self._ferry_api.async_get_next_ferry_stops(
self._from, self._to, when, 3
)
except ValueError as error:
except NoFerryFound as error:
raise UpdateFailed(
f"Departure {when} encountered a problem: {error}"
) from error
except InvalidAuthentication as error:
raise ConfigEntryAuthFailed(error) from error
states = {
"departure_time": routedata[0].departure_time,

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/trafikverket_ferry",
"iot_class": "cloud_polling",
"loggers": ["pytrafikverket"],
"requirements": ["pytrafikverket==0.2.3"]
"requirements": ["pytrafikverket==0.3.1"]
}

View File

@ -2,6 +2,11 @@
from __future__ import annotations
from pytrafikverket import TrafikverketTrain
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleTrainStationsFound,
NoTrainStationFound,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
@ -21,9 +26,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
to_station = await train_api.async_get_train_station(entry.data[CONF_TO])
from_station = await train_api.async_get_train_station(entry.data[CONF_FROM])
except ValueError as error:
if "Invalid authentication" in error.args[0]:
raise ConfigEntryAuthFailed from error
except InvalidAuthentication as error:
raise ConfigEntryAuthFailed from error
except (NoTrainStationFound, MultipleTrainStationsFound) as error:
raise ConfigEntryNotReady(
f"Problem when trying station {entry.data[CONF_FROM]} to"
f" {entry.data[CONF_TO]}. Error: {error} "

View File

@ -5,6 +5,11 @@ from collections.abc import Mapping
from typing import Any
from pytrafikverket import TrafikverketTrain
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleTrainStationsFound,
NoTrainStationFound,
)
import voluptuous as vol
from homeassistant import config_entries
@ -23,10 +28,6 @@ import homeassistant.util.dt as dt_util
from .const import CONF_FROM, CONF_TIME, CONF_TO, DOMAIN
from .util import create_unique_id
ERROR_INVALID_AUTH = "Source: Security, message: Invalid authentication"
ERROR_INVALID_STATION = "Could not find a station with the specified name"
ERROR_MULTIPLE_STATION = "Found multiple stations with the specified name"
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): TextSelector(),
@ -86,15 +87,14 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await self.validate_input(
api_key, self.entry.data[CONF_FROM], self.entry.data[CONF_TO]
)
except ValueError as err:
if str(err) == ERROR_INVALID_AUTH:
errors["base"] = "invalid_auth"
elif str(err) == ERROR_INVALID_STATION:
errors["base"] = "invalid_station"
elif str(err) == ERROR_MULTIPLE_STATION:
errors["base"] = "more_stations"
else:
errors["base"] = "cannot_connect"
except InvalidAuthentication:
errors["base"] = "invalid_auth"
except NoTrainStationFound:
errors["base"] = "invalid_station"
except MultipleTrainStationsFound:
errors["base"] = "more_stations"
except Exception: # pylint: disable=broad-exception-caught
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
@ -131,15 +131,14 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
await self.validate_input(api_key, train_from, train_to)
except ValueError as err:
if str(err) == ERROR_INVALID_AUTH:
errors["base"] = "invalid_auth"
elif str(err) == ERROR_INVALID_STATION:
errors["base"] = "invalid_station"
elif str(err) == ERROR_MULTIPLE_STATION:
errors["base"] = "more_stations"
else:
errors["base"] = "cannot_connect"
except InvalidAuthentication:
errors["base"] = "invalid_auth"
except NoTrainStationFound:
errors["base"] = "invalid_station"
except MultipleTrainStationsFound:
errors["base"] = "more_stations"
except Exception: # pylint: disable=broad-exception-caught
errors["base"] = "cannot_connect"
else:
if train_time:
if bool(dt_util.parse_time(train_time) is None):

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/trafikverket_train",
"iot_class": "cloud_polling",
"loggers": ["pytrafikverket"],
"requirements": ["pytrafikverket==0.2.3"]
"requirements": ["pytrafikverket==0.3.1"]
}

View File

@ -3,9 +3,13 @@ from __future__ import annotations
from datetime import date, datetime, time, timedelta
import logging
from typing import Any
from typing import TYPE_CHECKING, Any
from pytrafikverket import TrafikverketTrain
from pytrafikverket.exceptions import (
MultipleTrainAnnouncementFound,
NoTrainAnnouncementFound,
)
from pytrafikverket.trafikverket_train import StationInfo, TrainStop
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
@ -119,6 +123,8 @@ class TrainSensor(SensorEntity):
name=name,
configuration_url="https://api.trafikinfo.trafikverket.se/",
)
if TYPE_CHECKING:
assert from_station.name and to_station.name
self._attr_unique_id = create_unique_id(
from_station.name, to_station.name, departuretime, weekday
)
@ -134,6 +140,7 @@ class TrainSensor(SensorEntity):
)
try:
if self._time:
_LOGGER.debug("%s, %s, %s", self._from_station, self._to_station, when)
_state = await self._train_api.async_get_train_stop(
self._from_station, self._to_station, when
)
@ -141,7 +148,7 @@ class TrainSensor(SensorEntity):
_state = await self._train_api.async_get_next_train_stop(
self._from_station, self._to_station, when
)
except ValueError as error:
except (NoTrainAnnouncementFound, MultipleTrainAnnouncementFound) as error:
_LOGGER.error("Departure %s encountered a problem: %s", when, error)
if not _state:
@ -153,6 +160,8 @@ class TrainSensor(SensorEntity):
self._attr_available = True
# The original datetime doesn't provide a timezone so therefore attaching it here.
if TYPE_CHECKING:
assert _state.advertised_time_at_location
self._attr_native_value = dt.as_utc(_state.advertised_time_at_location)
if _state.time_at_location:
self._attr_native_value = dt.as_utc(_state.time_at_location)
@ -165,7 +174,7 @@ class TrainSensor(SensorEntity):
"""Return extra state attributes."""
attributes: dict[str, Any] = {
ATTR_DEPARTURE_STATE: state.get_state().name,
ATTR_DEPARTURE_STATE: state.get_state().value,
ATTR_CANCELED: state.canceled,
ATTR_DELAY_TIME: None,
ATTR_PLANNED_TIME: None,

View File

@ -1,6 +1,11 @@
"""Adds config flow for Trafikverket Weather integration."""
from __future__ import annotations
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleWeatherStationsFound,
NoWeatherStationFound,
)
from pytrafikverket.trafikverket_weather import TrafikverketWeather
import voluptuous as vol
@ -20,15 +25,11 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
entry: config_entries.ConfigEntry
async def validate_input(self, sensor_api: str, station: str) -> str:
async def validate_input(self, sensor_api: str, station: str) -> None:
"""Validate input from user input."""
web_session = async_get_clientsession(self.hass)
weather_api = TrafikverketWeather(web_session, sensor_api)
try:
await weather_api.async_get_weather(station)
except ValueError as err:
return str(err)
return "connected"
await weather_api.async_get_weather(station)
async def async_step_user(
self, user_input: dict[str, str] | None = None
@ -41,8 +42,17 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
api_key = user_input[CONF_API_KEY]
station = user_input[CONF_STATION]
validate = await self.validate_input(api_key, station)
if validate == "connected":
try:
await self.validate_input(api_key, station)
except InvalidAuthentication:
errors["base"] = "invalid_auth"
except NoWeatherStationFound:
errors["base"] = "invalid_station"
except MultipleWeatherStationsFound:
errors["base"] = "more_stations"
except Exception: # pylint: disable=broad-exception-caught
errors["base"] = "cannot_connect"
else:
return self.async_create_entry(
title=name,
data={
@ -50,14 +60,6 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONF_STATION: station,
},
)
if validate == "Source: Security, message: Invalid authentication":
errors["base"] = "invalid_auth"
elif validate == "Could not find a weather station with the specified name":
errors["base"] = "invalid_station"
elif validate == "Found multiple weather stations with the specified name":
errors["base"] = "more_stations"
else:
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user",

View File

@ -4,11 +4,17 @@ from __future__ import annotations
from datetime import timedelta
import logging
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleWeatherStationsFound,
NoWeatherStationFound,
)
from pytrafikverket.trafikverket_weather import TrafikverketWeather, WeatherStationInfo
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -38,6 +44,8 @@ class TVDataUpdateCoordinator(DataUpdateCoordinator[WeatherStationInfo]):
"""Fetch data from Trafikverket."""
try:
weatherdata = await self._weather_api.async_get_weather(self._station)
except ValueError as error:
except InvalidAuthentication as error:
raise ConfigEntryAuthFailed from error
except (NoWeatherStationFound, MultipleWeatherStationsFound) as error:
raise UpdateFailed from error
return weatherdata

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/trafikverket_weatherstation",
"iot_class": "cloud_polling",
"loggers": ["pytrafikverket"],
"requirements": ["pytrafikverket==0.2.3"]
"requirements": ["pytrafikverket==0.3.1"]
}

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -190,7 +191,9 @@ class TrafikverketWeatherStation(
def native_value(self) -> StateType | datetime:
"""Return state of sensor."""
if self.entity_description.api_key == "measure_time":
return _to_datetime(self.coordinator.data.measure_time)
if TYPE_CHECKING:
assert self.coordinator.data.measure_time
return self.coordinator.data.measure_time
state: StateType = getattr(
self.coordinator.data, self.entity_description.api_key
@ -204,4 +207,6 @@ class TrafikverketWeatherStation(
@property
def available(self) -> bool:
"""Return if entity is available."""
if TYPE_CHECKING:
assert self.coordinator.data.active
return self.coordinator.data.active and super().available

View File

@ -2146,7 +2146,7 @@ pytradfri[async]==9.0.1
# homeassistant.components.trafikverket_ferry
# homeassistant.components.trafikverket_train
# homeassistant.components.trafikverket_weatherstation
pytrafikverket==0.2.3
pytrafikverket==0.3.1
# homeassistant.components.usb
pyudev==0.23.2

View File

@ -1551,7 +1551,7 @@ pytradfri[async]==9.0.1
# homeassistant.components.trafikverket_ferry
# homeassistant.components.trafikverket_train
# homeassistant.components.trafikverket_weatherstation
pytrafikverket==0.2.3
pytrafikverket==0.3.1
# homeassistant.components.usb
pyudev==0.23.2

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from unittest.mock import patch
import pytest
from pytrafikverket.exceptions import InvalidAuthentication, NoFerryFound
from homeassistant import config_entries
from homeassistant.components.trafikverket_ferry.const import (
@ -63,24 +64,24 @@ async def test_form(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("error_message", "base_error"),
("side_effect", "base_error"),
[
(
"Source: Security, message: Invalid authentication",
InvalidAuthentication,
"invalid_auth",
),
(
"No FerryAnnouncement found",
NoFerryFound,
"invalid_route",
),
(
"Unknown",
Exception,
"cannot_connect",
),
],
)
async def test_flow_fails(
hass: HomeAssistant, error_message: str, base_error: str
hass: HomeAssistant, side_effect: str, base_error: str
) -> None:
"""Test config flow errors."""
result4 = await hass.config_entries.flow.async_init(
@ -92,7 +93,7 @@ async def test_flow_fails(
with patch(
"homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop",
side_effect=ValueError(error_message),
side_effect=side_effect(),
):
result4 = await hass.config_entries.flow.async_configure(
result4["flow_id"],
@ -161,24 +162,24 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("sideeffect", "p_error"),
("side_effect", "p_error"),
[
(
ValueError("Source: Security, message: Invalid authentication"),
InvalidAuthentication,
"invalid_auth",
),
(
ValueError("No FerryAnnouncement found"),
NoFerryFound,
"invalid_route",
),
(
ValueError("Unknown"),
Exception,
"cannot_connect",
),
],
)
async def test_reauth_flow_error(
hass: HomeAssistant, sideeffect: Exception, p_error: str
hass: HomeAssistant, side_effect: Exception, p_error: str
) -> None:
"""Test a reauthentication flow with error."""
entry = MockConfigEntry(
@ -207,7 +208,7 @@ async def test_reauth_flow_error(
with patch(
"homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop",
side_effect=sideeffect,
side_effect=side_effect(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],

View File

@ -2,10 +2,11 @@
from __future__ import annotations
from datetime import date, datetime, timedelta
from unittest.mock import AsyncMock, patch
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from pytrafikverket.exceptions import InvalidAuthentication, NoFerryFound
from pytrafikverket.trafikverket_ferry import FerryStop
from homeassistant.components.trafikverket_ferry.const import DOMAIN
@ -22,7 +23,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed
async def test_coordinator(
hass: HomeAssistant,
entity_registry_enabled_by_default: AsyncMock,
entity_registry_enabled_by_default: None,
monkeypatch: pytest.MonkeyPatch,
get_ferries: list[FerryStop],
) -> None:
@ -69,7 +70,7 @@ async def test_coordinator(
assert state3.state == str(dt.now().year + 2) + "-05-01T12:00:00+00:00"
mock_data.reset_mock()
mock_data.side_effect = ValueError("info")
mock_data.side_effect = NoFerryFound()
async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=6))
await hass.async_block_till_done()
mock_data.assert_called_once()
@ -81,11 +82,19 @@ async def test_coordinator(
mock_data.side_effect = None
async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=6))
await hass.async_block_till_done()
mock_data.assert_called_once()
# mock_data.assert_called_once()
state1 = hass.states.get("sensor.harbor1_departure_from")
assert state1.state == "Harbor 1"
mock_data.reset_mock()
mock_data.side_effect = InvalidAuthentication()
async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=6))
await hass.async_block_till_done()
mock_data.assert_called_once()
state1 = hass.states.get("sensor.harbor1_departure_from")
assert state1.state == STATE_UNAVAILABLE
mock_data.reset_mock()
async def test_coordinator_next_departuredate(freezer: FrozenDateTimeFactory) -> None:
"""Test the Trafikverket Ferry next_departuredate calculation."""

View File

@ -4,6 +4,11 @@ from __future__ import annotations
from unittest.mock import patch
import pytest
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleTrainStationsFound,
NoTrainStationFound,
)
from homeassistant import config_entries
from homeassistant.components.trafikverket_train.const import (
@ -108,28 +113,28 @@ async def test_form_entry_already_exist(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("error_message", "base_error"),
("side_effect", "base_error"),
[
(
"Source: Security, message: Invalid authentication",
InvalidAuthentication,
"invalid_auth",
),
(
"Could not find a station with the specified name",
NoTrainStationFound,
"invalid_station",
),
(
"Found multiple stations with the specified name",
MultipleTrainStationsFound,
"more_stations",
),
(
"Unknown",
Exception,
"cannot_connect",
),
],
)
async def test_flow_fails(
hass: HomeAssistant, error_message: str, base_error: str
hass: HomeAssistant, side_effect: Exception, base_error: str
) -> None:
"""Test config flow errors."""
result4 = await hass.config_entries.flow.async_init(
@ -141,7 +146,7 @@ async def test_flow_fails(
with patch(
"homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station",
side_effect=ValueError(error_message),
side_effect=side_effect(),
):
result4 = await hass.config_entries.flow.async_configure(
result4["flow_id"],
@ -234,28 +239,28 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("sideeffect", "p_error"),
("side_effect", "p_error"),
[
(
ValueError("Source: Security, message: Invalid authentication"),
InvalidAuthentication,
"invalid_auth",
),
(
ValueError("Could not find a station with the specified name"),
NoTrainStationFound,
"invalid_station",
),
(
ValueError("Found multiple stations with the specified name"),
MultipleTrainStationsFound,
"more_stations",
),
(
ValueError("Unknown"),
Exception,
"cannot_connect",
),
],
)
async def test_reauth_flow_error(
hass: HomeAssistant, sideeffect: Exception, p_error: str
hass: HomeAssistant, side_effect: Exception, p_error: str
) -> None:
"""Test a reauthentication flow with error."""
entry = MockConfigEntry(
@ -284,7 +289,7 @@ async def test_reauth_flow_error(
with patch(
"homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station",
side_effect=sideeffect,
side_effect=side_effect(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],

View File

@ -4,6 +4,11 @@ from __future__ import annotations
from unittest.mock import patch
import pytest
from pytrafikverket.exceptions import (
InvalidAuthentication,
MultipleWeatherStationsFound,
NoWeatherStationFound,
)
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY
@ -48,28 +53,28 @@ async def test_form(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("error_message", "base_error"),
("side_effect", "base_error"),
[
(
"Source: Security, message: Invalid authentication",
InvalidAuthentication,
"invalid_auth",
),
(
"Could not find a weather station with the specified name",
NoWeatherStationFound,
"invalid_station",
),
(
"Found multiple weather stations with the specified name",
MultipleWeatherStationsFound,
"more_stations",
),
(
"Unknown",
Exception,
"cannot_connect",
),
],
)
async def test_flow_fails(
hass: HomeAssistant, error_message: str, base_error: str
hass: HomeAssistant, side_effect: Exception, base_error: str
) -> None:
"""Test config flow errors."""
result4 = await hass.config_entries.flow.async_init(
@ -81,7 +86,7 @@ async def test_flow_fails(
with patch(
"homeassistant.components.trafikverket_weatherstation.config_flow.TrafikverketWeather.async_get_weather",
side_effect=ValueError(error_message),
side_effect=side_effect(),
):
result4 = await hass.config_entries.flow.async_configure(
result4["flow_id"],