mirror of
https://github.com/home-assistant/core.git
synced 2025-11-15 05:50:13 +00:00
Remove translations from WS get_services and REST /api/services (#147120)
This commit is contained in:
@@ -58,7 +58,6 @@ from . import (
|
|||||||
selector,
|
selector,
|
||||||
target as target_helpers,
|
target as target_helpers,
|
||||||
template,
|
template,
|
||||||
translation,
|
|
||||||
)
|
)
|
||||||
from .deprecation import deprecated_class, deprecated_function, deprecated_hass_argument
|
from .deprecation import deprecated_class, deprecated_function, deprecated_hass_argument
|
||||||
from .selector import TargetSelector
|
from .selector import TargetSelector
|
||||||
@@ -586,11 +585,6 @@ async def async_get_all_descriptions(
|
|||||||
_load_services_files, integrations
|
_load_services_files, integrations
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load translations for all service domains
|
|
||||||
translations = await translation.async_get_translations(
|
|
||||||
hass, "en", "services", services
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build response
|
# Build response
|
||||||
descriptions: dict[str, dict[str, Any]] = {}
|
descriptions: dict[str, dict[str, Any]] = {}
|
||||||
for domain, services_map in services.items():
|
for domain, services_map in services.items():
|
||||||
@@ -617,40 +611,11 @@ async def async_get_all_descriptions(
|
|||||||
|
|
||||||
# Don't warn for missing services, because it triggers false
|
# Don't warn for missing services, because it triggers false
|
||||||
# positives for things like scripts, that register as a service
|
# positives for things like scripts, that register as a service
|
||||||
#
|
description = {"fields": yaml_description.get("fields", {})}
|
||||||
# When name & description are in the translations use those;
|
|
||||||
# otherwise fallback to backwards compatible behavior from
|
|
||||||
# the time when we didn't have translations for descriptions yet.
|
|
||||||
# This mimics the behavior of the frontend.
|
|
||||||
description = {
|
|
||||||
"name": translations.get(
|
|
||||||
f"component.{domain}.services.{service_name}.name",
|
|
||||||
yaml_description.get("name", ""),
|
|
||||||
),
|
|
||||||
"description": translations.get(
|
|
||||||
f"component.{domain}.services.{service_name}.description",
|
|
||||||
yaml_description.get("description", ""),
|
|
||||||
),
|
|
||||||
"fields": dict(yaml_description.get("fields", {})),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Translate fields names & descriptions as well
|
for item in ("description", "name", "target"):
|
||||||
for field_name, field_schema in description["fields"].items():
|
if item in yaml_description:
|
||||||
if name := translations.get(
|
description[item] = yaml_description[item]
|
||||||
f"component.{domain}.services.{service_name}.fields.{field_name}.name"
|
|
||||||
):
|
|
||||||
field_schema["name"] = name
|
|
||||||
if desc := translations.get(
|
|
||||||
f"component.{domain}.services.{service_name}.fields.{field_name}.description"
|
|
||||||
):
|
|
||||||
field_schema["description"] = desc
|
|
||||||
if example := translations.get(
|
|
||||||
f"component.{domain}.services.{service_name}.fields.{field_name}.example"
|
|
||||||
):
|
|
||||||
field_schema["example"] = example
|
|
||||||
|
|
||||||
if "target" in yaml_description:
|
|
||||||
description["target"] = yaml_description["target"]
|
|
||||||
|
|
||||||
response = service.supports_response
|
response = service.supports_response
|
||||||
if response is not SupportsResponse.NONE:
|
if response is not SupportsResponse.NONE:
|
||||||
|
|||||||
@@ -5,18 +5,13 @@
|
|||||||
'domain': 'group',
|
'domain': 'group',
|
||||||
'services': dict({
|
'services': dict({
|
||||||
'reload': dict({
|
'reload': dict({
|
||||||
'description': 'Reloads group configuration, entities, and notify services from YAML-configuration.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
}),
|
}),
|
||||||
'name': 'Reload',
|
|
||||||
}),
|
}),
|
||||||
'remove': dict({
|
'remove': dict({
|
||||||
'description': 'Removes a group.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
'object_id': dict({
|
'object_id': dict({
|
||||||
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
|
|
||||||
'example': 'test_group',
|
'example': 'test_group',
|
||||||
'name': 'Object ID',
|
|
||||||
'required': True,
|
'required': True,
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'object': dict({
|
'object': dict({
|
||||||
@@ -25,15 +20,11 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': 'Remove',
|
|
||||||
}),
|
}),
|
||||||
'set': dict({
|
'set': dict({
|
||||||
'description': 'Creates/Updates a group.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
'add_entities': dict({
|
'add_entities': dict({
|
||||||
'description': 'List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Add entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -42,17 +33,13 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'all': dict({
|
'all': dict({
|
||||||
'description': 'Enable this option if the group should only be used when all entities are in state `on`.',
|
|
||||||
'name': 'All',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'boolean': dict({
|
'boolean': dict({
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'entities': dict({
|
'entities': dict({
|
||||||
'description': 'List of all members in the group. Cannot be used in combination with `Add entities` or `Remove entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -61,18 +48,14 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'icon': dict({
|
'icon': dict({
|
||||||
'description': 'Name of the icon for the group.',
|
|
||||||
'example': 'mdi:camera',
|
'example': 'mdi:camera',
|
||||||
'name': 'Icon',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'icon': dict({
|
'icon': dict({
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': dict({
|
'name': dict({
|
||||||
'description': 'Name of the group.',
|
|
||||||
'example': 'My test group',
|
'example': 'My test group',
|
||||||
'name': 'Name',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'text': dict({
|
'text': dict({
|
||||||
'multiline': False,
|
'multiline': False,
|
||||||
@@ -81,9 +64,7 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'object_id': dict({
|
'object_id': dict({
|
||||||
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
|
|
||||||
'example': 'test_group',
|
'example': 'test_group',
|
||||||
'name': 'Object ID',
|
|
||||||
'required': True,
|
'required': True,
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'text': dict({
|
'text': dict({
|
||||||
@@ -93,9 +74,7 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'remove_entities': dict({
|
'remove_entities': dict({
|
||||||
'description': 'List of members to be removed from a group. Cannot be used in combination with `Entities` or `Add entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Remove entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -104,7 +83,6 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': 'Set',
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -139,10 +117,8 @@
|
|||||||
'name': 'Translated name',
|
'name': 'Translated name',
|
||||||
}),
|
}),
|
||||||
'set_level': dict({
|
'set_level': dict({
|
||||||
'description': '',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
}),
|
}),
|
||||||
'name': '',
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -375,10 +375,6 @@ async def test_api_get_services(
|
|||||||
"homeassistant.helpers.service._load_services_file",
|
"homeassistant.helpers.service._load_services_file",
|
||||||
side_effect=_load_services_file,
|
side_effect=_load_services_file,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
|
||||||
return_value={},
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
resp = await mock_api_client.get(const.URL_API_SERVICES)
|
resp = await mock_api_client.get(const.URL_API_SERVICES)
|
||||||
|
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ async def test_translated_unit(
|
|||||||
"""Test translated unit."""
|
"""Test translated unit."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.entity_platform.translation.async_get_translations",
|
||||||
return_value={
|
return_value={
|
||||||
"component.test.entity.number.test_translation_key.unit_of_measurement": "Tests"
|
"component.test.entity.number.test_translation_key.unit_of_measurement": "Tests"
|
||||||
},
|
},
|
||||||
@@ -896,7 +896,7 @@ async def test_translated_unit_with_native_unit_raises(
|
|||||||
"""Test that translated unit."""
|
"""Test that translated unit."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.entity_platform.translation.async_get_translations",
|
||||||
return_value={
|
return_value={
|
||||||
"component.test.entity.number.test_translation_key.unit_of_measurement": "Tests"
|
"component.test.entity.number.test_translation_key.unit_of_measurement": "Tests"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -627,9 +627,6 @@ async def test_service_descriptions(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert descriptions[DOMAIN]["test_name"]["name"] == "ABC"
|
assert descriptions[DOMAIN]["test_name"]["name"] == "ABC"
|
||||||
|
|
||||||
# Test 4: verify that names from YAML are taken into account as well
|
|
||||||
assert descriptions[DOMAIN]["turn_on"]["name"] == "Turn on"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_shared_context(hass: HomeAssistant) -> None:
|
async def test_shared_context(hass: HomeAssistant) -> None:
|
||||||
"""Test that the shared context is passed down the chain."""
|
"""Test that the shared context is passed down the chain."""
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ async def test_translated_unit(
|
|||||||
"""Test translated unit."""
|
"""Test translated unit."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.entity_platform.translation.async_get_translations",
|
||||||
return_value={
|
return_value={
|
||||||
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
||||||
},
|
},
|
||||||
@@ -633,7 +633,7 @@ async def test_translated_unit_with_native_unit_raises(
|
|||||||
"""Test that translated unit."""
|
"""Test that translated unit."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.entity_platform.translation.async_get_translations",
|
||||||
return_value={
|
return_value={
|
||||||
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
||||||
},
|
},
|
||||||
@@ -664,7 +664,7 @@ async def test_unit_translation_key_without_platform_raises(
|
|||||||
"""Test that unit translation key property raises if the entity has no platform yet."""
|
"""Test that unit translation key property raises if the entity has no platform yet."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.entity_platform.translation.async_get_translations",
|
||||||
return_value={
|
return_value={
|
||||||
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
"component.test.entity.sensor.test_translation_key.unit_of_measurement": "Tests"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,18 +2,13 @@
|
|||||||
# name: test_get_services
|
# name: test_get_services
|
||||||
dict({
|
dict({
|
||||||
'reload': dict({
|
'reload': dict({
|
||||||
'description': 'Reloads group configuration, entities, and notify services from YAML-configuration.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
}),
|
}),
|
||||||
'name': 'Reload',
|
|
||||||
}),
|
}),
|
||||||
'remove': dict({
|
'remove': dict({
|
||||||
'description': 'Removes a group.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
'object_id': dict({
|
'object_id': dict({
|
||||||
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
|
|
||||||
'example': 'test_group',
|
'example': 'test_group',
|
||||||
'name': 'Object ID',
|
|
||||||
'required': True,
|
'required': True,
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'object': dict({
|
'object': dict({
|
||||||
@@ -22,15 +17,11 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': 'Remove',
|
|
||||||
}),
|
}),
|
||||||
'set': dict({
|
'set': dict({
|
||||||
'description': 'Creates/Updates a group.',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
'add_entities': dict({
|
'add_entities': dict({
|
||||||
'description': 'List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Add entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -39,17 +30,13 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'all': dict({
|
'all': dict({
|
||||||
'description': 'Enable this option if the group should only be used when all entities are in state `on`.',
|
|
||||||
'name': 'All',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'boolean': dict({
|
'boolean': dict({
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'entities': dict({
|
'entities': dict({
|
||||||
'description': 'List of all members in the group. Cannot be used in combination with `Add entities` or `Remove entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -58,18 +45,14 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'icon': dict({
|
'icon': dict({
|
||||||
'description': 'Name of the icon for the group.',
|
|
||||||
'example': 'mdi:camera',
|
'example': 'mdi:camera',
|
||||||
'name': 'Icon',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'icon': dict({
|
'icon': dict({
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': dict({
|
'name': dict({
|
||||||
'description': 'Name of the group.',
|
|
||||||
'example': 'My test group',
|
'example': 'My test group',
|
||||||
'name': 'Name',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'text': dict({
|
'text': dict({
|
||||||
'multiline': False,
|
'multiline': False,
|
||||||
@@ -78,9 +61,7 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'object_id': dict({
|
'object_id': dict({
|
||||||
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
|
|
||||||
'example': 'test_group',
|
'example': 'test_group',
|
||||||
'name': 'Object ID',
|
|
||||||
'required': True,
|
'required': True,
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'text': dict({
|
'text': dict({
|
||||||
@@ -90,9 +71,7 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'remove_entities': dict({
|
'remove_entities': dict({
|
||||||
'description': 'List of members to be removed from a group. Cannot be used in combination with `Entities` or `Add entities`.',
|
|
||||||
'example': 'domain.entity_id1, domain.entity_id2',
|
'example': 'domain.entity_id1, domain.entity_id2',
|
||||||
'name': 'Remove entities',
|
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'entity': dict({
|
'entity': dict({
|
||||||
'multiple': True,
|
'multiple': True,
|
||||||
@@ -101,7 +80,6 @@
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'name': 'Set',
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
@@ -132,10 +110,8 @@
|
|||||||
'name': 'Translated name',
|
'name': 'Translated name',
|
||||||
}),
|
}),
|
||||||
'set_level': dict({
|
'set_level': dict({
|
||||||
'description': '',
|
|
||||||
'fields': dict({
|
'fields': dict({
|
||||||
}),
|
}),
|
||||||
'name': '',
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
|||||||
@@ -784,10 +784,6 @@ async def test_get_services(
|
|||||||
"homeassistant.helpers.service._load_services_file",
|
"homeassistant.helpers.service._load_services_file",
|
||||||
side_effect=_load_services_file,
|
side_effect=_load_services_file,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
|
||||||
return_value={},
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
await websocket_client.send_json_auto_id({"type": "get_services"})
|
await websocket_client.send_json_auto_id({"type": "get_services"})
|
||||||
msg = await websocket_client.receive_json()
|
msg = await websocket_client.receive_json()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import io
|
import io
|
||||||
|
import threading
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
@@ -46,14 +47,13 @@ from homeassistant.helpers import (
|
|||||||
entity_registry as er,
|
entity_registry as er,
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.translation import async_get_translations
|
|
||||||
from homeassistant.loader import (
|
from homeassistant.loader import (
|
||||||
Integration,
|
Integration,
|
||||||
async_get_integration,
|
async_get_integration,
|
||||||
async_get_integrations,
|
async_get_integrations,
|
||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.yaml.loader import parse_yaml
|
from homeassistant.util.yaml.loader import JSON_TYPE, parse_yaml
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockEntity,
|
MockEntity,
|
||||||
@@ -849,7 +849,7 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert len(descriptions) == 1
|
assert len(descriptions) == 1
|
||||||
assert DOMAIN_GROUP in descriptions
|
assert DOMAIN_GROUP in descriptions
|
||||||
assert "description" in descriptions[DOMAIN_GROUP]["reload"]
|
assert "description" not in descriptions[DOMAIN_GROUP]["reload"]
|
||||||
assert "fields" in descriptions[DOMAIN_GROUP]["reload"]
|
assert "fields" in descriptions[DOMAIN_GROUP]["reload"]
|
||||||
|
|
||||||
# Does not have services
|
# Does not have services
|
||||||
@@ -857,26 +857,39 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
logger_config = {DOMAIN_LOGGER: {}}
|
logger_config = {DOMAIN_LOGGER: {}}
|
||||||
|
|
||||||
async def async_get_translations(
|
# Test legacy service with translations in services.yaml
|
||||||
hass: HomeAssistant,
|
def _load_services_file(integration: Integration) -> JSON_TYPE:
|
||||||
language: str,
|
|
||||||
category: str,
|
|
||||||
integrations: Iterable[str] | None = None,
|
|
||||||
config_flow: bool | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Return all backend translations."""
|
|
||||||
translation_key_prefix = f"component.{DOMAIN_LOGGER}.services.set_default_level"
|
|
||||||
return {
|
return {
|
||||||
f"{translation_key_prefix}.name": "Translated name",
|
"set_default_level": {
|
||||||
f"{translation_key_prefix}.description": "Translated description",
|
"description": "Translated description",
|
||||||
f"{translation_key_prefix}.fields.level.name": "Field name",
|
"fields": {
|
||||||
f"{translation_key_prefix}.fields.level.description": "Field description",
|
"level": {
|
||||||
f"{translation_key_prefix}.fields.level.example": "Field example",
|
"description": "Field description",
|
||||||
|
"example": "Field example",
|
||||||
|
"name": "Field name",
|
||||||
|
"selector": {
|
||||||
|
"select": {
|
||||||
|
"options": [
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"fatal",
|
||||||
|
"critical",
|
||||||
|
],
|
||||||
|
"translation_key": "level",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Translated name",
|
||||||
|
},
|
||||||
|
"set_level": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.service._load_services_file",
|
||||||
side_effect=async_get_translations,
|
side_effect=_load_services_file,
|
||||||
):
|
):
|
||||||
await async_setup_component(hass, DOMAIN_LOGGER, logger_config)
|
await async_setup_component(hass, DOMAIN_LOGGER, logger_config)
|
||||||
descriptions = await service.async_get_all_descriptions(hass)
|
descriptions = await service.async_get_all_descriptions(hass)
|
||||||
@@ -1003,18 +1016,11 @@ async def test_async_get_all_descriptions_dot_keys(hass: HomeAssistant) -> None:
|
|||||||
assert descriptions == {
|
assert descriptions == {
|
||||||
"test_domain": {
|
"test_domain": {
|
||||||
"test_service": {
|
"test_service": {
|
||||||
"description": "",
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"test": {
|
"test": {
|
||||||
"selector": {
|
"selector": {"text": {"multiline": False, "multiple": False}}
|
||||||
"text": {
|
|
||||||
"multiline": False,
|
|
||||||
"multiple": False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1096,7 +1102,6 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
test_service_schema = {
|
test_service_schema = {
|
||||||
"description": "",
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"advanced_stuff": {
|
"advanced_stuff": {
|
||||||
"fields": {
|
"fields": {
|
||||||
@@ -1155,7 +1160,6 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"name": "",
|
|
||||||
"target": {
|
"target": {
|
||||||
"entity": [
|
"entity": [
|
||||||
{
|
{
|
||||||
@@ -1191,31 +1195,11 @@ async def test_async_get_all_descriptions_failing_integration(
|
|||||||
integrations[DOMAIN_LOGGER] = ImportError("Failed to load services.yaml")
|
integrations[DOMAIN_LOGGER] = ImportError("Failed to load services.yaml")
|
||||||
return integrations
|
return integrations
|
||||||
|
|
||||||
async def wrap_get_translations(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
language: str,
|
|
||||||
category: str,
|
|
||||||
integrations: Iterable[str] | None = None,
|
|
||||||
config_flow: bool | None = None,
|
|
||||||
) -> dict[str, str]:
|
|
||||||
translations = await async_get_translations(
|
|
||||||
hass, language, category, integrations, config_flow
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
key: value
|
|
||||||
for key, value in translations.items()
|
|
||||||
if not key.startswith("component.logger.services.")
|
|
||||||
}
|
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.helpers.service.async_get_integrations",
|
"homeassistant.helpers.service.async_get_integrations",
|
||||||
wraps=wrap_get_integrations,
|
wraps=wrap_get_integrations,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
|
||||||
wrap_get_translations,
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
descriptions = await service.async_get_all_descriptions(hass)
|
descriptions = await service.async_get_all_descriptions(hass)
|
||||||
|
|
||||||
@@ -1224,16 +1208,12 @@ async def test_async_get_all_descriptions_failing_integration(
|
|||||||
|
|
||||||
# Services are empty defaults if the load fails but should
|
# Services are empty defaults if the load fails but should
|
||||||
# not raise
|
# not raise
|
||||||
assert descriptions[DOMAIN_GROUP]["remove"]["description"]
|
assert "description" not in descriptions[DOMAIN_GROUP]["remove"]
|
||||||
assert descriptions[DOMAIN_GROUP]["remove"]["fields"]
|
assert descriptions[DOMAIN_GROUP]["remove"]["fields"]
|
||||||
|
|
||||||
assert descriptions[DOMAIN_LOGGER]["set_level"] == {
|
assert descriptions[DOMAIN_LOGGER]["set_level"] == {"fields": {}}
|
||||||
"description": "",
|
|
||||||
"fields": {},
|
|
||||||
"name": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert descriptions[DOMAIN_INPUT_BUTTON]["press"]["description"]
|
assert "description" not in descriptions[DOMAIN_INPUT_BUTTON]["press"]
|
||||||
assert descriptions[DOMAIN_INPUT_BUTTON]["press"]["fields"] == {}
|
assert descriptions[DOMAIN_INPUT_BUTTON]["press"]["fields"] == {}
|
||||||
assert "target" in descriptions[DOMAIN_INPUT_BUTTON]["press"]
|
assert "target" in descriptions[DOMAIN_INPUT_BUTTON]["press"]
|
||||||
|
|
||||||
@@ -1288,7 +1268,7 @@ async def test_async_get_all_descriptions_dynamically_created_services(
|
|||||||
|
|
||||||
assert len(descriptions) == 1
|
assert len(descriptions) == 1
|
||||||
|
|
||||||
assert "description" in descriptions["group"]["reload"]
|
assert "description" not in descriptions["group"]["reload"]
|
||||||
assert "fields" in descriptions["group"]["reload"]
|
assert "fields" in descriptions["group"]["reload"]
|
||||||
|
|
||||||
shell_command_config = {DOMAIN_SHELL_COMMAND: {"test_service": "ls /bin"}}
|
shell_command_config = {DOMAIN_SHELL_COMMAND: {"test_service": "ls /bin"}}
|
||||||
@@ -1297,9 +1277,7 @@ async def test_async_get_all_descriptions_dynamically_created_services(
|
|||||||
|
|
||||||
assert len(descriptions) == 2
|
assert len(descriptions) == 2
|
||||||
assert descriptions[DOMAIN_SHELL_COMMAND]["test_service"] == {
|
assert descriptions[DOMAIN_SHELL_COMMAND]["test_service"] == {
|
||||||
"description": "",
|
|
||||||
"fields": {},
|
"fields": {},
|
||||||
"name": "",
|
|
||||||
"response": {"optional": True},
|
"response": {"optional": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1314,41 +1292,53 @@ async def test_async_get_all_descriptions_new_service_added_while_loading(
|
|||||||
|
|
||||||
assert len(descriptions) == 1
|
assert len(descriptions) == 1
|
||||||
|
|
||||||
assert "description" in descriptions["group"]["reload"]
|
assert "description" not in descriptions["group"]["reload"]
|
||||||
assert "fields" in descriptions["group"]["reload"]
|
assert "fields" in descriptions["group"]["reload"]
|
||||||
|
|
||||||
logger_domain = DOMAIN_LOGGER
|
logger_domain = DOMAIN_LOGGER
|
||||||
logger_config = {logger_domain: {}}
|
logger_config = {logger_domain: {}}
|
||||||
|
|
||||||
translations_called = asyncio.Event()
|
translations_called = threading.Event()
|
||||||
translations_wait = asyncio.Event()
|
translations_wait = threading.Event()
|
||||||
|
|
||||||
async def async_get_translations(
|
def _load_services_file(integration: Integration) -> JSON_TYPE:
|
||||||
hass: HomeAssistant,
|
|
||||||
language: str,
|
|
||||||
category: str,
|
|
||||||
integrations: Iterable[str] | None = None,
|
|
||||||
config_flow: bool | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Return all backend translations."""
|
|
||||||
translations_called.set()
|
translations_called.set()
|
||||||
await translations_wait.wait()
|
translations_wait.wait()
|
||||||
translation_key_prefix = f"component.{logger_domain}.services.set_default_level"
|
|
||||||
return {
|
return {
|
||||||
f"{translation_key_prefix}.name": "Translated name",
|
"set_default_level": {
|
||||||
f"{translation_key_prefix}.description": "Translated description",
|
"description": "Translated description",
|
||||||
f"{translation_key_prefix}.fields.level.name": "Field name",
|
"fields": {
|
||||||
f"{translation_key_prefix}.fields.level.description": "Field description",
|
"level": {
|
||||||
f"{translation_key_prefix}.fields.level.example": "Field example",
|
"description": "Field description",
|
||||||
|
"example": "Field example",
|
||||||
|
"name": "Field name",
|
||||||
|
"selector": {
|
||||||
|
"select": {
|
||||||
|
"options": [
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"fatal",
|
||||||
|
"critical",
|
||||||
|
],
|
||||||
|
"translation_key": "level",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Translated name",
|
||||||
|
},
|
||||||
|
"set_level": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.service.translation.async_get_translations",
|
"homeassistant.helpers.service._load_services_file",
|
||||||
side_effect=async_get_translations,
|
side_effect=_load_services_file,
|
||||||
):
|
):
|
||||||
await async_setup_component(hass, logger_domain, logger_config)
|
await async_setup_component(hass, logger_domain, logger_config)
|
||||||
task = asyncio.create_task(service.async_get_all_descriptions(hass))
|
task = asyncio.create_task(service.async_get_all_descriptions(hass))
|
||||||
await translations_called.wait()
|
await hass.async_add_executor_job(translations_called.wait)
|
||||||
# Now register a new service while translations are being loaded
|
# Now register a new service while translations are being loaded
|
||||||
hass.services.async_register(logger_domain, "new_service", lambda x: None, None)
|
hass.services.async_register(logger_domain, "new_service", lambda x: None, None)
|
||||||
service.async_set_service_schema(
|
service.async_set_service_schema(
|
||||||
|
|||||||
Reference in New Issue
Block a user