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 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', {})
} }

View File

@ -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]

View File

@ -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)

View File

@ -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']