Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these behave as properties or instance attributes, ie write protected or not?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are write protected because the class is annotated with frozen=True. The class is frozen after the post_init has run. It allows me to operate on the values entered before it's frozen.

you have to read the 2 methods from attr as attrib and attrs btw. Makes it easier to remember

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',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces attrs to the codebase as per home-assistant/architecture#4

]

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