mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
2023.12.3 (#105757)
This commit is contained in:
commit
d56b79a993
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aemet_opendata"],
|
"loggers": ["aemet_opendata"],
|
||||||
"requirements": ["AEMET-OpenData==0.4.6"]
|
"requirements": ["AEMET-OpenData==0.4.7"]
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,6 @@ from homeassistant.components.http.data_validator import RequestDataValidator
|
|||||||
from homeassistant.components.http.view import HomeAssistantView
|
from homeassistant.components.http.view import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.network import is_cloud_connection
|
from homeassistant.helpers.network import is_cloud_connection
|
||||||
from homeassistant.util.network import is_local
|
|
||||||
|
|
||||||
from . import indieauth
|
from . import indieauth
|
||||||
|
|
||||||
@ -165,8 +164,6 @@ class AuthProvidersView(HomeAssistantView):
|
|||||||
|
|
||||||
providers = []
|
providers = []
|
||||||
for provider in hass.auth.auth_providers:
|
for provider in hass.auth.auth_providers:
|
||||||
additional_data = {}
|
|
||||||
|
|
||||||
if provider.type == "trusted_networks":
|
if provider.type == "trusted_networks":
|
||||||
if cloud_connection:
|
if cloud_connection:
|
||||||
# Skip quickly as trusted networks are not available on cloud
|
# Skip quickly as trusted networks are not available on cloud
|
||||||
@ -179,30 +176,12 @@ class AuthProvidersView(HomeAssistantView):
|
|||||||
except InvalidAuthError:
|
except InvalidAuthError:
|
||||||
# Not a trusted network, so we don't expose that trusted_network authenticator is setup
|
# Not a trusted network, so we don't expose that trusted_network authenticator is setup
|
||||||
continue
|
continue
|
||||||
elif (
|
|
||||||
provider.type == "homeassistant"
|
|
||||||
and not cloud_connection
|
|
||||||
and is_local(remote_address)
|
|
||||||
and "person" in hass.config.components
|
|
||||||
):
|
|
||||||
# We are local, return user id and username
|
|
||||||
users = await provider.store.async_get_users()
|
|
||||||
additional_data["users"] = {
|
|
||||||
user.id: credentials.data["username"]
|
|
||||||
for user in users
|
|
||||||
for credentials in user.credentials
|
|
||||||
if (
|
|
||||||
credentials.auth_provider_type == provider.type
|
|
||||||
and credentials.auth_provider_id == provider.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
providers.append(
|
providers.append(
|
||||||
{
|
{
|
||||||
"name": provider.name,
|
"name": provider.name,
|
||||||
"id": provider.id,
|
"id": provider.id,
|
||||||
"type": provider.type,
|
"type": provider.type,
|
||||||
**additional_data,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,20 +70,22 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzboxBinarySensor(coordinator, ain, description)
|
FritzboxBinarySensor(coordinator, ain, description)
|
||||||
for ain in coordinator.new_devices
|
for ain in devices
|
||||||
for description in BINARY_SENSOR_TYPES
|
for description in BINARY_SENSOR_TYPES
|
||||||
if description.suitable(coordinator.data.devices[ain])
|
if description.suitable(coordinator.data.devices[ain])
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzboxBinarySensor(FritzBoxDeviceEntity, BinarySensorEntity):
|
class FritzboxBinarySensor(FritzBoxDeviceEntity, BinarySensorEntity):
|
||||||
|
@ -19,17 +19,17 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(templates: set[str] | None = None) -> None:
|
||||||
"""Add templates."""
|
"""Add templates."""
|
||||||
if not coordinator.new_templates:
|
if templates is None:
|
||||||
|
templates = coordinator.new_templates
|
||||||
|
if not templates:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(FritzBoxTemplate(coordinator, ain) for ain in templates)
|
||||||
FritzBoxTemplate(coordinator, ain) for ain in coordinator.new_templates
|
|
||||||
)
|
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.templates.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzBoxTemplate(FritzBoxEntity, ButtonEntity):
|
class FritzBoxTemplate(FritzBoxEntity, ButtonEntity):
|
||||||
|
@ -52,19 +52,21 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzboxThermostat(coordinator, ain)
|
FritzboxThermostat(coordinator, ain)
|
||||||
for ain in coordinator.new_devices
|
for ain in devices
|
||||||
if coordinator.data.devices[ain].has_thermostat
|
if coordinator.data.devices[ain].has_thermostat
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||||
|
@ -24,19 +24,21 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzboxCover(coordinator, ain)
|
FritzboxCover(coordinator, ain)
|
||||||
for ain in coordinator.new_devices
|
for ain in devices
|
||||||
if coordinator.data.devices[ain].has_blind
|
if coordinator.data.devices[ain].has_blind
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzboxCover(FritzBoxDeviceEntity, CoverEntity):
|
class FritzboxCover(FritzBoxDeviceEntity, CoverEntity):
|
||||||
|
@ -30,22 +30,21 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzboxLight(
|
FritzboxLight(coordinator, ain)
|
||||||
coordinator,
|
for ain in devices
|
||||||
ain,
|
if coordinator.data.devices[ain].has_lightbulb
|
||||||
)
|
|
||||||
for ain in coordinator.new_devices
|
|
||||||
if (coordinator.data.devices[ain]).has_lightbulb
|
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzboxLight(FritzBoxDeviceEntity, LightEntity):
|
class FritzboxLight(FritzBoxDeviceEntity, LightEntity):
|
||||||
|
@ -215,20 +215,22 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzBoxSensor(coordinator, ain, description)
|
FritzBoxSensor(coordinator, ain, description)
|
||||||
for ain in coordinator.new_devices
|
for ain in devices
|
||||||
for description in SENSOR_TYPES
|
for description in SENSOR_TYPES
|
||||||
if description.suitable(coordinator.data.devices[ain])
|
if description.suitable(coordinator.data.devices[ain])
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzBoxSensor(FritzBoxDeviceEntity, SensorEntity):
|
class FritzBoxSensor(FritzBoxDeviceEntity, SensorEntity):
|
||||||
|
@ -19,19 +19,21 @@ async def async_setup_entry(
|
|||||||
coordinator = get_coordinator(hass, entry.entry_id)
|
coordinator = get_coordinator(hass, entry.entry_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_entities() -> None:
|
def _add_entities(devices: set[str] | None = None) -> None:
|
||||||
"""Add devices."""
|
"""Add devices."""
|
||||||
if not coordinator.new_devices:
|
if devices is None:
|
||||||
|
devices = coordinator.new_devices
|
||||||
|
if not devices:
|
||||||
return
|
return
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
FritzboxSwitch(coordinator, ain)
|
FritzboxSwitch(coordinator, ain)
|
||||||
for ain in coordinator.new_devices
|
for ain in devices
|
||||||
if coordinator.data.devices[ain].has_switch
|
if coordinator.data.devices[ain].has_switch
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
|
|
||||||
_add_entities()
|
_add_entities(set(coordinator.data.devices.keys()))
|
||||||
|
|
||||||
|
|
||||||
class FritzboxSwitch(FritzBoxDeviceEntity, SwitchEntity):
|
class FritzboxSwitch(FritzBoxDeviceEntity, SwitchEntity):
|
||||||
|
@ -74,7 +74,8 @@ class FullyKioskEntity(CoordinatorEntity[FullyKioskDataUpdateCoordinator], Entit
|
|||||||
@callback
|
@callback
|
||||||
def message_callback(message: mqtt.ReceiveMessage) -> None:
|
def message_callback(message: mqtt.ReceiveMessage) -> None:
|
||||||
payload = json.loads(message.payload)
|
payload = json.loads(message.payload)
|
||||||
event_callback(**payload)
|
if "event" in payload and payload["event"] == event:
|
||||||
|
event_callback(**payload)
|
||||||
|
|
||||||
topic_template = data["settings"]["mqttEventTopic"]
|
topic_template = data["settings"]["mqttEventTopic"]
|
||||||
topic = (
|
topic = (
|
||||||
@ -82,4 +83,5 @@ class FullyKioskEntity(CoordinatorEntity[FullyKioskDataUpdateCoordinator], Entit
|
|||||||
.replace("$event", event)
|
.replace("$event", event)
|
||||||
.replace("$deviceId", data["deviceID"])
|
.replace("$deviceId", data["deviceID"])
|
||||||
)
|
)
|
||||||
|
|
||||||
return await mqtt.async_subscribe(self.hass, topic, message_callback)
|
return await mqtt.async_subscribe(self.hass, topic, message_callback)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from ipaddress import ip_address
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -51,12 +50,10 @@ from homeassistant.helpers import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.event import async_track_state_change_event
|
from homeassistant.helpers.event import async_track_state_change_event
|
||||||
from homeassistant.helpers.network import is_cloud_connection
|
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util.network import is_local
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -588,33 +585,8 @@ class ListPersonsView(HomeAssistantView):
|
|||||||
|
|
||||||
async def get(self, request: web.Request) -> web.Response:
|
async def get(self, request: web.Request) -> web.Response:
|
||||||
"""Return a list of persons if request comes from a local IP."""
|
"""Return a list of persons if request comes from a local IP."""
|
||||||
try:
|
return self.json_message(
|
||||||
remote_address = ip_address(request.remote) # type: ignore[arg-type]
|
message="Not local",
|
||||||
except ValueError:
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
return self.json_message(
|
message_code="not_local",
|
||||||
message="Invalid remote IP",
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
|
||||||
message_code="invalid_remote_ip",
|
|
||||||
)
|
|
||||||
|
|
||||||
hass: HomeAssistant = request.app["hass"]
|
|
||||||
if is_cloud_connection(hass) or not is_local(remote_address):
|
|
||||||
return self.json_message(
|
|
||||||
message="Not local",
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
|
||||||
message_code="not_local",
|
|
||||||
)
|
|
||||||
|
|
||||||
yaml, storage, _ = hass.data[DOMAIN]
|
|
||||||
persons = [*yaml.async_items(), *storage.async_items()]
|
|
||||||
|
|
||||||
return self.json(
|
|
||||||
{
|
|
||||||
person[ATTR_USER_ID]: {
|
|
||||||
ATTR_NAME: person[ATTR_NAME],
|
|
||||||
CONF_PICTURE: person.get(CONF_PICTURE),
|
|
||||||
}
|
|
||||||
for person in persons
|
|
||||||
if person.get(ATTR_USER_ID)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["renault_api"],
|
"loggers": ["renault_api"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["renault-api==0.2.0"]
|
"requirements": ["renault-api==0.2.1"]
|
||||||
}
|
}
|
||||||
|
@ -43,13 +43,15 @@ SERVICE_CHARGE_SET_SCHEDULE_SCHEMA = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required("id"): cv.positive_int,
|
vol.Required("id"): cv.positive_int,
|
||||||
vol.Optional("activated"): cv.boolean,
|
vol.Optional("activated"): cv.boolean,
|
||||||
vol.Optional("monday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
vol.Optional("monday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
vol.Optional("tuesday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
vol.Optional("tuesday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
vol.Optional("wednesday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
vol.Optional("wednesday"): vol.Any(
|
||||||
vol.Optional("thursday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA
|
||||||
vol.Optional("friday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
),
|
||||||
vol.Optional("saturday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
vol.Optional("thursday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
vol.Optional("sunday"): vol.Schema(SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
vol.Optional("friday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
|
vol.Optional("saturday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
|
vol.Optional("sunday"): vol.Any(None, SERVICE_CHARGE_SET_SCHEDULE_DAY_SCHEMA),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
SERVICE_CHARGE_SET_SCHEDULES_SCHEMA = SERVICE_VEHICLE_SCHEMA.extend(
|
SERVICE_CHARGE_SET_SCHEDULES_SCHEMA = SERVICE_VEHICLE_SCHEMA.extend(
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
reload:
|
8
homeassistant/components/rest_command/strings.json
Normal file
8
homeassistant/components/rest_command/strings.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"services": {
|
||||||
|
"reload": {
|
||||||
|
"name": "[%key:common::action::reload%]",
|
||||||
|
"description": "Reloads RESTful commands from the YAML-configuration."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -260,8 +260,8 @@ class UniFiController:
|
|||||||
for entry in async_entries_for_config_entry(
|
for entry in async_entries_for_config_entry(
|
||||||
entity_registry, self.config_entry.entry_id
|
entity_registry, self.config_entry.entry_id
|
||||||
):
|
):
|
||||||
if entry.domain == Platform.DEVICE_TRACKER:
|
if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id:
|
||||||
macs.append(entry.unique_id.split("-", 1)[0])
|
macs.append(entry.unique_id.split("-", 1)[1])
|
||||||
|
|
||||||
for mac in self.option_supported_clients + self.option_block_clients + macs:
|
for mac in self.option_supported_clients + self.option_block_clients + macs:
|
||||||
if mac not in self.api.clients and mac in self.api.clients_all:
|
if mac not in self.api.clients and mac in self.api.clients_all:
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["zeroconf"],
|
"loggers": ["zeroconf"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["zeroconf==0.128.4"]
|
"requirements": ["zeroconf==0.128.5"]
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ from typing import Final
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 12
|
MINOR_VERSION: Final = 12
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -57,7 +57,7 @@ voluptuous-serialize==2.6.0
|
|||||||
voluptuous==0.13.1
|
voluptuous==0.13.1
|
||||||
webrtc-noise-gain==1.2.3
|
webrtc-noise-gain==1.2.3
|
||||||
yarl==1.9.2
|
yarl==1.9.2
|
||||||
zeroconf==0.128.4
|
zeroconf==0.128.5
|
||||||
|
|
||||||
# Constrain pycryptodome to avoid vulnerability
|
# Constrain pycryptodome to avoid vulnerability
|
||||||
# see https://github.com/home-assistant/core/pull/16238
|
# see https://github.com/home-assistant/core/pull/16238
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.12.2"
|
version = "2023.12.3"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
# homeassistant.components.aemet
|
# homeassistant.components.aemet
|
||||||
AEMET-OpenData==0.4.6
|
AEMET-OpenData==0.4.7
|
||||||
|
|
||||||
# homeassistant.components.aladdin_connect
|
# homeassistant.components.aladdin_connect
|
||||||
AIOAladdinConnect==0.1.58
|
AIOAladdinConnect==0.1.58
|
||||||
@ -2332,7 +2332,7 @@ raspyrfm-client==1.2.8
|
|||||||
regenmaschine==2023.06.0
|
regenmaschine==2023.06.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.2.0
|
renault-api==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.renson
|
# homeassistant.components.renson
|
||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
@ -2810,7 +2810,7 @@ zamg==0.3.3
|
|||||||
zengge==0.2
|
zengge==0.2
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.128.4
|
zeroconf==0.128.5
|
||||||
|
|
||||||
# homeassistant.components.zeversolar
|
# homeassistant.components.zeversolar
|
||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
-r requirements_test.txt
|
-r requirements_test.txt
|
||||||
|
|
||||||
# homeassistant.components.aemet
|
# homeassistant.components.aemet
|
||||||
AEMET-OpenData==0.4.6
|
AEMET-OpenData==0.4.7
|
||||||
|
|
||||||
# homeassistant.components.aladdin_connect
|
# homeassistant.components.aladdin_connect
|
||||||
AIOAladdinConnect==0.1.58
|
AIOAladdinConnect==0.1.58
|
||||||
@ -1744,7 +1744,7 @@ rapt-ble==0.1.2
|
|||||||
regenmaschine==2023.06.0
|
regenmaschine==2023.06.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.2.0
|
renault-api==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.renson
|
# homeassistant.components.renson
|
||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
@ -2105,7 +2105,7 @@ yt-dlp==2023.11.16
|
|||||||
zamg==0.3.3
|
zamg==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.128.4
|
zeroconf==0.128.5
|
||||||
|
|
||||||
# homeassistant.components.zeversolar
|
# homeassistant.components.zeversolar
|
||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
"""Tests for the login flow."""
|
"""Tests for the login flow."""
|
||||||
from collections.abc import Callable
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.auth.models import User
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
@ -67,22 +65,16 @@ async def _test_fetch_auth_providers_home_assistant(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aiohttp_client: ClientSessionGenerator,
|
aiohttp_client: ClientSessionGenerator,
|
||||||
ip: str,
|
ip: str,
|
||||||
additional_expected_fn: Callable[[User], dict[str, Any]],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test fetching auth providers for homeassistant auth provider."""
|
"""Test fetching auth providers for homeassistant auth provider."""
|
||||||
client = await async_setup_auth(
|
client = await async_setup_auth(
|
||||||
hass, aiohttp_client, [{"type": "homeassistant"}], custom_ip=ip
|
hass, aiohttp_client, [{"type": "homeassistant"}], custom_ip=ip
|
||||||
)
|
)
|
||||||
|
|
||||||
provider = hass.auth.auth_providers[0]
|
|
||||||
credentials = await provider.async_get_or_create_credentials({"username": "hello"})
|
|
||||||
user = await hass.auth.async_get_or_create_user(credentials)
|
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
"name": "Home Assistant Local",
|
"name": "Home Assistant Local",
|
||||||
"type": "homeassistant",
|
"type": "homeassistant",
|
||||||
"id": None,
|
"id": None,
|
||||||
**additional_expected_fn(user),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.get("/auth/providers")
|
resp = await client.get("/auth/providers")
|
||||||
@ -105,9 +97,7 @@ async def test_fetch_auth_providers_home_assistant_person_not_loaded(
|
|||||||
ip: str,
|
ip: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test fetching auth providers for homeassistant auth provider, where person integration is not loaded."""
|
"""Test fetching auth providers for homeassistant auth provider, where person integration is not loaded."""
|
||||||
await _test_fetch_auth_providers_home_assistant(
|
await _test_fetch_auth_providers_home_assistant(hass, aiohttp_client, ip)
|
||||||
hass, aiohttp_client, ip, lambda _: {}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -134,7 +124,6 @@ async def test_fetch_auth_providers_home_assistant_person_loaded(
|
|||||||
hass,
|
hass,
|
||||||
aiohttp_client,
|
aiohttp_client,
|
||||||
ip,
|
ip,
|
||||||
lambda user: {"users": {user.id: user.name}} if is_local else {},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,19 +107,35 @@ async def test_switches_mqtt_update(
|
|||||||
assert entity
|
assert entity
|
||||||
assert entity.state == "on"
|
assert entity.state == "on"
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, "fully/event/onScreensaverStart/abcdef-123456", "{}")
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"fully/event/onScreensaverStart/abcdef-123456",
|
||||||
|
'{"deviceId": "abcdef-123456","event": "onScreensaverStart"}',
|
||||||
|
)
|
||||||
entity = hass.states.get("switch.amazon_fire_screensaver")
|
entity = hass.states.get("switch.amazon_fire_screensaver")
|
||||||
assert entity.state == "on"
|
assert entity.state == "on"
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, "fully/event/onScreensaverStop/abcdef-123456", "{}")
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"fully/event/onScreensaverStop/abcdef-123456",
|
||||||
|
'{"deviceId": "abcdef-123456","event": "onScreensaverStop"}',
|
||||||
|
)
|
||||||
entity = hass.states.get("switch.amazon_fire_screensaver")
|
entity = hass.states.get("switch.amazon_fire_screensaver")
|
||||||
assert entity.state == "off"
|
assert entity.state == "off"
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, "fully/event/screenOff/abcdef-123456", "{}")
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"fully/event/screenOff/abcdef-123456",
|
||||||
|
'{"deviceId": "abcdef-123456","event": "screenOff"}',
|
||||||
|
)
|
||||||
entity = hass.states.get("switch.amazon_fire_screen")
|
entity = hass.states.get("switch.amazon_fire_screen")
|
||||||
assert entity.state == "off"
|
assert entity.state == "off"
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, "fully/event/screenOn/abcdef-123456", "{}")
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"fully/event/screenOn/abcdef-123456",
|
||||||
|
'{"deviceId": "abcdef-123456","event": "screenOn"}',
|
||||||
|
)
|
||||||
entity = hass.states.get("switch.amazon_fire_screen")
|
entity = hass.states.get("switch.amazon_fire_screen")
|
||||||
assert entity.state == "on"
|
assert entity.state == "on"
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""The tests for the person component."""
|
"""The tests for the person component."""
|
||||||
from collections.abc import Callable
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
@ -31,7 +30,6 @@ from homeassistant.setup import async_setup_component
|
|||||||
from .conftest import DEVICE_TRACKER, DEVICE_TRACKER_2
|
from .conftest import DEVICE_TRACKER, DEVICE_TRACKER_2
|
||||||
|
|
||||||
from tests.common import MockUser, mock_component, mock_restore_cache
|
from tests.common import MockUser, mock_component, mock_restore_cache
|
||||||
from tests.test_util import mock_real_ip
|
|
||||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
@ -852,42 +850,10 @@ async def test_entities_in_person(hass: HomeAssistant) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("ip", "status_code", "expected_fn"),
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"192.168.0.10",
|
|
||||||
HTTPStatus.OK,
|
|
||||||
lambda user: {
|
|
||||||
user["user_id"]: {"name": user["name"], "picture": user["picture"]}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"::ffff:192.168.0.10",
|
|
||||||
HTTPStatus.OK,
|
|
||||||
lambda user: {
|
|
||||||
user["user_id"]: {"name": user["name"], "picture": user["picture"]}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"1.2.3.4",
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
lambda _: {"code": "not_local", "message": "Not local"},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"2001:db8::1",
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
lambda _: {"code": "not_local", "message": "Not local"},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_list_persons(
|
async def test_list_persons(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
hass_admin_user: MockUser,
|
hass_admin_user: MockUser,
|
||||||
ip: str,
|
|
||||||
status_code: HTTPStatus,
|
|
||||||
expected_fn: Callable[[dict[str, Any]], dict[str, Any]],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test listing persons from a not local ip address."""
|
"""Test listing persons from a not local ip address."""
|
||||||
|
|
||||||
@ -902,11 +868,10 @@ async def test_list_persons(
|
|||||||
assert await async_setup_component(hass, DOMAIN, config)
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
|
||||||
await async_setup_component(hass, "api", {})
|
await async_setup_component(hass, "api", {})
|
||||||
mock_real_ip(hass.http.app)(ip)
|
|
||||||
client = await hass_client_no_auth()
|
client = await hass_client_no_auth()
|
||||||
|
|
||||||
resp = await client.get("/api/person/list")
|
resp = await client.get("/api/person/list")
|
||||||
|
|
||||||
assert resp.status == status_code
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
assert result == expected_fn(admin)
|
assert result == {"code": "not_local", "message": "Not local"}
|
||||||
|
@ -203,13 +203,12 @@ async def test_service_set_charge_schedule_multi(
|
|||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"activated": True,
|
"activated": True,
|
||||||
"monday": {"startTime": "T12:00Z", "duration": 15},
|
"monday": {"startTime": "T12:00Z", "duration": 30},
|
||||||
"tuesday": {"startTime": "T12:00Z", "duration": 15},
|
"tuesday": {"startTime": "T12:00Z", "duration": 30},
|
||||||
"wednesday": {"startTime": "T12:00Z", "duration": 15},
|
"wednesday": None,
|
||||||
"thursday": {"startTime": "T12:00Z", "duration": 15},
|
"friday": {"startTime": "T12:00Z", "duration": 30},
|
||||||
"friday": {"startTime": "T12:00Z", "duration": 15},
|
"saturday": {"startTime": "T12:00Z", "duration": 30},
|
||||||
"saturday": {"startTime": "T12:00Z", "duration": 15},
|
"sunday": {"startTime": "T12:00Z", "duration": 30},
|
||||||
"sunday": {"startTime": "T12:00Z", "duration": 15},
|
|
||||||
},
|
},
|
||||||
{"id": 3},
|
{"id": 3},
|
||||||
]
|
]
|
||||||
@ -238,6 +237,15 @@ async def test_service_set_charge_schedule_multi(
|
|||||||
mock_call_data: list[ChargeSchedule] = mock_action.mock_calls[0][1][0]
|
mock_call_data: list[ChargeSchedule] = mock_action.mock_calls[0][1][0]
|
||||||
assert mock_action.mock_calls[0][1] == (mock_call_data,)
|
assert mock_action.mock_calls[0][1] == (mock_call_data,)
|
||||||
|
|
||||||
|
# Monday updated with new values
|
||||||
|
assert mock_call_data[1].monday.startTime == "T12:00Z"
|
||||||
|
assert mock_call_data[1].monday.duration == 30
|
||||||
|
# Wednesday has original values cleared
|
||||||
|
assert mock_call_data[1].wednesday is None
|
||||||
|
# Thursday keeps original values
|
||||||
|
assert mock_call_data[1].thursday.startTime == "T23:30Z"
|
||||||
|
assert mock_call_data[1].thursday.duration == 15
|
||||||
|
|
||||||
|
|
||||||
async def test_service_invalid_device_id(
|
async def test_service_invalid_device_id(
|
||||||
hass: HomeAssistant, config_entry: ConfigEntry
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
Loading…
x
Reference in New Issue
Block a user