Convert service helper to use async_get_integration (#23023)

* Convert service helper to use async_get_integration

* Fix tests
This commit is contained in:
Paulus Schoutsen 2019-04-12 10:09:17 -07:00 committed by GitHub
parent c8375be4b1
commit f7d4c48199
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 43 deletions

View file

@ -2,7 +2,6 @@
import asyncio
from functools import wraps
import logging
from os import path
from typing import Callable
import voluptuous as vol
@ -11,12 +10,14 @@ from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import (
ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ATTR_AREA_ID)
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.loader import get_component, bind_hass
from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe
from homeassistant.helpers.typing import HomeAssistantType
CONF_SERVICE = 'service'
CONF_SERVICE_TEMPLATE = 'service_template'
@ -152,60 +153,68 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
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
async def async_get_all_descriptions(hass):
"""Return descriptions (i.e. user documentation) for all service calls."""
if SERVICE_DESCRIPTION_CACHE not in hass.data:
hass.data[SERVICE_DESCRIPTION_CACHE] = {}
description_cache = hass.data[SERVICE_DESCRIPTION_CACHE]
descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
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()
# 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()
for domain in services:
for service in services[domain]:
if format_cache_key(domain, service) not in description_cache:
missing.add(domain_yaml_file(domain))
if format_cache_key(domain, service) not in descriptions_cache:
missing.add(domain)
break
# Files we loaded for missing descriptions
loaded = {}
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
descriptions = {}
for domain in services:
descriptions[domain] = {}
yaml_file = domain_yaml_file(domain)
for service in services[domain]:
cache_key = format_cache_key(domain, service)
description = description_cache.get(cache_key)
description = descriptions_cache.get(cache_key)
# Cache missing descriptions
if description is None:
yaml_services = loaded[yaml_file]
yaml_description = yaml_services.get(service, {})
domain_yaml = loaded[domain]
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', ''),
'fields': yaml_description.get('fields', {})
}

View file

@ -87,7 +87,8 @@ class Integration:
continue
return cls(
hass, "{}.{}".format(root_module.__name__, domain), manifest
hass, "{}.{}".format(root_module.__name__, domain),
manifest_path.parent, manifest
)
return None
@ -105,13 +106,16 @@ class Integration:
return None
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."""
self.hass = hass
self.pkg_path = pkg_path
self.file_path = file_path
self.name = manifest['name'] # type: str
self.domain = manifest['domain'] # type: str
self.dependencies = manifest['dependencies'] # type: List[str]

View file

@ -904,7 +904,7 @@ async def get_system_health_info(hass, domain):
def mock_integration(hass, module):
"""Mock an integration."""
integration = loader.Integration(
hass, 'homeassisant.components.{}'.format(module.DOMAIN),
hass, 'homeassisant.components.{}'.format(module.DOMAIN), None,
loader.manifest_from_legacy_module(module))
_LOGGER.info("Adding mock integration: %s", module.DOMAIN)

View file

@ -164,12 +164,13 @@ async def test_get_integration_custom_component(hass):
def test_integration_properties(hass):
"""Test integration properties."""
integration = loader.Integration(hass, 'homeassistant.components.hue', {
'name': 'Philips Hue',
'domain': 'hue',
'dependencies': ['test-dep'],
'requirements': ['test-req==1.0.0'],
})
integration = loader.Integration(
hass, 'homeassistant.components.hue', None, {
'name': 'Philips Hue',
'domain': 'hue',
'dependencies': ['test-dep'],
'requirements': ['test-req==1.0.0'],
})
assert integration.name == "Philips Hue"
assert integration.domain == 'hue'
assert integration.dependencies == ['test-dep']