Skip to content
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

Clean up entity component #11691

Merged
merged 5 commits into from
Jan 23, 2018
Merged

Clean up entity component #11691

merged 5 commits into from
Jan 23, 2018

Conversation

balloob
Copy link
Member

@balloob balloob commented Jan 16, 2018

Description:

First off: I'm sorry, this became quite a lengthy PR. It is meant as a clean up round as preparation for #11533.

Overview of the changes:
The responsibility for managing the lifecycle of an entity was spread out over EntityComponent, EntityPlatform and Entity. This PR brings all the responsibility of managing the lifecycle to EntityPlatform. EntityComponent is now only handling a collection of EntityPlatforms and some helper functions (config, discovery, extract entities from service).

Although I have done my due diligence, this PR might break some components that do weird things and are not covered by tests.

Here are the changes:

  • Remove entity.async_remove
  • Add method EntityComponent.async_remove_entity which delegates to correct EntityPlatform.async_remove_entity
  • Add lifecycle hook Entity.async_will_remove_from_hass for clean up tasks when an entity is removed
  • Rewrite group to use EntityComponent so lifecycles are getting called
  • EntityComponent.entities is no longer a dictionary of all entities of that component. Instead it is an iterable of entities. (Most components use EntityComponent.extract_from_service which remains unchanged)
  • Introduced EntityComponent.get_entity(entity_id) for camera, group, media_player and script. (to work around breaking change in above change)
  • Raise in EntityPlatform.async_add_entities when entity is None
  • Remove unused methods Group.start, Group.stop

Breaking Change

EntityComponent.add_entity(entity) and EntityComponent.async_add_entity(entity) have been removed. Use EntityComponent.add_entities([entity]) and EntityComponent.async_add_entities([entity]) instead.

CC @rcloran

Checklist:

If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • Tests have been added to verify that the new code works.

@balloob balloob requested a review from a team as a code owner January 16, 2018 08:12
if entity_ids:
self.tracking = tuple(ent_id.lower() for ent_id in entity_ids)
else:
self.tracking = []
Copy link
Member

Choose a reason for hiding this comment

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

Above you create a tuple here you create a list.

return

entity.hass = self.component.hass
entity.platform = self
Copy link
Member

Choose a reason for hiding this comment

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

This allows access to the platform from all platform entities but also allows the entities to change attributes of the platform. Powerful but maybe dangerous?

Copy link
Member Author

Choose a reason for hiding this comment

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

They already have access to hass. But I guess you're right in that it might allow people to directly interact with entities from other platforms.

The reason I wanted to add this is to allow platforms to be able to remove entities which is something @rcloran was looking at in a recent PR.

Copy link
Member

Choose a reason for hiding this comment

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

You could write protect some of the attributes with properties, if we think that's needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is Python, people can work around it. I'm not afraid of them assigning the platform attribute.

Copy link
Member

@MartinHjelmare MartinHjelmare Jan 16, 2018

Choose a reason for hiding this comment

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

Of course, we're all adults. I'm more thinking about mistakes. But I think it's ok like this.

@balloob
Copy link
Member Author

balloob commented Jan 16, 2018

We could reinstate Entity.async_remove if we want, but I'm not sure if it's necessary:

def async_remove(self):
    if self.platform is not None:
        self.platform.remove_entity(self.entity_id)
    else:
        self.hass.states.async_remove(self.entity_id)

@balloob balloob mentioned this pull request Jan 16, 2018
9 tasks
@andrey-git andrey-git self-requested a review January 17, 2018 08:57
Copy link
Member

@pvizeli pvizeli left a comment

Choose a reason for hiding this comment

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

I would prefere for remove things or handle dynamic platform renew stuff, we should make a mechanic like discovery that allow a component to remove all entity from a platform or only a single entity over our bus without we prefere to store all stuff into hass.data. I like hass.data for handle global things but at the moment hass.data would be use for all things and allow to make bad stuff instead to use cool things like the bus or dispatcher and handling abstraction better.

But I like 99% of that code 😄

def component_platform_discovered(platform, info):
"""Handle the loading of a platform."""
self.hass.async_add_job(
self._async_setup_platform(platform, {}, info))
yield from self._async_setup_platform(platform, {}, info)
Copy link
Member

Choose a reason for hiding this comment

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

So it look better but I think before it was faster. Ben mean that a coro they wait of a other coro without any reason is slower than only schedule a job.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that we're micro-optimizing here and it is costing us meaningful stack traces.

return

entity.hass = self.component.hass
entity.platform = self
Copy link
Member

@pvizeli pvizeli Jan 17, 2018

Choose a reason for hiding this comment

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

I think that is not needed or we should not do it for our software designe. We should add the platform to setup_platform(..., platform=None) so we need not store the entities anymore in hass.data for own service handling. At the end, the platform need handling the entities and need access to his platform and not the entity

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't want to change the method signature of platform setup. That will break all platforms.

