-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lazy loading of service descriptions #11479
Changes from 4 commits
2000bad
0e52af7
a1d0a89
94555de
aff3d73
ad57ef8
959a34a
aba4b18
c3e9fab
990fa1f
4fe0b96
136a8c5
d1cd820
9052ba6
e1b0a24
a162015
2ee3909
9b2ea2d
817f69a
38bccc5
e310c0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Describes the format for available scene services | ||
|
||
turn_on: | ||
description: Activate a scene. | ||
fields: | ||
entity_id: | ||
description: Name(s) of scenes to turn on | ||
example: 'scene.romantic' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -754,25 +754,15 @@ def async_set(self, entity_id, new_state, attributes=None, | |
class Service(object): | ||
"""Representation of a callable service.""" | ||
|
||
__slots__ = ['func', 'description', 'fields', 'schema', | ||
'is_callback', 'is_coroutinefunction'] | ||
__slots__ = ['func', 'schema', 'is_callback', 'is_coroutinefunction'] | ||
|
||
def __init__(self, func, description, fields, schema): | ||
def __init__(self, func, schema): | ||
"""Initialize a service.""" | ||
self.func = func | ||
self.description = description or '' | ||
self.fields = fields or {} | ||
self.schema = schema | ||
self.is_callback = is_callback(func) | ||
self.is_coroutinefunction = asyncio.iscoroutinefunction(func) | ||
|
||
def as_dict(self): | ||
"""Return dictionary representation of this service.""" | ||
return { | ||
'description': self.description, | ||
'fields': self.fields, | ||
} | ||
|
||
|
||
class ServiceCall(object): | ||
"""Representation of a call to a service.""" | ||
|
@@ -826,9 +816,7 @@ def async_services(self): | |
|
||
This method must be run in the event loop. | ||
""" | ||
return {domain: {key: value.as_dict() for key, value | ||
in self._services[domain].items()} | ||
for domain in self._services} | ||
return {domain: self._services[domain].copy() for domain in self._services} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line too long (83 > 79 characters) |
||
|
||
def has_service(self, domain, service): | ||
"""Test if specified service exists. | ||
|
@@ -842,9 +830,6 @@ def register(self, domain, service, service_func, description=None, | |
""" | ||
Register a service. | ||
|
||
Description is a dict containing key 'description' to describe | ||
the service and a key 'fields' to describe the fields. | ||
|
||
Schema is called to coerce and validate the service data. | ||
""" | ||
run_callback_threadsafe( | ||
|
@@ -859,18 +844,13 @@ def async_register(self, domain, service, service_func, description=None, | |
""" | ||
Register a service. | ||
|
||
Description is a dict containing key 'description' to describe | ||
the service and a key 'fields' to describe the fields. | ||
|
||
Schema is called to coerce and validate the service data. | ||
|
||
This method must be run in the event loop. | ||
""" | ||
domain = domain.lower() | ||
service = service.lower() | ||
description = description or {} | ||
service_obj = Service(service_func, description.get('description'), | ||
description.get('fields', {}), schema) | ||
service_obj = Service(service_func, schema) | ||
|
||
if domain in self._services: | ||
self._services[domain][service] = service_obj | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,15 @@ | |
import logging | ||
# pylint: disable=unused-import | ||
from typing import Optional # NOQA | ||
from os import path | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.const import ATTR_ENTITY_ID | ||
from homeassistant.core import HomeAssistant # NOQA | ||
import homeassistant.core as ha | ||
from homeassistant.exceptions import TemplateError | ||
from homeassistant.loader import get_component, bind_hass | ||
from homeassistant.config import load_yaml_config_file | ||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.util.async import run_coroutine_threadsafe | ||
|
||
|
@@ -21,6 +23,8 @@ | |
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DESCRIPTION_CACHE = {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't store the description cache in a global. Instead store it in |
||
|
||
|
||
@bind_hass | ||
def call_from_config(hass, config, blocking=False, variables=None, | ||
|
@@ -112,3 +116,48 @@ def extract_entity_ids(hass, service_call, expand_group=True): | |
return [service_ent_id] | ||
|
||
return service_ent_id | ||
|
||
|
||
@bind_hass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
def get_description(hass, domain, service): | ||
"""Return the description (i.e. user documentation) for a service call.""" | ||
|
||
if domain == ha.DOMAIN: | ||
import homeassistant.components as components | ||
file = components.__file__ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
else: | ||
file = get_component(domain).__file__ | ||
|
||
descriptions = DESCRIPTION_CACHE.get(file) | ||
if descriptions is None: | ||
descriptions = load_yaml_config_file( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This call will do I/O. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh. This was moved from the LIFX I am a bit confused by the main/executor transitions available. What is the proper way to schedule the function in this situation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the function that schedules the job for the executor, via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct Martin There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this file might not exist |
||
path.join(path.dirname(file), 'services.yaml')) | ||
DESCRIPTION_CACHE[file] = descriptions | ||
|
||
if domain == ha.DOMAIN: | ||
description = descriptions[domain].get(service, {}) | ||
else: | ||
description = descriptions.get(service, {}) | ||
|
||
return { | ||
'description': description.get('description', ''), | ||
'fields': description.get('fields', {}) | ||
} | ||
|
||
|
||
@asyncio.coroutine | ||
@bind_hass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
def async_get_all_descriptions(hass): | ||
"""Return the descriptions for all service calls.""" | ||
|
||
services = hass.services.async_services() | ||
descriptions = {} | ||
for domain in services: | ||
descriptions[domain] = {} | ||
for service in services[domain]: | ||
description = yield from hass.async_add_job( | ||
get_description, hass, domain, service) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we know that we're going to load all the descriptions, it would help if we preload the catch all services file ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should only be read and parsed once, that's what the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see now that you cache that file. I'm not sure if that is a great idea, it's big and a lot will be unused. |
||
if description: | ||
descriptions[domain][service] = description | ||
|
||
return descriptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name and docstring are stale. This function doesn't call a service.