Skip to content

Commit

Permalink
Allow overriding name via entity registry (#12292)
Browse files Browse the repository at this point in the history
* Allow overriding name via entity registry

* Update requirements
  • Loading branch information
balloob committed Feb 12, 2018
1 parent 56b185f commit bfd9a5a
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 21 deletions.
1 change: 0 additions & 1 deletion homeassistant/components/remote/xiaomi_miio.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ def async_turn_off(self, **kwargs):
_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
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -225,7 +228,7 @@ def async_update_ha_state(self, force_refresh=False):
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

Expand Down
10 changes: 5 additions & 5 deletions homeassistant/helpers/entity_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ def __init__(self, logger, domain, hass,
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):
Expand Down Expand Up @@ -190,7 +190,7 @@ def _async_reset(self):
yield from asyncio.wait(tasks, loop=self.hass.loop)

self._platforms = {
'core': self._platforms['core']
self.domain: self._platforms[self.domain]
}
self.config = None

Expand Down
1 change: 1 addition & 0 deletions homeassistant/helpers/entity_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def _async_add_entity(self, entity, update_before_add, component_entities,
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
Expand Down
30 changes: 22 additions & 8 deletions homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,37 @@
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."""

Expand Down Expand Up @@ -65,11 +80,10 @@ def async_get_or_create(self, domain, platform, unique_id, *,

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',
Expand Down Expand Up @@ -98,11 +112,11 @@ def _async_load(self):
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
Expand Down
1 change: 1 addition & 0 deletions homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
'chardet==3.0.4',
'astral==1.5',
'certifi>=2017.4.17',
'attrs==17.4.0',
]

MIN_PY_VERSION = '.'.join(map(
Expand Down
4 changes: 2 additions & 2 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 22 additions & 1 deletion tests/helpers/test_entity_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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

Expand Down Expand Up @@ -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'
35 changes: 32 additions & 3 deletions tests/helpers/test_entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from tests.common import mock_registry


YAML__OPEN_PATH = 'homeassistant.util.yaml.open'


@pytest.fixture
def registry(hass):
"""Return an empty, loaded, registry."""
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

0 comments on commit bfd9a5a

Please sign in to comment.