mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Convert service helper to use async_get_integration (#23023)
* Convert service helper to use async_get_integration * Fix tests
This commit is contained in:
parent
c8375be4b1
commit
f7d4c48199
@ -2,7 +2,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
from os import path
|
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -11,12 +10,14 @@ from homeassistant.auth.permissions.const import POLICY_CONTROL
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ATTR_AREA_ID)
|
ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ATTR_AREA_ID)
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser
|
from homeassistant.exceptions import (
|
||||||
|
HomeAssistantError, TemplateError, Unauthorized, UnknownUser)
|
||||||
from homeassistant.helpers import template, typing
|
from homeassistant.helpers import template, typing
|
||||||
from homeassistant.loader import get_component, bind_hass
|
from homeassistant.loader import async_get_integration, bind_hass
|
||||||
from homeassistant.util.yaml import load_yaml
|
from homeassistant.util.yaml import load_yaml
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.async_ import run_coroutine_threadsafe
|
from homeassistant.util.async_ import run_coroutine_threadsafe
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
CONF_SERVICE = 'service'
|
CONF_SERVICE = 'service'
|
||||||
CONF_SERVICE_TEMPLATE = 'service_template'
|
CONF_SERVICE_TEMPLATE = 'service_template'
|
||||||
@ -152,60 +153,68 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
|
|||||||
return extracted
|
return extracted
|
||||||
|
|
||||||
|
|
||||||
|
async def _load_services_file(hass: HomeAssistantType, domain: str):
|
||||||
|
"""Load services file for an integration."""
|
||||||
|
integration = await async_get_integration(hass, domain)
|
||||||
|
try:
|
||||||
|
return await hass.async_add_executor_job(
|
||||||
|
load_yaml, str(integration.file_path / 'services.yaml'))
|
||||||
|
except FileNotFoundError:
|
||||||
|
_LOGGER.warning("Unable to find services.yaml for the %s integration",
|
||||||
|
domain)
|
||||||
|
return {}
|
||||||
|
except HomeAssistantError:
|
||||||
|
_LOGGER.warning("Unable to parse services.yaml for the %s integration",
|
||||||
|
domain)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def async_get_all_descriptions(hass):
|
async def async_get_all_descriptions(hass):
|
||||||
"""Return descriptions (i.e. user documentation) for all service calls."""
|
"""Return descriptions (i.e. user documentation) for all service calls."""
|
||||||
if SERVICE_DESCRIPTION_CACHE not in hass.data:
|
descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
|
||||||
hass.data[SERVICE_DESCRIPTION_CACHE] = {}
|
|
||||||
description_cache = hass.data[SERVICE_DESCRIPTION_CACHE]
|
|
||||||
|
|
||||||
format_cache_key = '{}.{}'.format
|
format_cache_key = '{}.{}'.format
|
||||||
|
|
||||||
def domain_yaml_file(domain):
|
|
||||||
"""Return the services.yaml location for a domain."""
|
|
||||||
component_path = path.dirname(get_component(hass, domain).__file__)
|
|
||||||
return path.join(component_path, 'services.yaml')
|
|
||||||
|
|
||||||
def load_services_files(yaml_files):
|
|
||||||
"""Load and parse services.yaml files."""
|
|
||||||
loaded = {}
|
|
||||||
for yaml_file in yaml_files:
|
|
||||||
try:
|
|
||||||
loaded[yaml_file] = load_yaml(yaml_file)
|
|
||||||
except FileNotFoundError:
|
|
||||||
loaded[yaml_file] = {}
|
|
||||||
|
|
||||||
return loaded
|
|
||||||
|
|
||||||
services = hass.services.async_services()
|
services = hass.services.async_services()
|
||||||
|
|
||||||
# Load missing files
|
# See if there are new services not seen before.
|
||||||
|
# Any service that we saw before already has an entry in description_cache.
|
||||||
missing = set()
|
missing = set()
|
||||||
for domain in services:
|
for domain in services:
|
||||||
for service in services[domain]:
|
for service in services[domain]:
|
||||||
if format_cache_key(domain, service) not in description_cache:
|
if format_cache_key(domain, service) not in descriptions_cache:
|
||||||
missing.add(domain_yaml_file(domain))
|
missing.add(domain)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Files we loaded for missing descriptions
|
||||||
|
loaded = {}
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
loaded = await hass.async_add_job(load_services_files, missing)
|
contents = await asyncio.gather(*[
|
||||||
|
_load_services_file(hass, domain) for domain in missing
|
||||||
|
])
|
||||||
|
|
||||||
|
for domain, content in zip(missing, contents):
|
||||||
|
loaded[domain] = content
|
||||||
|
|
||||||
# Build response
|
# Build response
|
||||||
descriptions = {}
|
descriptions = {}
|
||||||
for domain in services:
|
for domain in services:
|
||||||
descriptions[domain] = {}
|
descriptions[domain] = {}
|
||||||
yaml_file = domain_yaml_file(domain)
|
|
||||||
|
|
||||||
for service in services[domain]:
|
for service in services[domain]:
|
||||||
cache_key = format_cache_key(domain, service)
|
cache_key = format_cache_key(domain, service)
|
||||||
description = description_cache.get(cache_key)
|
description = descriptions_cache.get(cache_key)
|
||||||
|
|
||||||
# Cache missing descriptions
|
# Cache missing descriptions
|
||||||
if description is None:
|
if description is None:
|
||||||
yaml_services = loaded[yaml_file]
|
domain_yaml = loaded[domain]
|
||||||
yaml_description = yaml_services.get(service, {})
|
yaml_description = domain_yaml.get(service, {})
|
||||||
|
|
||||||
description = description_cache[cache_key] = {
|
if not yaml_description:
|
||||||
|
_LOGGER.warning("Missing service description for %s/%s",
|
||||||
|
domain, service)
|
||||||
|
|
||||||
|
description = descriptions_cache[cache_key] = {
|
||||||
'description': yaml_description.get('description', ''),
|
'description': yaml_description.get('description', ''),
|
||||||
'fields': yaml_description.get('fields', {})
|
'fields': yaml_description.get('fields', {})
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,8 @@ class Integration:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
hass, "{}.{}".format(root_module.__name__, domain), manifest
|
hass, "{}.{}".format(root_module.__name__, domain),
|
||||||
|
manifest_path.parent, manifest
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@ -105,13 +106,16 @@ class Integration:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
hass, comp.__name__, manifest_from_legacy_module(comp)
|
hass, comp.__name__, pathlib.Path(comp.__file__).parent,
|
||||||
|
manifest_from_legacy_module(comp)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hass: 'HomeAssistant', pkg_path: str, manifest: Dict):
|
def __init__(self, hass: 'HomeAssistant', pkg_path: str,
|
||||||
|
file_path: pathlib.Path, manifest: Dict):
|
||||||
"""Initialize an integration."""
|
"""Initialize an integration."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.pkg_path = pkg_path
|
self.pkg_path = pkg_path
|
||||||
|
self.file_path = file_path
|
||||||
self.name = manifest['name'] # type: str
|
self.name = manifest['name'] # type: str
|
||||||
self.domain = manifest['domain'] # type: str
|
self.domain = manifest['domain'] # type: str
|
||||||
self.dependencies = manifest['dependencies'] # type: List[str]
|
self.dependencies = manifest['dependencies'] # type: List[str]
|
||||||
|
@ -904,7 +904,7 @@ async def get_system_health_info(hass, domain):
|
|||||||
def mock_integration(hass, module):
|
def mock_integration(hass, module):
|
||||||
"""Mock an integration."""
|
"""Mock an integration."""
|
||||||
integration = loader.Integration(
|
integration = loader.Integration(
|
||||||
hass, 'homeassisant.components.{}'.format(module.DOMAIN),
|
hass, 'homeassisant.components.{}'.format(module.DOMAIN), None,
|
||||||
loader.manifest_from_legacy_module(module))
|
loader.manifest_from_legacy_module(module))
|
||||||
|
|
||||||
_LOGGER.info("Adding mock integration: %s", module.DOMAIN)
|
_LOGGER.info("Adding mock integration: %s", module.DOMAIN)
|
||||||
|
@ -164,12 +164,13 @@ async def test_get_integration_custom_component(hass):
|
|||||||
|
|
||||||
def test_integration_properties(hass):
|
def test_integration_properties(hass):
|
||||||
"""Test integration properties."""
|
"""Test integration properties."""
|
||||||
integration = loader.Integration(hass, 'homeassistant.components.hue', {
|
integration = loader.Integration(
|
||||||
'name': 'Philips Hue',
|
hass, 'homeassistant.components.hue', None, {
|
||||||
'domain': 'hue',
|
'name': 'Philips Hue',
|
||||||
'dependencies': ['test-dep'],
|
'domain': 'hue',
|
||||||
'requirements': ['test-req==1.0.0'],
|
'dependencies': ['test-dep'],
|
||||||
})
|
'requirements': ['test-req==1.0.0'],
|
||||||
|
})
|
||||||
assert integration.name == "Philips Hue"
|
assert integration.name == "Philips Hue"
|
||||||
assert integration.domain == 'hue'
|
assert integration.domain == 'hue'
|
||||||
assert integration.dependencies == ['test-dep']
|
assert integration.dependencies == ['test-dep']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user