Allow overriding name via entity registry (#12292)

* Allow overriding name via entity registry

* Update requirements
This commit is contained in:
Paulus Schoutsen 2018-02-11 09:16:01 -08:00 committed by GitHub
parent 8b9eab196c
commit 17e5740a0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 91 additions and 21 deletions

View file

@ -226,7 +226,6 @@ class XiaomiMiioRemote(RemoteDevice):
_LOGGER.error("Device does not support turn_off, " +
"please use 'remote.send_command' to send commands.")
# pylint: enable=R0201
def _send_command(self, payload):
"""Send a command."""
from miio import DeviceException

View file

@ -80,6 +80,9 @@ class Entity(object):
# Process updates in parallel
parallel_updates = None
# Name in the entity registry
registry_name = None
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
@ -225,7 +228,7 @@ class Entity(object):
if unit_of_measurement is not None:
attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
name = self.name
name = self.registry_name or self.name
if name is not None:
attr[ATTR_FRIENDLY_NAME] = name

View file

@ -40,19 +40,19 @@ class EntityComponent(object):
self.config = None
self._platforms = {
'core': EntityPlatform(
domain: EntityPlatform(
hass=hass,
logger=logger,
domain=domain,
platform_name='core',
platform_name=domain,
scan_interval=self.scan_interval,
parallel_updates=0,
entity_namespace=None,
async_entities_added_callback=self._async_update_group,
)
}
self.async_add_entities = self._platforms['core'].async_add_entities
self.add_entities = self._platforms['core'].add_entities
self.async_add_entities = self._platforms[domain].async_add_entities
self.add_entities = self._platforms[domain].add_entities
@property
def entities(self):
@ -190,7 +190,7 @@ class EntityComponent(object):
yield from asyncio.wait(tasks, loop=self.hass.loop)
self._platforms = {
'core': self._platforms['core']
self.domain: self._platforms[self.domain]
}
self.config = None

View file

@ -213,6 +213,7 @@ class EntityPlatform(object):
self.domain, self.platform_name, entity.unique_id,
suggested_object_id=suggested_object_id)
entity.entity_id = entry.entity_id
entity.registry_name = entry.name
# We won't generate an entity ID if the platform has already set one
# We will however make sure that platform cannot pick a registered ID

View file

@ -11,22 +11,37 @@ After initializing, call EntityRegistry.async_ensure_loaded to load the data
from disk.
"""
import asyncio
from collections import namedtuple, OrderedDict
from collections import OrderedDict
from itertools import chain
import logging
import os
import attr
from ..core import callback, split_entity_id
from ..util import ensure_unique_string, slugify
from ..util.yaml import load_yaml, save_yaml
PATH_REGISTRY = 'entity_registry.yaml'
SAVE_DELAY = 10
Entry = namedtuple('EntityRegistryEntry',
'entity_id,unique_id,platform,domain')
_LOGGER = logging.getLogger(__name__)
@attr.s(slots=True, frozen=True)
class RegistryEntry:
"""Entity Registry Entry."""
entity_id = attr.ib(type=str)
unique_id = attr.ib(type=str)
platform = attr.ib(type=str)
name = attr.ib(type=str, default=None)
domain = attr.ib(type=str, default=None, init=False, repr=False)
def __attrs_post_init__(self):
"""Computed properties."""
object.__setattr__(self, "domain", split_entity_id(self.entity_id)[0])
class EntityRegistry:
"""Class to hold a registry of entities."""
@ -65,11 +80,10 @@ class EntityRegistry:
entity_id = self.async_generate_entity_id(
domain, suggested_object_id or '{}_{}'.format(platform, unique_id))
entity = Entry(
entity = RegistryEntry(
entity_id=entity_id,
unique_id=unique_id,
platform=platform,
domain=domain,
)
self.entities[entity_id] = entity
_LOGGER.info('Registered new %s.%s entity: %s',
@ -98,11 +112,11 @@ class EntityRegistry:
data = yield from self.hass.async_add_job(load_yaml, path)
for entity_id, info in data.items():
entities[entity_id] = Entry(
domain=split_entity_id(entity_id)[0],
entities[entity_id] = RegistryEntry(
entity_id=entity_id,
unique_id=info['unique_id'],
platform=info['platform']
platform=info['platform'],
name=info.get('name')
)
self.entities = entities

View file

@ -11,6 +11,7 @@ async_timeout==2.0.0
chardet==3.0.4
astral==1.5
certifi>=2017.4.17
attrs==17.4.0
# Breaks Python 3.6 and is not needed for our supported Pythons
enum34==1000000000.0.0

View file

@ -12,6 +12,7 @@ async_timeout==2.0.0
chardet==3.0.4
astral==1.5
certifi>=2017.4.17
attrs==17.4.0
# homeassistant.components.nuimo_controller
--only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0

View file

@ -61,6 +61,7 @@ REQUIRES = [
'chardet==3.0.4',
'astral==1.5',
'certifi>=2017.4.17',
'attrs==17.4.0',
]
MIN_PY_VERSION = '.'.join(map(

View file

@ -317,10 +317,10 @@ def mock_component(hass, component):
hass.config.components.add(component)
def mock_registry(hass):
def mock_registry(hass, mock_entries=None):
"""Mock the Entity Registry."""
registry = entity_registry.EntityRegistry(hass)
registry.entities = {}
registry.entities = mock_entries or {}
hass.data[entity_platform.DATA_REGISTRY] = registry
return registry

View file

@ -9,7 +9,7 @@ import homeassistant.loader as loader
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_component import (
EntityComponent, DEFAULT_SCAN_INTERVAL)
from homeassistant.helpers import entity_platform
from homeassistant.helpers import entity_platform, entity_registry
import homeassistant.util.dt as dt_util
@ -433,3 +433,24 @@ def test_entity_with_name_and_entity_id_getting_registered(hass):
MockEntity(unique_id='1234', name='bla',
entity_id='test_domain.world')])
assert 'test_domain.world' in hass.states.async_entity_ids()
@asyncio.coroutine
def test_overriding_name_from_registry(hass):
"""Test that we can override a name via the Entity Registry."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
mock_registry(hass, {
'test_domain.world': entity_registry.RegistryEntry(
entity_id='test_domain.world',
unique_id='1234',
# Using component.async_add_entities is equal to platform "domain"
platform='test_domain',
name='Overridden'
)
})
yield from component.async_add_entities([
MockEntity(unique_id='1234', name='Device Name')])
state = hass.states.get('test_domain.world')
assert state is not None
assert state.name == 'Overridden'

View file

@ -9,6 +9,9 @@ from homeassistant.helpers import entity_registry
from tests.common import mock_registry
YAML__OPEN_PATH = 'homeassistant.util.yaml.open'
@pytest.fixture
def registry(hass):
"""Return an empty, loaded, registry."""
@ -82,13 +85,12 @@ def test_save_timer_reset_on_subsequent_save(hass, registry):
@asyncio.coroutine
def test_loading_saving_data(hass, registry):
"""Test that we load/save data correctly."""
yaml_path = 'homeassistant.util.yaml.open'
orig_entry1 = registry.async_get_or_create('light', 'hue', '1234')
orig_entry2 = registry.async_get_or_create('light', 'hue', '5678')
assert len(registry.entities) == 2
with patch(yaml_path, mock_open(), create=True) as mock_write:
with patch(YAML__OPEN_PATH, mock_open(), create=True) as mock_write:
yield from registry._async_save()
# Mock open calls are: open file, context enter, write, context leave
@ -98,7 +100,7 @@ def test_loading_saving_data(hass, registry):
registry2 = entity_registry.EntityRegistry(hass)
with patch('os.path.isfile', return_value=True), \
patch(yaml_path, mock_open(read_data=written), create=True):
patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True):
yield from registry2._async_load()
# Ensure same order
@ -133,3 +135,30 @@ def test_is_registered(registry):
entry = registry.async_get_or_create('light', 'hue', '1234')
assert registry.async_is_registered(entry.entity_id)
assert not registry.async_is_registered('light.non_existing')
@asyncio.coroutine
def test_loading_extra_values(hass):
"""Test we load extra data from the registry."""
written = """
test.named:
platform: super_platform
unique_id: with-name
name: registry override
test.no_name:
platform: super_platform
unique_id: without-name
"""
registry = entity_registry.EntityRegistry(hass)
with patch('os.path.isfile', return_value=True), \
patch(YAML__OPEN_PATH, mock_open(read_data=written), create=True):
yield from registry._async_load()
entry_with_name = registry.async_get_or_create(
'test', 'super_platform', 'with-name')
entry_without_name = registry.async_get_or_create(
'test', 'super_platform', 'without-name')
assert entry_with_name.name == 'registry override'
assert entry_without_name.name is None