Compare commits

...

7 Commits

Author SHA1 Message Date
Paul Bottein df8925b1b9 Update homeassistant/helpers/selector.py
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2025-06-21 18:15:53 +02:00
Paul Bottein d61cf0f805 Update format 2025-06-20 14:53:01 +02:00
Paul Bottein 272837205c Add schema supports to object selector 2025-06-20 11:17:18 +02:00
Raphael Hehl 956f726ef3 Bump uiprotect to version 7.14.0 (#147102) 2025-06-19 11:20:29 +02:00
epenet fada81e1ce Bump ovoenergy to 2.0.1 (#147112) 2025-06-19 08:46:03 +02:00
Simon Lamon 6a16424bb4 Fix nightly build (#147110)
Update builder.yml
2025-06-19 08:20:19 +02:00
Abílio Costa f90a740429 Use non-autospec mock for Reolink's binary_sensor, camera and diag tests (#147095) 2025-06-19 08:03:48 +02:00
12 changed files with 87 additions and 42 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ jobs:
uses: dawidd6/action-download-artifact@v11
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/intents-package
repo: OHF-Voice/intents-package
branch: main
workflow: nightly.yaml
workflow_conclusion: success
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["ovoenergy"],
"requirements": ["ovoenergy==2.0.0"]
"requirements": ["ovoenergy==2.0.1"]
}
@@ -40,7 +40,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["uiprotect", "unifi_discovery"],
"requirements": ["uiprotect==7.13.0", "unifi-discovery==1.2.0"],
"requirements": ["uiprotect==7.14.0", "unifi-discovery==1.2.0"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",
+29 -1
View File
@@ -1113,9 +1113,23 @@ class NumberSelector(Selector[NumberSelectorConfig]):
return value
class ObjectSelectorField(TypedDict):
"""Class to represent an object selector fields dict."""
label: str
required: bool
selector: dict[str, Any]
class ObjectSelectorConfig(BaseSelectorConfig):
"""Class to represent an object selector config."""
fields: dict[str, ObjectSelectorField]
multiple: bool
label_field: str
description_field: bool
translation_key: str
@SELECTORS.register("object")
class ObjectSelector(Selector[ObjectSelectorConfig]):
@@ -1123,7 +1137,21 @@ class ObjectSelector(Selector[ObjectSelectorConfig]):
selector_type = "object"
CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA
CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend(
{
vol.Optional("fields"): {
str: {
vol.Required("selector"): dict,
vol.Optional("required"): bool,
vol.Optional("label"): str,
}
},
vol.Optional("multiple", default=False): bool,
vol.Optional("label_field"): str,
vol.Optional("description_field"): str,
vol.Optional("translation_key"): str,
}
)
def __init__(self, config: ObjectSelectorConfig | None = None) -> None:
"""Instantiate a selector."""
+2 -2
View File
@@ -1632,7 +1632,7 @@ orvibo==1.1.2
ourgroceries==1.5.4
# homeassistant.components.ovo_energy
ovoenergy==2.0.0
ovoenergy==2.0.1
# homeassistant.components.p1_monitor
p1monitor==3.1.0
@@ -2987,7 +2987,7 @@ typedmonarchmoney==0.4.4
uasiren==0.0.1
# homeassistant.components.unifiprotect
uiprotect==7.13.0
uiprotect==7.14.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.5.7
+2 -2
View File
@@ -1379,7 +1379,7 @@ oralb-ble==0.17.6
ourgroceries==1.5.4
# homeassistant.components.ovo_energy
ovoenergy==2.0.0
ovoenergy==2.0.1
# homeassistant.components.p1_monitor
p1monitor==3.1.0
@@ -2458,7 +2458,7 @@ typedmonarchmoney==0.4.4
uasiren==0.0.1
# homeassistant.components.unifiprotect
uiprotect==7.13.0
uiprotect==7.14.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.5.7
-5
View File
@@ -245,11 +245,6 @@ FORBIDDEN_PACKAGE_EXCEPTIONS: dict[str, dict[str, set[str]]] = {
# opower > arrow > types-python-dateutil
"arrow": {"types-python-dateutil"}
},
"ovo_energy": {
# https://github.com/timmo001/ovoenergy/issues/132
# ovoenergy > incremental > setuptools
"incremental": {"setuptools"}
},
"pi_hole": {"hole": {"async-timeout"}},
"pvpc_hourly_pricing": {"aiopvpc": {"async-timeout"}},
"remote_rpi_gpio": {
+4 -1
View File
@@ -73,6 +73,10 @@ def _init_host_mock(host_mock: MagicMock) -> None:
host_mock.reboot = AsyncMock()
host_mock.set_ptz_command = AsyncMock()
host_mock.get_motion_state_all_ch = AsyncMock(return_value=False)
host_mock.get_stream_source = AsyncMock()
host_mock.get_snapshot = AsyncMock()
host_mock.get_encoding = AsyncMock(return_value="h264")
host_mock.ONVIF_event_callback = AsyncMock()
host_mock.is_nvr = True
host_mock.is_hub = False
host_mock.mac_address = TEST_MAC
@@ -105,7 +109,6 @@ def _init_host_mock(host_mock: MagicMock) -> None:
host_mock.camera_uid.return_value = TEST_UID_CAM
host_mock.camera_online.return_value = True
host_mock.channel_for_uid.return_value = 0
host_mock.get_encoding.return_value = "h264"
host_mock.firmware_update_available.return_value = False
host_mock.session_active = True
host_mock.timeout = 60
+17 -17
View File
@@ -21,11 +21,11 @@ async def test_motion_sensor(
hass_client_no_auth: ClientSessionGenerator,
freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
reolink_host: MagicMock,
) -> None:
"""Test binary sensor entity with motion sensor."""
reolink_connect.model = TEST_DUO_MODEL
reolink_connect.motion_detected.return_value = True
reolink_host.model = TEST_DUO_MODEL
reolink_host.motion_detected.return_value = True
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@@ -34,7 +34,7 @@ async def test_motion_sensor(
entity_id = f"{Platform.BINARY_SENSOR}.{TEST_NVR_NAME}_motion_lens_0"
assert hass.states.get(entity_id).state == STATE_ON
reolink_connect.motion_detected.return_value = False
reolink_host.motion_detected.return_value = False
freezer.tick(DEVICE_UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
@@ -42,8 +42,8 @@ async def test_motion_sensor(
assert hass.states.get(entity_id).state == STATE_OFF
# test ONVIF webhook callback
reolink_connect.motion_detected.return_value = True
reolink_connect.ONVIF_event_callback.return_value = [0]
reolink_host.motion_detected.return_value = True
reolink_host.ONVIF_event_callback.return_value = [0]
webhook_id = config_entry.runtime_data.host.webhook_id
client = await hass_client_no_auth()
await client.post(f"/api/webhook/{webhook_id}", data="test_data")
@@ -56,11 +56,11 @@ async def test_smart_ai_sensor(
hass_client_no_auth: ClientSessionGenerator,
freezer: FrozenDateTimeFactory,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
reolink_host: MagicMock,
) -> None:
"""Test smart ai binary sensor entity."""
reolink_connect.model = TEST_HOST_MODEL
reolink_connect.baichuan.smart_ai_state.return_value = True
reolink_host.model = TEST_HOST_MODEL
reolink_host.baichuan.smart_ai_state.return_value = True
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@@ -69,7 +69,7 @@ async def test_smart_ai_sensor(
entity_id = f"{Platform.BINARY_SENSOR}.{TEST_NVR_NAME}_crossline_zone1_person"
assert hass.states.get(entity_id).state == STATE_ON
reolink_connect.baichuan.smart_ai_state.return_value = False
reolink_host.baichuan.smart_ai_state.return_value = False
freezer.tick(DEVICE_UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
@@ -80,7 +80,7 @@ async def test_smart_ai_sensor(
async def test_tcp_callback(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
reolink_host: MagicMock,
) -> None:
"""Test tcp callback using motion sensor."""
@@ -95,11 +95,11 @@ async def test_tcp_callback(
callback_mock = callback_mock_class()
reolink_connect.model = TEST_HOST_MODEL
reolink_connect.baichuan.events_active = True
reolink_connect.baichuan.subscribe_events.reset_mock(side_effect=True)
reolink_connect.baichuan.register_callback = callback_mock.register_callback
reolink_connect.motion_detected.return_value = True
reolink_host.model = TEST_HOST_MODEL
reolink_host.baichuan.events_active = True
reolink_host.baichuan.subscribe_events.reset_mock(side_effect=True)
reolink_host.baichuan.register_callback = callback_mock.register_callback
reolink_host.motion_detected.return_value = True
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.BINARY_SENSOR]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
@@ -110,7 +110,7 @@ async def test_tcp_callback(
assert hass.states.get(entity_id).state == STATE_ON
# simulate a TCP push callback
reolink_connect.motion_detected.return_value = False
reolink_host.motion_detected.return_value = False
assert callback_mock.callback_func is not None
callback_mock.callback_func()
+6 -8
View File
@@ -25,7 +25,7 @@ async def test_camera(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
reolink_host: MagicMock,
) -> None:
"""Test camera entity with fluent."""
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]):
@@ -37,28 +37,26 @@ async def test_camera(
assert hass.states.get(entity_id).state == CameraState.IDLE
# check getting a image from the camera
reolink_connect.get_snapshot.return_value = b"image"
reolink_host.get_snapshot.return_value = b"image"
assert (await async_get_image(hass, entity_id)).content == b"image"
reolink_connect.get_snapshot.side_effect = ReolinkError("Test error")
reolink_host.get_snapshot.side_effect = ReolinkError("Test error")
with pytest.raises(HomeAssistantError):
await async_get_image(hass, entity_id)
# check getting the stream source
assert await async_get_stream_source(hass, entity_id) is not None
reolink_connect.get_snapshot.reset_mock(side_effect=True)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_camera_no_stream_source(
hass: HomeAssistant,
config_entry: MockConfigEntry,
reolink_connect: MagicMock,
reolink_host: MagicMock,
) -> None:
"""Test camera entity with no stream source."""
reolink_connect.model = TEST_DUO_MODEL
reolink_connect.get_stream_source.return_value = None
reolink_host.model = TEST_DUO_MODEL
reolink_host.get_stream_source.return_value = None
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.CAMERA]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
+2 -2
View File
@@ -15,8 +15,8 @@ from tests.typing import ClientSessionGenerator
async def test_entry_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
reolink_connect: MagicMock,
test_chime: Chime,
reolink_host: MagicMock,
reolink_chime: Chime,
config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
+22 -1
View File
@@ -590,7 +590,28 @@ def test_action_selector_schema(schema, valid_selections, invalid_selections) ->
@pytest.mark.parametrize(
("schema", "valid_selections", "invalid_selections"),
[({}, ("abc123",), ())],
[
({}, ("abc123",), ()),
(
{
"fields": {
"name": {
"required": True,
"selector": {"text": {}},
},
"percentage": {
"selector": {"number": {}},
},
},
"multiple": True,
"label_field": "name",
"description_field": "percentage",
},
(),
(),
),
],
[],
)
def test_object_selector_schema(schema, valid_selections, invalid_selections) -> None:
"""Test object selector."""