Skip to content

Commit aca10f5

Browse files
committed
Bump pyuptimerobot to 25.0.0
1 parent 175a128 commit aca10f5

11 files changed

Lines changed: 86 additions & 51 deletions

File tree

homeassistant/components/uptimerobot/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"iot_class": "cloud_polling",
99
"loggers": ["pyuptimerobot"],
1010
"quality_scale": "gold",
11-
"requirements": ["pyuptimerobot==24.0.1"]
11+
"requirements": ["pyuptimerobot==25.0.0"]
1212
}

homeassistant/components/uptimerobot/sensor.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ class UptimeRobotSensor(UptimeRobotEntity, SensorEntity):
6161
"""Representation of a UptimeRobot sensor."""
6262

6363
@property
64-
def native_value(self) -> str:
64+
def native_value(self) -> str | None:
6565
"""Return the status of the monitor."""
66+
if not self._monitor.status:
67+
return None
68+
6669
status = self._monitor.status.lower()
6770
# The API returns "paused"
6871
# but the entity state will be "pause" to avoid a breaking change
69-
return {"paused": "pause"}.get(status, status) # type: ignore[no-any-return]
72+
return {"paused": "pause"}.get(status, status)

homeassistant/components/uptimerobot/switch.py

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,20 @@
44

55
from typing import Any
66

7-
from pyuptimerobot import (
8-
UptimeRobotAuthenticationException,
9-
UptimeRobotException,
10-
UptimeRobotMonitor,
11-
)
7+
from pyuptimerobot import UptimeRobotMonitor
128

139
from homeassistant.components.switch import (
1410
SwitchDeviceClass,
1511
SwitchEntity,
1612
SwitchEntityDescription,
1713
)
1814
from homeassistant.core import HomeAssistant
19-
from homeassistant.exceptions import HomeAssistantError
2015
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2116

22-
from .const import DOMAIN, STATUS_DOWN, STATUS_UP
17+
from .const import STATUS_UP
2318
from .coordinator import UptimeRobotConfigEntry
2419
from .entity import UptimeRobotEntity
25-
from .utils import new_device_listener
20+
from .utils import new_device_listener, uptimerobot_api_call
2621

2722
# Limit the number of parallel updates to 1
2823
PARALLEL_UPDATES = 1
@@ -65,26 +60,14 @@ def is_on(self) -> bool:
6560
"""Return True if the entity is on."""
6661
return bool(self._monitor.status == STATUS_UP)
6762

68-
async def _async_edit_monitor(self, **kwargs: Any) -> None:
69-
"""Edit monitor status."""
70-
try:
71-
await self.api.async_edit_monitor(**kwargs)
72-
except UptimeRobotAuthenticationException:
73-
self.coordinator.config_entry.async_start_reauth(self.hass)
74-
return
75-
except UptimeRobotException as exception:
76-
raise HomeAssistantError(
77-
translation_domain=DOMAIN,
78-
translation_key="api_exception",
79-
translation_placeholders={"error": "Generic UptimeRobot exception"},
80-
) from exception
81-
82-
await self.coordinator.async_request_refresh()
83-
63+
@uptimerobot_api_call
8464
async def async_turn_off(self, **kwargs: Any) -> None:
8565
"""Turn off switch."""
86-
await self._async_edit_monitor(monitor_id=self._monitor.id, status=STATUS_DOWN)
66+
await self.api.async_pause_monitor(monitor_id=self._monitor.id)
67+
await self.coordinator.async_request_refresh()
8768

69+
@uptimerobot_api_call
8870
async def async_turn_on(self, **kwargs: Any) -> None:
8971
"""Turn on switch."""
90-
await self._async_edit_monitor(monitor_id=self._monitor.id, status=STATUS_UP)
72+
await self.api.async_start_monitor(monitor_id=self._monitor.id)
73+
await self.coordinator.async_request_refresh()

homeassistant/components/uptimerobot/utils.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
11
"""Utility functions for the UptimeRobot integration."""
22

