Skip to content

Commit 10c8ee4

Browse files
heindrichpaulCopilotgjohansson-STerwindounajoostlek
authored
Refactor Nederlandse Spoorwegen integration (home-assistant#154616)
Co-authored-by: Copilot <[email protected]> Co-authored-by: G Johansson <[email protected]> Co-authored-by: Erwin Douna <[email protected]> Co-authored-by: Joostlek <[email protected]>
1 parent b23134f commit 10c8ee4

File tree

5 files changed

+1192
-75
lines changed

5 files changed

+1192
-75
lines changed

homeassistant/components/nederlandse_spoorwegen/sensor.py

Lines changed: 46 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
from typing import Any
88

9+
from ns_api import Trip
910
import voluptuous as vol
1011

1112
from homeassistant.components.sensor import (
@@ -38,6 +39,33 @@
3839
)
3940
from .coordinator import NSConfigEntry, NSDataUpdateCoordinator
4041

42+
43+
def _get_departure_time(trip: Trip | None) -> datetime | None:
44+
"""Get next departure time from trip data."""
45+
return trip.departure_time_actual or trip.departure_time_planned if trip else None
46+
47+
48+
def _get_time_str(time: datetime | None) -> str | None:
49+
"""Get time as string."""
50+
return time.strftime("%H:%M") if time else None
51+
52+
53+
def _get_route(trip: Trip | None) -> list[str]:
54+
"""Get the route as a list of station names from trip data."""
55+
if not trip or not (trip_parts := trip.trip_parts):
56+
return []
57+
route = []
58+
if departure := trip.departure:
59+
route.append(departure)
60+
route.extend(part.destination for part in trip_parts)
61+
return route
62+
63+
64+
def _get_delay(planned: datetime | None, actual: datetime | None) -> bool:
65+
"""Return True if delay is present, False otherwise."""
66+
return bool(planned and actual and planned != actual)
67+
68+
4169
_LOGGER = logging.getLogger(__name__)
4270

4371
ROUTE_SCHEMA = vol.Schema(
@@ -163,94 +191,38 @@ def native_value(self) -> datetime | None:
163191
return None
164192

165193
first_trip = route_data.first_trip
166-
if first_trip.departure_time_actual:
167-
return first_trip.departure_time_actual
168-
return first_trip.departure_time_planned
194+
return _get_departure_time(first_trip)
169195

170196
@property
171197
def extra_state_attributes(self) -> dict[str, Any] | None:
172198
"""Return the state attributes."""
173-
route_data = self.coordinator.data
174-
if not route_data:
175-
return None
176-
177-
first_trip = route_data.first_trip
178-
next_trip = route_data.next_trip
199+
first_trip = self.coordinator.data.first_trip
200+
next_trip = self.coordinator.data.next_trip
179201

180202
if not first_trip:
181203
return None
182204

183-
route = []
184-
if first_trip.trip_parts:
185-
route = [first_trip.departure]
186-
route.extend(k.destination for k in first_trip.trip_parts)
187-
188-
# Static attributes
189-
attributes = {
205+
return {
190206
"going": first_trip.going,
191-
"departure_time_planned": None,
192-
"departure_time_actual": None,
193-
"departure_delay": False,
207+
"departure_time_planned": _get_time_str(first_trip.departure_time_planned),
208+
"departure_time_actual": _get_time_str(first_trip.departure_time_actual),
209+
"departure_delay": _get_delay(
210+
first_trip.departure_time_planned,
211+
first_trip.departure_time_actual,
212+
),
194213
"departure_platform_planned": first_trip.departure_platform_planned,
195214
"departure_platform_actual": first_trip.departure_platform_actual,
196-
"arrival_time_planned": None,
197-
"arrival_time_actual": None,
198-
"arrival_delay": False,
215+
"arrival_time_planned": _get_time_str(first_trip.arrival_time_planned),
216+
"arrival_time_actual": _get_time_str(first_trip.arrival_time_actual),
217+
"arrival_delay": _get_delay(
218+
first_trip.arrival_time_planned,
219+
first_trip.arrival_time_actual,
220+
),
199221
"arrival_platform_planned": first_trip.arrival_platform_planned,
200222
"arrival_platform_actual": first_trip.arrival_platform_actual,
201-
"next": None,
223+
"next": _get_time_str(_get_departure_time(next_trip)),
202224
"status": first_trip.status.lower() if first_trip.status else None,
203225
"transfers": first_trip.nr_transfers,
204-
"route": route,
226+
"route": _get_route(first_trip),
205227
"remarks": None,
206228
}
207-
208-
# Planned departure attributes
209-
if first_trip.departure_time_planned is not None:
210-
attributes["departure_time_planned"] = (
211-
first_trip.departure_time_planned.strftime("%H:%M")
212-
)
213-
214-
# Actual departure attributes
215-
if first_trip.departure_time_actual is not None:
216-
attributes["departure_time_actual"] = (
217-
first_trip.departure_time_actual.strftime("%H:%M")
218-
)
219-
220-
# Delay departure attributes
221-
if (
222-
attributes["departure_time_planned"]
223-
and attributes["departure_time_actual"]
224-
and attributes["departure_time_planned"]
225-
!= attributes["departure_time_actual"]
226-
):
227-
attributes["departure_delay"] = True
228-
229-
# Planned arrival attributes
230-
if first_trip.arrival_time_planned is not None:
231-
attributes["arrival_time_planned"] = (
232-
first_trip.arrival_time_planned.strftime("%H:%M")
233-
)
234-
235-
# Actual arrival attributes
236-
if first_trip.arrival_time_actual is not None:
237-
attributes["arrival_time_actual"] = first_trip.arrival_time_actual.strftime(
238-
"%H:%M"
239-
)
240-
241-
# Delay arrival attributes
242-
if (
243-
attributes["arrival_time_planned"]
244-
and attributes["arrival_time_actual"]
245-
and attributes["arrival_time_planned"] != attributes["arrival_time_actual"]
246-
):
247-
attributes["arrival_delay"] = True
248-
249-
# Next trip attributes
250-
if next_trip:
251-
if next_trip.departure_time_actual is not None:
252-
attributes["next"] = next_trip.departure_time_actual.strftime("%H:%M")
253-
elif next_trip.departure_time_planned is not None:
254-
attributes["next"] = next_trip.departure_time_planned.strftime("%H:%M")
255-
256-
return attributes

tests/components/nederlandse_spoorwegen/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ def mock_nsapi() -> Generator[AsyncMock]:
5656
yield client
5757

5858

59+
@pytest.fixture
60+
def mock_single_trip_nsapi(mock_nsapi: AsyncMock) -> Generator[AsyncMock]:
61+
"""Override async_setup_entry."""
62+
trips_data = load_json_object_fixture("trip_single.json", DOMAIN)
63+
mock_nsapi.get_trips.return_value = [Trip(trip) for trip in trips_data["trips"]]
64+
return mock_nsapi
65+
66+
67+
@pytest.fixture
68+
def mock_no_trips_nsapi(mock_nsapi: AsyncMock) -> Generator[AsyncMock]:
69+
"""Override async_setup_entry."""
70+
mock_nsapi.get_trips.return_value = []
71+
return mock_nsapi
72+
73+
5974
@pytest.fixture
6075
def mock_config_entry() -> MockConfigEntry:
6176
"""Mock config entry."""

0 commit comments

Comments
 (0)