One thing we could do (and it feels like a hack), is to make EntityPlatform callable and behave like async_add_devices for async setup.

Copy link
Member Author

Choose a reason for hiding this comment

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

Current responsibilities:

  • EntityComponent: responsible for managing platforms, group of all entities in all platforms, discovery, extracting entities from services
  • EntityPlatform: responsible for managing the entities of a platform and keeping them up to date
  • Entity: responsible for representing an entity in the state machine

EntityPlatform already knows about it's parent EntityComponent to notify that the group needs updating.

The alternative to the entity not knowing the platform it belongs to is using an event to have the entity signal that something should happen. However, in the case of remove, it feels like we're abusing an event system: it is a call for action instead of something that did happen.

Copy link
Member

@pvizeli pvizeli Jan 20, 2018

Choose a reason for hiding this comment

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

Yes, you are right.

Maybe we find in future a nice way that a platform have access to his platform i.e. for handle services.

if hasattr(entity, 'async_will_remove_from_hass'):
yield from entity.async_will_remove_from_hass()

self.component.hass.states.async_remove(entity_id)
Copy link
Member

@pvizeli pvizeli Jan 17, 2018

Choose a reason for hiding this comment

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

I think we should not remove the old async_remove from entity. We can call it on this please instead to make a new hidden function. for async_update and maybe async_added_to_hass is that okay but I prefere (for software designe) a overwritte able exists function on base class that every one can see it and can overwrite it (maybe with pass). The enity.async_remove was a nice overwrite for it self. He add it to state machine and he remove it from statemachine.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason I removed Entity.async_remove is because the platform is responsible for managing the entities of a platform. The entity cannot just decide to skip out without notifying the platform.

To help entities be notified of being removed, I've added the async_will_remove_from_hass hook. (similar to async_added_to_hass)

Copy link
Member Author

Choose a reason for hiding this comment

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

btw, I am not against reinstating Entity.async_remove. It would look like this: #11691 (comment)

Copy link
Member

@pvizeli pvizeli Jan 20, 2018

Choose a reason for hiding this comment

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

Maybe async_remove_from_hass. And we should add this method with pass to entity for the design. I think async_update should be the only function for look at it. This help also on async to can make a flip to other task and speed up the parallel. I don't think we lost some things with make coroutine with pass and it make it easyer to read.

I'm fine with this change with comment above but the #11691 is bad.

Copy link
Member

Choose a reason for hiding this comment

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

I change my mind. I think #11691 is a good solution 👍

@andrey-git andrey-git removed their request for review January 17, 2018 19:32
@pvizeli
Copy link
Member

pvizeli commented Jan 20, 2018

I have leaf only a comment for the design of entity.

Anyway. We need a mechanic like discovery for handling remove stuff in a nice way. A component can discovery platforms and for reload he can remove the platform/entity over the eventbus without store stuff to hass.data.

Later we can make a remove like setup (bootstrap - component) and we can make components they can setup or remove on runtime.

@balloob
Copy link
Member Author

balloob commented Jan 21, 2018

I've added Entity.async_remove back and added some tests for it.

@balloob
Copy link
Member Author

balloob commented Jan 21, 2018

The problem with a remove a-la discovery is that we don't know where to route it to. I guess we can add a remove_platform(…) as a counter to setup_platform(…). But then again, when would we trigger this?

@pvizeli
Copy link
Member

pvizeli commented Jan 22, 2018

We trigger that on EntityComponent like we do now with discovery... So we can call the platform remove of a single entity or all entities.

hass = None # type: Optional[HomeAssistant]

# Owning platform instance. Will be set by EntityPlatform
platform = None

Copy link
Member

Choose a reason for hiding this comment

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

I think we should extend this entity with:

async def async_added_to_hass(self):
    pass

async def async_remove_from_hass(self):
    pass

Copy link
Member Author

Choose a reason for hiding this comment

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

I rather have us check if it exists so that we don't need an extra yield? (we can do this in a later PR, will merge now)

Copy link
Member

Choose a reason for hiding this comment

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

I think it make the object more understandable and help to give extra time for loop to handle parallel tasks. Like OS scheduler, more small task are better as long running tasks.

@balloob balloob merged commit 183e054 into dev Jan 23, 2018
@balloob balloob deleted the cleanup-entity-component branch January 23, 2018 06:54
@andrey-git
Copy link
Contributor

Marked as a breaking change due to removal of Entity.async_add_entity which might be used by custom components.

@balloob
Copy link
Member Author

balloob commented Jan 24, 2018

Good point. Have updated the PR description.

@balloob balloob mentioned this pull request Jan 26, 2018
@home-assistant home-assistant locked and limited conversation to collaborators May 29, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants