-
-
Notifications
You must be signed in to change notification settings - Fork 30.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
Clean up entity component #11691
Clean up entity component #11691
Conversation
if entity_ids: | ||
self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) | ||
else: | ||
self.tracking = [] |
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.
Above you create a tuple here you create a list.
return | ||
|
||
entity.hass = self.component.hass | ||
entity.platform = self |
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.
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?
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.
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.
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.
You could write protect some of the attributes with properties, if we think that's needed.
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.
This is Python, people can work around it. I'm not afraid of them assigning the platform attribute.
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.
Of course, we're all adults. I'm more thinking about mistakes. But I think it's ok like this.
We could reinstate 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) |
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.
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) |
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.
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.
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.
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 |
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.
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
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.
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.
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.
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.
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.
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) |
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.
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.
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 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
)
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.
btw, I am not against reinstating Entity.async_remove
. It would look like this: #11691 (comment)
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.
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.
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.
I change my mind. I think #11691 is a good solution 👍
I have leaf only a comment for the design of entity. Anyway. We need a mechanic like Later we can make a |
02a94a1
to
2938dbe
Compare
I've added Entity.async_remove back and added some tests for it. |
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 |
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 | ||
|
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.
I think we should extend this entity with:
async def async_added_to_hass(self):
pass
async def async_remove_from_hass(self):
pass
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.
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)
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.
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.
a8eaf11
to
dd0fc42
Compare
Marked as a breaking change due to removal of Entity.async_add_entity which might be used by custom components. |
Good point. Have updated the PR description. |
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:
entity.async_remove
EntityComponent.async_remove_entity
which delegates to correctEntityPlatform.async_remove_entity
Entity.async_will_remove_from_hass
for clean up tasks when an entity is removedEntityComponent.entities
is no longer a dictionary of all entities of that component. Instead it is an iterable of entities. (Most components useEntityComponent.extract_from_service
which remains unchanged)EntityComponent.get_entity(entity_id)
for camera, group, media_player and script. (to work around breaking change in above change)EntityPlatform.async_add_entities
when entity isNone
Group.start
,Group.stop
Breaking Change
EntityComponent.add_entity(entity)
andEntityComponent.async_add_entity(entity)
have been removed. UseEntityComponent.add_entities([entity])
andEntityComponent.async_add_entities([entity])
instead.CC @rcloran
Checklist:
If the code does not interact with devices:
tox
run successfully. Your PR cannot be merged unless tests pass