3-
from __future__ import annotations
4-
5-
from collections.abc import Callable
6-
7-
from pyuptimerobot import UptimeRobotMonitor
8-
9-
from .coordinator import UptimeRobotDataUpdateCoordinator
3+
from collections.abc import Awaitable, Callable, Coroutine
4+
from functools import wraps
5+
from typing import Any, Concatenate
6+
7+
from pyuptimerobot import UptimeRobotException, UptimeRobotMonitor
8+
9+
from homeassistant.exceptions import HomeAssistantError
10+
11+
from .const import DOMAIN
12+
from .coordinator import (
13+
UptimeRobotAuthenticationException,
14+
UptimeRobotDataUpdateCoordinator,
15+
)
16+
from .entity import UptimeRobotEntity
17+
18+
19+
def uptimerobot_api_call[_T: UptimeRobotEntity, **_P](
20+
func: Callable[Concatenate[_T, _P], Awaitable[None]],
21+
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
22+
"""Catch UptimeRobot API call exceptions."""
23+
24+
@wraps(func)
25+
async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
26+
"""Wrap all command methods."""
27+
try:
28+
await func(self, *args, **kwargs)
29+
except UptimeRobotAuthenticationException:
30+
self.coordinator.config_entry.async_start_reauth(self.hass)
31+
return
32+
except UptimeRobotException as exception:
33+
raise HomeAssistantError(
34+
translation_domain=DOMAIN,
35+
translation_key="api_exception",
36+
translation_placeholders={"error": "Generic UptimeRobot exception"},
37+
) from exception
38+
39+
return cmd_wrapper
1040

1141

1242
def new_device_listener(

requirements_all.txt

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

requirements_test_all.txt

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/components/uptimerobot/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from typing import Any
55
from unittest.mock import patch
66

7-
from pyuptimerobot import API_PATH_MONITORS, UptimeRobotApiResponse
7+
from pyuptimerobot import UptimeRobotApiResponse
8+
from pyuptimerobot.const import API_PATH_MONITORS
89

910
from homeassistant import config_entries
1011
from homeassistant.components.uptimerobot.const import DOMAIN

tests/components/uptimerobot/test_config_flow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
import pytest
66
from pyuptimerobot import (
7-
API_PATH_USER_ME,
87
UptimeRobotAuthenticationException,
98
UptimeRobotConnectionException,
109
UptimeRobotException,
1110
)
11+
from pyuptimerobot.const import API_PATH_USER_ME
1212

1313
from homeassistant import config_entries
1414
from homeassistant.components.uptimerobot.const import DOMAIN

tests/components/uptimerobot/test_diagnostics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import json
44
from unittest.mock import patch
55

6-
from pyuptimerobot import API_PATH_USER_ME, UptimeRobotException
6+
from pyuptimerobot import UptimeRobotException
7+
from pyuptimerobot.const import API_PATH_USER_ME
78

89
from homeassistant.core import HomeAssistant
910

tests/components/uptimerobot/test_sensor.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
from homeassistant.components.sensor import SensorDeviceClass
88
from homeassistant.components.uptimerobot.const import COORDINATOR_UPDATE_INTERVAL
9-
from homeassistant.const import STATE_UNAVAILABLE
9+
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
1010
from homeassistant.core import HomeAssistant
1111
from homeassistant.util import dt as dt_util
1212

1313
from .common import (
14+
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
1415
MOCK_UPTIMEROBOT_MONITOR,
1516
MOCK_UPTIMEROBOT_MONITOR_2,
1617
STATE_UP,
@@ -19,7 +20,7 @@
1920
setup_uptimerobot_integration,
2021
)
2122

22-
from tests.common import async_fire_time_changed
23+
from tests.common import MockConfigEntry, async_fire_time_changed
2324

2425

2526
async def test_presentation(hass: HomeAssistant) -> None:
@@ -83,3 +84,22 @@ async def test_sensor_dynamic(hass: HomeAssistant) -> None:
8384

8485
assert (entity := hass.states.get(entity_id_2))
8586
assert entity.state == STATE_UP
87+
88+
89+
async def test_sensor_monitor_status_missing(
90+
hass: HomeAssistant,
91+
) -> None:
92+
"""Test sensor becomes unknown when the monitor status is missing."""
93+
monitor_without_status = {**MOCK_UPTIMEROBOT_MONITOR, "status": None}
94+
mock_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA)
95+
mock_entry.add_to_hass(hass)
96+
97+
with patch(
98+
"pyuptimerobot.UptimeRobot.async_get_monitors",
99+
return_value=mock_uptimerobot_api_response(data=[monitor_without_status]),
100+
):
101+
assert await hass.config_entries.async_setup(mock_entry.entry_id)
102+
await hass.async_block_till_done()
103+
104+
assert (entity := hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY))
105+
assert entity.state == STATE_UNKNOWN

0 commit comments

Comments
 (0)