Add Dynalite switch platform#32389
Conversation
Codecov Report
@@ Coverage Diff @@
## dev #32389 +/- ##
==========================================
+ Coverage 94.75% 94.75% +<.01%
==========================================
Files 776 777 +1
Lines 56174 56195 +21
==========================================
+ Hits 53227 53248 +21
Misses 2947 2947
Continue to review full report at Codecov.
|
| """Record the async_add_entities function to add them later when received from Dynalite.""" | ||
|
|
||
| @callback | ||
| def switch_from_device(device, bridge): |
There was a problem hiding this comment.
Why even this and not pass DynaliteSwitch directly ?
There was a problem hiding this comment.
what do you mean? pass it in the function?
async_setup_entry_base(
hass, config_entry, async_add_entities, "switch", DynaliteSwitch
)
There was a problem hiding this comment.
done. also in light.py
| vol.Optional(CONF_DEFAULT): PLATFORM_DEFAULTS_SCHEMA, | ||
| vol.Optional(CONF_ACTIVE, default=False): vol.Coerce(bool), | ||
| vol.Optional(CONF_ACTIVE, default=False): vol.Any( | ||
| CONF_ACTIVE_ON, CONF_ACTIVE_OFF, CONF_ACTIVE_INIT, bool |
There was a problem hiding this comment.
bool should probably be cv.boolean
There was a problem hiding this comment.
Also, isn't it confusing that you allow string values and boolean values here ?
There was a problem hiding this comment.
I wanted to keep it backwards compatible since there is a third option. I can move it to only the CONF_* as true and false get routed to on and off
| return await self.dynalite_devices.async_setup() | ||
|
|
||
| async def reload_config(self, config): | ||
| def reload_config(self, config): |
There was a problem hiding this comment.
We're still awaiting this function in the entry update listener.
There was a problem hiding this comment.
I know. the reload_config is a sync function, and the library is built in a way that reload_config can be called at any time, as long as it is called from the same thread of the event loop.
However, I didn't like this too much either, as it is confusing, but couldn't find another solution, so definitely open to any suggestions. My problem is that if the entry is already defined, but the config has changed, I need to call the update. However, the bridge is not yet fully set up at this point as this happens in parallel. If there is a way to wait until the config entry is set up before loading the new config from configuration.yaml, then I would only call it at that time. However, all the solutions I could think off were even uglier, so would appreciate any insight on another component that is doing it right and I could use as reference.
Note that it does work and I couldnt find any bugs in the flow, but it is not the best design pattern and will happily change it if I can find a cleaner solution
There was a problem hiding this comment.
I don't think this can work. It's not possible to await non awaitable functions.
There was a problem hiding this comment.
Why not? reload_config does not await on anything. It only updates internal data structures, which are designed that they could change during any await in the async_setup (or others), so it does not depend on async_setup being completed.
That said, I like your suggestion below, which will make it possible to write a clean code that does not require explanations (and therefore also safer). Will give it a shot tonight
There was a problem hiding this comment.
We're still awaiting this function, that's the problem.
There was a problem hiding this comment.
you are correct. my mistake. fixed in the PR I am preparing
There was a problem hiding this comment.
just found out why it wasn't caught in the tests. it still behaved "ok" since it evaluated "bridge.reload_config(entry.data)" as it is a sync function. then it tried to await its return value, None, and threw an assertion. However, since this is the last meaningful statement (except a log), the test still showed that everything happened.
BTW, I believe the code would also behave well in that case except for the log about the exception, but obviously i fixed it to remove the await.
I was wondering if there is a way to make pytest fail in case there are uncaught exception, not awaited coroutines, etc. to avoid these instances and verify a cleaner code
There was a problem hiding this comment.
Non awaited coroutines should show up in the warning log when running pytest I think.
There was a problem hiding this comment.
But here it's the other way, right? We're awaiting when we shouldn't. Not sure about that one.
There was a problem hiding this comment.
that is correct. it is not a good pattern anyway and i fixed it. It throws an exception. The problem is that the test succeeds. I would like a way to fail it on assertions / non awaited, etc. as i shouldn't have them in the code
BTW, it can be a bit tricky since i get many tests with an uncaught exception for persistent_notification/create:
ERROR homeassistant.core:core.py:143 Error doing job: Task exception was never retrieved
Traceback (most recent call last):
File "/home/ziv/switch/home-assistant/homeassistant/core.py", line 1209, in async_call
raise ServiceNotFound(domain, service) from None
homeassistant.exceptions.ServiceNotFound: Unable to find service persistent_notification/dismiss
I am not doing anything directly with persistent_notification, but it does seem to me that tests should complete with no warnings at all
| bridge = DynaliteBridge(hass, entry.data) | ||
| # need to do it before the listener | ||
| hass.data[DOMAIN][entry.entry_id] = bridge | ||
| entry.add_update_listener(async_entry_changed) |
There was a problem hiding this comment.
Why add the listener before the bridge is set up?
There was a problem hiding this comment.
For the same reason listed above. If the entry was already set up with some config, but in the next restart of HA, the configuration.yaml file has changed, the listener will need to be called as the entry is being changed and it is happening in parallel with the setup of the original entry, which has not completed yet. If I can find a better solution for the first one, will hopefully be also able to solve this
There was a problem hiding this comment.
Perhaps store the bridge.async_setup task, id it with the config entry id and await the task before updating the entry.
There was a problem hiding this comment.
that could work. Should i store it in hass.data[entry_id]?
There was a problem hiding this comment.
i removed the await and it should be ok now. When I try this, it complicates things since if I await, it is still not guaranteed that the hass.data[] has been updated, depending on which gets executed first
There was a problem hiding this comment.
Let's discuss more in the PR for this.
Breaking change
Not a breaking change - backwards compatible
Proposed change
this is the next phase in the Dynalite integration. Now that the component is refactored, adding the switch is simple
Type of change
Example entry for
configuration.yaml:Additional information
Checklist
black --fast homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all..coveragerc.The integration reached or maintains the following Integration Quality Scale: