mirror of
https://github.com/home-assistant/core.git
synced 2026-06-30 02:25:31 +02:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6f70bc101 | |||
| ca7ae00c7e | |||
| ff460901b7 | |||
| 30512f08a8 | |||
| 9dd1a59d50 | |||
| 91aded4474 | |||
| 543eab3354 | |||
| 47b331a869 | |||
| 696dd45803 | |||
| f92239877f | |||
| 45ceb13937 | |||
| c5aeee8097 | |||
| bfc750b608 |
@@ -54,3 +54,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
@@ -43,3 +43,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
|
||||
@@ -298,7 +298,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
):
|
||||
self.options.pop(CONF_LLM_HASS_API)
|
||||
if not errors:
|
||||
return await self.async_step_advanced()
|
||||
return await self.async_step_additional()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
@@ -308,10 +308,10 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
errors=errors or None,
|
||||
)
|
||||
|
||||
async def async_step_advanced(
|
||||
async def async_step_additional(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage advanced options."""
|
||||
"""Manage additional options."""
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
|
||||
@@ -360,7 +360,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return await self.async_step_model()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced",
|
||||
step_id="additional",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(step_schema), self.options
|
||||
),
|
||||
|
||||
@@ -48,16 +48,16 @@
|
||||
"user": "Add AI task"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]"
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data::prompt_caching%]"
|
||||
},
|
||||
"data_description": {
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]"
|
||||
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::chat_model%]",
|
||||
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::additional::data_description::prompt_caching%]"
|
||||
},
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::advanced::title%]"
|
||||
"title": "[%key:component::anthropic::config_subentries::conversation::step::additional::title%]"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
@@ -115,7 +115,7 @@
|
||||
"user": "Add conversation agent"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"prompt_caching": "Caching strategy"
|
||||
@@ -124,7 +124,7 @@
|
||||
"chat_model": "The model to serve the responses.",
|
||||
"prompt_caching": "Optimize your API cost and response times based on your usage."
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
"title": "Additional settings"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.data_entry_flow import SectionConfig, section
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_HOSTNAME,
|
||||
CONF_IPV4,
|
||||
CONF_IPV6,
|
||||
@@ -39,7 +39,7 @@ from .const import (
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
|
||||
vol.Required(CONF_ADVANCED_OPTIONS): section(
|
||||
vol.Required(CONF_ADDITIONAL_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_RESOLVER): cv.string,
|
||||
@@ -117,13 +117,13 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input:
|
||||
hostname = user_input[CONF_HOSTNAME]
|
||||
name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname
|
||||
advanced_options = user_input[CONF_ADVANCED_OPTIONS]
|
||||
resolver = advanced_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = advanced_options.get(
|
||||
additional_options = user_input[CONF_ADDITIONAL_OPTIONS]
|
||||
resolver = additional_options.get(CONF_RESOLVER, DEFAULT_RESOLVER)
|
||||
resolver_ipv6 = additional_options.get(
|
||||
CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6
|
||||
)
|
||||
port = advanced_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = advanced_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
port = additional_options.get(CONF_PORT, DEFAULT_PORT)
|
||||
port_ipv6 = additional_options.get(CONF_PORT_IPV6, DEFAULT_PORT)
|
||||
|
||||
validate = await async_validate_hostname(
|
||||
hostname, resolver, resolver_ipv6, port, port_ipv6
|
||||
|
||||
@@ -12,7 +12,7 @@ CONF_PORT_IPV6 = "port_ipv6"
|
||||
CONF_IPV4 = "ipv4"
|
||||
CONF_IPV6 = "ipv6"
|
||||
CONF_IPV6_V4 = "ipv6_v4"
|
||||
CONF_ADVANCED_OPTIONS = "advanced_options"
|
||||
CONF_ADDITIONAL_OPTIONS = "additional_options"
|
||||
|
||||
DEFAULT_HOSTNAME = "myip.opendns.com"
|
||||
DEFAULT_IPV6 = False
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"hostname": "The hostname for which to perform the DNS query."
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"additional_options": {
|
||||
"data": {
|
||||
"port": "IPv4 port",
|
||||
"port_ipv6": "IPv6 port",
|
||||
@@ -63,16 +63,16 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data::resolver_ipv6%]"
|
||||
},
|
||||
"data_description": {
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::advanced_options::data_description::resolver_ipv6%]"
|
||||
"port": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port%]",
|
||||
"port_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::port_ipv6%]",
|
||||
"resolver": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver%]",
|
||||
"resolver_ipv6": "[%key:component::dnsip::config::step::user::sections::additional_options::data_description::resolver_ipv6%]"
|
||||
},
|
||||
"description": "Optionally change resolvers and ports."
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import Any, TypedDict, cast, override
|
||||
from xml.etree.ElementTree import ParseError
|
||||
|
||||
from fritzconnection import FritzConnection
|
||||
from fritzconnection.core.exceptions import FritzActionError
|
||||
from fritzconnection.core.exceptions import FritzActionError, FritzConnectionException
|
||||
from fritzconnection.lib.fritzcall import FritzCall
|
||||
from fritzconnection.lib.fritzhosts import FritzHosts
|
||||
from fritzconnection.lib.fritzstatus import FritzStatus
|
||||
@@ -267,9 +267,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
) = self._update_device_info()
|
||||
|
||||
if self.fritz_status.has_wan_support:
|
||||
self.device_conn_type = (
|
||||
self.fritz_status.get_default_connection_service().connection_service
|
||||
)
|
||||
self.device_conn_type = self.fritz_status.connection_service
|
||||
self.device_is_router = self.fritz_status.has_wan_enabled
|
||||
|
||||
self.has_call_deflections = "X_AVM-DE_OnTel1" in self.connection.services
|
||||
@@ -682,7 +680,18 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
|
||||
async def async_trigger_reconnect(self) -> None:
|
||||
"""Trigger device reconnect."""
|
||||
await self.hass.async_add_executor_job(self.connection.reconnect)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self.connection.call_action,
|
||||
f"{self.device_conn_type}1",
|
||||
"ForceTermination",
|
||||
)
|
||||
except FritzConnectionException as ex:
|
||||
# ignore UPnPError:
|
||||
# errorCode: 707
|
||||
# errorDescription: DisconnectInProgress
|
||||
if "disconnectinprogress" not in str(ex).lower():
|
||||
raise
|
||||
|
||||
async def async_trigger_set_guest_password(
|
||||
self, password: str | None, length: int
|
||||
|
||||
@@ -20,7 +20,7 @@ from .const import (
|
||||
CONF_MIN_STATE_DURATION,
|
||||
CONF_START,
|
||||
PLATFORMS,
|
||||
SECTION_ADVANCED_SETTINGS,
|
||||
SECTION_ADDITIONAL_SETTINGS,
|
||||
)
|
||||
from .coordinator import HistoryStatsUpdateCoordinator
|
||||
from .data import HistoryStats
|
||||
@@ -44,8 +44,8 @@ async def async_setup_entry(
|
||||
min_state_duration: timedelta
|
||||
if duration_dict := entry.options.get(CONF_DURATION):
|
||||
duration = timedelta(**duration_dict)
|
||||
advanced_settings = entry.options.get(SECTION_ADVANCED_SETTINGS, {})
|
||||
if min_state_duration_dict := advanced_settings.get(CONF_MIN_STATE_DURATION):
|
||||
additional_settings = entry.options.get(SECTION_ADDITIONAL_SETTINGS, {})
|
||||
if min_state_duration_dict := additional_settings.get(CONF_MIN_STATE_DURATION):
|
||||
min_state_duration = timedelta(**min_state_duration_dict)
|
||||
else:
|
||||
min_state_duration = timedelta(0)
|
||||
@@ -121,6 +121,13 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, options=options, minor_version=3
|
||||
)
|
||||
if config_entry.minor_version < 4:
|
||||
# The "advanced_settings" section was renamed to "additional_settings"
|
||||
if (additional := options.pop("advanced_settings", None)) is not None:
|
||||
options[SECTION_ADDITIONAL_SETTINGS] = additional
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, options=options, minor_version=4
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migration to version %s.%s successful",
|
||||
|
||||
@@ -44,7 +44,7 @@ from .const import (
|
||||
CONF_TYPE_TIME,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
SECTION_ADVANCED_SETTINGS,
|
||||
SECTION_ADDITIONAL_SETTINGS,
|
||||
)
|
||||
from .coordinator import HistoryStatsUpdateCoordinator
|
||||
from .data import HistoryStats
|
||||
@@ -149,7 +149,7 @@ def _get_options_schema_with_entity_id(entity_id: str, type: str) -> vol.Schema:
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
),
|
||||
),
|
||||
vol.Optional(SECTION_ADVANCED_SETTINGS): section(
|
||||
vol.Optional(SECTION_ADDITIONAL_SETTINGS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_MIN_STATE_DURATION): DurationSelector(
|
||||
@@ -189,7 +189,7 @@ OPTIONS_FLOW = {
|
||||
class HistoryStatsConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
||||
"""Handle a config flow for History stats."""
|
||||
|
||||
MINOR_VERSION = 3
|
||||
MINOR_VERSION = 4
|
||||
|
||||
config_flow = CONFIG_FLOW
|
||||
options_flow = OPTIONS_FLOW
|
||||
@@ -290,8 +290,8 @@ async def ws_start_preview(
|
||||
start = validated_data.get(CONF_START)
|
||||
end = validated_data.get(CONF_END)
|
||||
duration = validated_data.get(CONF_DURATION)
|
||||
advanced_settings = validated_data.get(SECTION_ADVANCED_SETTINGS, {})
|
||||
min_state_duration = advanced_settings.get(CONF_MIN_STATE_DURATION)
|
||||
additional_settings = validated_data.get(SECTION_ADDITIONAL_SETTINGS, {})
|
||||
min_state_duration = additional_settings.get(CONF_MIN_STATE_DURATION)
|
||||
state_class = validated_data.get(CONF_STATE_CLASS)
|
||||
|
||||
history_stats = HistoryStats(
|
||||
|
||||
@@ -18,4 +18,4 @@ CONF_TYPE_KEYS = [CONF_TYPE_TIME, CONF_TYPE_RATIO, CONF_TYPE_COUNT]
|
||||
|
||||
DEFAULT_NAME = "unnamed statistics"
|
||||
|
||||
SECTION_ADVANCED_SETTINGS = "advanced_settings"
|
||||
SECTION_ADDITIONAL_SETTINGS = "additional_settings"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"description": "Read the documentation for further details on how to configure the history stats sensor using these options.",
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
"additional_settings": {
|
||||
"data": { "min_state_duration": "Minimum state duration" },
|
||||
"data_description": {
|
||||
"min_state_duration": "The minimum state duration to account for the statistics. Default is 0 seconds."
|
||||
@@ -93,14 +93,14 @@
|
||||
},
|
||||
"description": "[%key:component::history_stats::config::step::options::description%]",
|
||||
"sections": {
|
||||
"advanced_settings": {
|
||||
"additional_settings": {
|
||||
"data": {
|
||||
"min_state_duration": "[%key:component::history_stats::config::step::options::sections::advanced_settings::data::min_state_duration%]"
|
||||
"min_state_duration": "[%key:component::history_stats::config::step::options::sections::additional_settings::data::min_state_duration%]"
|
||||
},
|
||||
"data_description": {
|
||||
"min_state_duration": "[%key:component::history_stats::config::step::options::sections::advanced_settings::data_description::min_state_duration%]"
|
||||
"min_state_duration": "[%key:component::history_stats::config::step::options::sections::additional_settings::data_description::min_state_duration%]"
|
||||
},
|
||||
"name": "[%key:component::history_stats::config::step::options::sections::advanced_settings::name%]"
|
||||
"name": "[%key:component::history_stats::config::step::options::sections::additional_settings::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
},
|
||||
"get_queue": {
|
||||
"description": "Retrieves the details of the currently active queue of a Music Assistant player.",
|
||||
"name": "Get playerQueue details (advanced)"
|
||||
"name": "Get playerQueue details"
|
||||
},
|
||||
"play_announcement": {
|
||||
"description": "Plays an announcement on a Music Assistant player with more fine-grained control options.",
|
||||
|
||||
@@ -326,7 +326,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
options.update(user_input)
|
||||
if CONF_LLM_HASS_API in options and CONF_LLM_HASS_API not in user_input:
|
||||
options.pop(CONF_LLM_HASS_API)
|
||||
return await self.async_step_advanced()
|
||||
return await self.async_step_additional()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
@@ -335,10 +335,10 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_advanced(
|
||||
async def async_step_additional(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage advanced options."""
|
||||
"""Manage additional options."""
|
||||
options = self.options
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -374,7 +374,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
return await self.async_step_model()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced",
|
||||
step_id="additional",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(step_schema), options
|
||||
),
|
||||
|
||||
@@ -47,18 +47,18 @@
|
||||
"user": "Add AI task"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"max_tokens": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::max_tokens%]",
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::store_responses%]",
|
||||
"temperature": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::temperature%]",
|
||||
"top_p": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data::top_p%]"
|
||||
"max_tokens": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::max_tokens%]",
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::store_responses%]",
|
||||
"temperature": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::temperature%]",
|
||||
"top_p": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data::top_p%]"
|
||||
},
|
||||
"data_description": {
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::data_description::store_responses%]"
|
||||
"store_responses": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::data_description::store_responses%]"
|
||||
},
|
||||
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::advanced::title%]"
|
||||
"title": "[%key:component::openai_conversation::config_subentries::conversation::step::additional::title%]"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
@@ -109,7 +109,7 @@
|
||||
"user": "Add conversation agent"
|
||||
},
|
||||
"step": {
|
||||
"advanced": {
|
||||
"additional": {
|
||||
"data": {
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"max_tokens": "Maximum tokens to return in response",
|
||||
@@ -120,7 +120,7 @@
|
||||
"data_description": {
|
||||
"store_responses": "If enabled, requests and responses are stored by OpenAI and visible in your OpenAI dashboard logs"
|
||||
},
|
||||
"title": "Advanced settings"
|
||||
"title": "Additional settings"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
|
||||
@@ -34,6 +34,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .util import sanitize_container_name
|
||||
|
||||
type PortainerConfigEntry = ConfigEntry[PortainerCoordinator]
|
||||
|
||||
@@ -263,7 +264,7 @@ class PortainerCoordinator(
|
||||
|
||||
# Map containers, started and stopped
|
||||
for container in containers:
|
||||
container_name = self._get_container_name(container.names[0])
|
||||
container_name = sanitize_container_name(container.names[0])
|
||||
prev_container = (
|
||||
prev_endpoint.containers.get(container_name)
|
||||
if prev_endpoint
|
||||
@@ -313,7 +314,7 @@ class PortainerCoordinator(
|
||||
container_stats = dict(
|
||||
zip(
|
||||
(
|
||||
self._get_container_name(container.names[0])
|
||||
sanitize_container_name(container.names[0])
|
||||
for container in active_containers
|
||||
),
|
||||
await asyncio.gather(
|
||||
@@ -431,10 +432,6 @@ class PortainerCoordinator(
|
||||
for stack_callback in self.new_stacks_callbacks:
|
||||
stack_callback(new_stack_data)
|
||||
|
||||
def _get_container_name(self, container_name: str) -> str:
|
||||
"""Sanitize to get a proper container name."""
|
||||
return container_name.replace("/", " ").strip()
|
||||
|
||||
|
||||
class PortainerDockerDiskSpaceCoordinator(
|
||||
PortainerBaseCoordinator[dict[int, DockerSystemDF]]
|
||||
|
||||
@@ -19,6 +19,7 @@ from .coordinator import (
|
||||
PortainerStackData,
|
||||
PortainerVolumeData,
|
||||
)
|
||||
from .util import sanitize_container_name
|
||||
|
||||
|
||||
class PortainerCoordinatorEntity(CoordinatorEntity[PortainerCoordinator]):
|
||||
@@ -95,7 +96,7 @@ class PortainerContainerEntity(PortainerCoordinatorEntity):
|
||||
# According to Docker's API docs, the first name is unique
|
||||
names = self._device_info.container.names
|
||||
assert names, "Container names list unexpectedly empty"
|
||||
self.device_name = names[0].replace("/", " ").strip()
|
||||
self.device_name = sanitize_container_name(names[0])
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Utility functions for the Portainer integration."""
|
||||
|
||||
|
||||
def sanitize_container_name(container_name: str) -> str:
|
||||
"""Sanitize to get a proper container name."""
|
||||
return container_name.replace("/", " ").strip()
|
||||
@@ -32,8 +32,8 @@
|
||||
"timeout": "Timeout for connection to website.",
|
||||
"verify_ssl": "Enables/disables verification of SSL/TLS certificate, for example if it is self-signed."
|
||||
},
|
||||
"description": "Provide additional advanced settings for the resource.",
|
||||
"name": "Advanced settings"
|
||||
"description": "Provide additional settings for the resource.",
|
||||
"name": "Additional settings"
|
||||
},
|
||||
"auth": {
|
||||
"data": {
|
||||
@@ -117,8 +117,8 @@
|
||||
"unit_of_measurement": "Choose unit of measurement or create your own.",
|
||||
"value_template": "Defines a template to get the state of the sensor."
|
||||
},
|
||||
"description": "Provide additional advanced settings for the sensor.",
|
||||
"name": "Advanced settings"
|
||||
"description": "Provide additional settings for the sensor.",
|
||||
"name": "Additional settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["verisure"],
|
||||
"requirements": ["vsure==2.7.1"]
|
||||
"requirements": ["vsure==2.8.0"]
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"migration_strategy_recommended": "This is the quickest option to migrate to a new adapter."
|
||||
},
|
||||
"menu_options": {
|
||||
"migration_strategy_advanced": "Advanced migration",
|
||||
"migration_strategy_advanced": "Migrate manually",
|
||||
"migration_strategy_recommended": "Migrate automatically (recommended)"
|
||||
},
|
||||
"title": "Migrate to a new adapter"
|
||||
@@ -74,7 +74,7 @@
|
||||
"setup_strategy_recommended": "This is the quickest option to create a new network and get started."
|
||||
},
|
||||
"menu_options": {
|
||||
"setup_strategy_advanced": "Advanced setup",
|
||||
"setup_strategy_advanced": "Set up manually",
|
||||
"setup_strategy_recommended": "Set up automatically (recommended)"
|
||||
},
|
||||
"title": "Set up Zigbee"
|
||||
|
||||
Generated
+1
-1
@@ -3313,7 +3313,7 @@ volkszaehler==0.4.0
|
||||
volvocarsapi==0.4.3
|
||||
|
||||
# homeassistant.components.verisure
|
||||
vsure==2.7.1
|
||||
vsure==2.8.0
|
||||
|
||||
# homeassistant.components.vasttrafik
|
||||
vtjp==0.2.1
|
||||
|
||||
@@ -275,9 +275,9 @@ async def test_subentry_options_thinking_budget_more_than_max(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
options = await hass.config_entries.subentries.async_configure(
|
||||
options["flow_id"],
|
||||
{"chat_model": "claude-sonnet-4-5"},
|
||||
@@ -330,9 +330,9 @@ async def test_subentry_web_search_user_location(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
options = await hass.config_entries.subentries.async_configure(
|
||||
options["flow_id"],
|
||||
{
|
||||
@@ -424,7 +424,7 @@ async def test_model_list(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
assert options["data_schema"].schema["chat_model"].config["options"] == snapshot
|
||||
|
||||
|
||||
@@ -447,9 +447,9 @@ async def test_invalid_model(
|
||||
},
|
||||
)
|
||||
assert options["type"] is FlowResultType.FORM
|
||||
assert options["step_id"] == "advanced"
|
||||
assert options["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step but with api error
|
||||
# Configure additional step but with api error
|
||||
with patch(
|
||||
"homeassistant.components.anthropic.config_flow.anthropic.resources.models.AsyncModels.retrieve",
|
||||
new_callable=AsyncMock,
|
||||
@@ -877,12 +877,12 @@ async def test_ai_task_subentry_not_loaded(
|
||||
assert result.get("reason") == "entry_not_loaded"
|
||||
|
||||
|
||||
async def test_creating_ai_task_subentry_advanced(
|
||||
async def test_creating_ai_task_subentry_additional(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry with advanced settings."""
|
||||
"""Test creating an AI task subentry with additional settings."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
(mock_config_entry.entry_id, "ai_task_data"),
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
@@ -891,7 +891,7 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "init"
|
||||
|
||||
# Go to advanced settings
|
||||
# Go to additional settings
|
||||
result2 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -901,9 +901,9 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "advanced"
|
||||
assert result2.get("step_id") == "additional"
|
||||
|
||||
# Configure advanced settings
|
||||
# Configure additional settings
|
||||
result3 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.dnsip.config_flow import DATA_SCHEMA
|
||||
from homeassistant.components.dnsip.const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ADDITIONAL_OPTIONS,
|
||||
CONF_HOSTNAME,
|
||||
CONF_IPV4,
|
||||
CONF_IPV6,
|
||||
@@ -50,7 +50,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOSTNAME: "home-assistant.io", CONF_ADVANCED_OPTIONS: {}},
|
||||
{CONF_HOSTNAME: "home-assistant.io", CONF_ADDITIONAL_OPTIONS: {}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -71,7 +71,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_with_advanced_options(hass: HomeAssistant) -> None:
|
||||
async def test_form_with_additional_options(hass: HomeAssistant) -> None:
|
||||
"""Test we can submit the form with custom resolver and port options."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -95,7 +95,7 @@ async def test_form_with_advanced_options(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {
|
||||
CONF_ADDITIONAL_OPTIONS: {
|
||||
CONF_RESOLVER: "8.8.8.8",
|
||||
CONF_RESOLVER_IPV6: "2620:119:53::53",
|
||||
CONF_PORT: 53,
|
||||
@@ -136,7 +136,7 @@ async def test_form_error(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -185,7 +185,7 @@ async def test_flow_already_exist(hass: HomeAssistant) -> None:
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOSTNAME: "home-assistant.io",
|
||||
CONF_ADVANCED_OPTIONS: {},
|
||||
CONF_ADDITIONAL_OPTIONS: {},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -483,31 +483,44 @@ async def test_trigger_methods(
|
||||
) -> None:
|
||||
"""Test trigger methods delegate to correct underlying calls."""
|
||||
|
||||
# test async_trigger_firmware_update
|
||||
fritz_tools.connection.call_action = MagicMock(
|
||||
return_value={"NewX_AVM-DE_UpdateState": True}
|
||||
)
|
||||
assert await fritz_tools.async_trigger_firmware_update() is True
|
||||
|
||||
# test async_trigger_reboot
|
||||
fritz_tools.connection.reboot = MagicMock()
|
||||
fritz_tools.connection.reconnect = MagicMock()
|
||||
await fritz_tools.async_trigger_reboot()
|
||||
fritz_tools.connection.reboot.assert_called_once()
|
||||
|
||||
# test async_trigger_set_guest_password
|
||||
fritz_tools.fritz_guest_wifi.set_password = MagicMock()
|
||||
await fritz_tools.async_trigger_set_guest_password("new-password", 20)
|
||||
fritz_tools.fritz_guest_wifi.set_password.assert_called_once_with(
|
||||
"new-password", 20
|
||||
)
|
||||
|
||||
# test async_trigger_reconnect
|
||||
fritz_tools.connection.call_action = MagicMock(
|
||||
side_effect=FritzConnectionException(
|
||||
"UPnPError:\nerrorCode: 707\nerrorDescription: DisconnectInProgress"
|
||||
)
|
||||
)
|
||||
await fritz_tools.async_trigger_reconnect()
|
||||
fritz_tools.connection.call_action.assert_called_with(
|
||||
"WANPPPConnection1", "ForceTermination"
|
||||
)
|
||||
|
||||
# test async_trigger_dial
|
||||
fritz_tools.fritz_call.dial = MagicMock()
|
||||
fritz_tools.fritz_call.hangup = MagicMock()
|
||||
|
||||
assert await fritz_tools.async_trigger_firmware_update() is True
|
||||
await fritz_tools.async_trigger_reboot()
|
||||
await fritz_tools.async_trigger_reconnect()
|
||||
await fritz_tools.async_trigger_set_guest_password("new-password", 20)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fritz.coordinator.asyncio.sleep",
|
||||
new=AsyncMock(),
|
||||
) as sleep_mock:
|
||||
await fritz_tools.async_trigger_dial("012345", 1)
|
||||
|
||||
fritz_tools.connection.reboot.assert_called_once()
|
||||
fritz_tools.connection.reconnect.assert_called_once()
|
||||
fritz_tools.fritz_guest_wifi.set_password.assert_called_once_with(
|
||||
"new-password", 20
|
||||
)
|
||||
fritz_tools.fritz_call.dial.assert_called_once_with("012345")
|
||||
sleep_mock.assert_awaited_once_with(1)
|
||||
fritz_tools.fritz_call.hangup.assert_called_once()
|
||||
|
||||
@@ -10,9 +10,11 @@ from homeassistant.components.history_stats.config_flow import (
|
||||
)
|
||||
from homeassistant.components.history_stats.const import (
|
||||
CONF_END,
|
||||
CONF_MIN_STATE_DURATION,
|
||||
CONF_START,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
SECTION_ADDITIONAL_SETTINGS,
|
||||
)
|
||||
from homeassistant.components.sensor import CONF_STATE_CLASS, SensorStateClass
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
@@ -476,6 +478,46 @@ async def test_migration_1_2(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("recorder_mock")
|
||||
async def test_migration_1_3(
|
||||
hass: HomeAssistant,
|
||||
sensor_entity_entry: er.RegistryEntry,
|
||||
) -> None:
|
||||
"""Test migration from v1.3 renames advanced_settings to additional_settings."""
|
||||
|
||||
history_stats_config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
CONF_NAME: DEFAULT_NAME,
|
||||
CONF_ENTITY_ID: sensor_entity_entry.entity_id,
|
||||
CONF_STATE: ["on"],
|
||||
CONF_TYPE: "count",
|
||||
CONF_START: "{{ as_timestamp(utcnow()) - 3600 }}",
|
||||
CONF_END: "{{ utcnow() }}",
|
||||
"advanced_settings": {CONF_MIN_STATE_DURATION: {"seconds": 30}},
|
||||
},
|
||||
title="My history stats",
|
||||
version=1,
|
||||
minor_version=3,
|
||||
)
|
||||
history_stats_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(history_stats_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert history_stats_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert "advanced_settings" not in history_stats_config_entry.options
|
||||
assert history_stats_config_entry.options[SECTION_ADDITIONAL_SETTINGS] == {
|
||||
CONF_MIN_STATE_DURATION: {"seconds": 30}
|
||||
}
|
||||
assert history_stats_config_entry.version == 1
|
||||
assert (
|
||||
history_stats_config_entry.minor_version
|
||||
== HistoryStatsConfigFlowHandler.MINOR_VERSION
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("recorder_mock")
|
||||
async def test_migration_from_future_version(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -246,9 +246,9 @@ async def test_subentry_unsupported_model(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -300,9 +300,9 @@ async def test_subentry_reasoning_effort_list(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -354,9 +354,9 @@ async def test_subentry_reasoning_summary_visibility(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -403,7 +403,7 @@ async def test_subentry_reasoning_summary_options(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
@@ -450,7 +450,7 @@ async def test_subentry_reasoning_summary_default_sanitized_on_model_switch(
|
||||
CONF_LLM_HASS_API: ["assist"],
|
||||
},
|
||||
)
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
@@ -515,9 +515,9 @@ async def test_subentry_service_tier_list(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -561,9 +561,9 @@ async def test_subentry_unsupported_reasoning_effort(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -1144,9 +1144,9 @@ async def test_subentry_web_search_user_location(
|
||||
},
|
||||
)
|
||||
assert subentry_flow["type"] is FlowResultType.FORM
|
||||
assert subentry_flow["step_id"] == "advanced"
|
||||
assert subentry_flow["step_id"] == "additional"
|
||||
|
||||
# Configure advanced step
|
||||
# Configure additional step
|
||||
subentry_flow = await hass.config_entries.subentries.async_configure(
|
||||
subentry_flow["flow_id"],
|
||||
{
|
||||
@@ -1292,12 +1292,12 @@ async def test_ai_task_subentry_not_loaded(
|
||||
assert result.get("reason") == "entry_not_loaded"
|
||||
|
||||
|
||||
async def test_creating_ai_task_subentry_advanced(
|
||||
async def test_creating_ai_task_subentry_additional(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry with advanced settings."""
|
||||
"""Test creating an AI task subentry with additional settings."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
(mock_config_entry.entry_id, "ai_task_data"),
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
@@ -1306,7 +1306,7 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "init"
|
||||
|
||||
# Go to advanced settings
|
||||
# Go to additional settings
|
||||
result2 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
@@ -1316,9 +1316,9 @@ async def test_creating_ai_task_subentry_advanced(
|
||||
)
|
||||
|
||||
assert result2.get("type") is FlowResultType.FORM
|
||||
assert result2.get("step_id") == "advanced"
|
||||
assert result2.get("step_id") == "additional"
|
||||
|
||||
# Configure advanced settings
|
||||
# Configure additional settings
|
||||
result3 = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user