Conversation
| assert "Weather Summary" == friendly_name | ||
| elif entity_id == 'sensor.pws_alerts': | ||
| assert 1 == device.state | ||
| assert 'This is a test alert message' == device.device_state_attributes['Message'] |
There was a problem hiding this comment.
line too long (94 > 79 characters)
| friendly_name = device.name | ||
|
|
||
| if entity_id == 'sensor.pws_weather': | ||
| assert 'https://icons.wxug.com/i/c/k/clear.gif' == device.entity_picture |
There was a problem hiding this comment.
line too long (84 > 79 characters)
| """Test that the component is loaded if passed in PWS Id.""" | ||
| aioclient_mock.get(URL, text=load_fixture('wunderground-valid.json')) | ||
| aioclient_mock.get(PWS_URL, text=load_fixture('wunderground-valid.json')) | ||
| aioclient_mock.get(INVALID_URL, text=load_fixture('wunderground-error.json')) |
There was a problem hiding this comment.
line too long (81 > 79 characters)
| def _build_url(self, baseurl=_RESOURCE): | ||
| url = baseurl.format( | ||
| self._api_key, "/".join(self._features), self._lang) | ||
| self._api_key, '/'.join(sorted(self._features)), self._lang) |
There was a problem hiding this comment.
self._features needs to be sorted in order to make the generated URL stable for tests - i.e. so that the generated URL doesn't depend on the order of execution.
MartinHjelmare
left a comment
There was a problem hiding this comment.
Please also remove the use of STATE_UNKNOWN from this module. Default unknown state should be Nonein the entities. The base entity class will handle this and return the correct unknown state.
| add_devices(sensors) | ||
| async_add_devices(sensors) | ||
|
|
||
| return True |
There was a problem hiding this comment.
Please remove this. Nothing is checking this return value.
There was a problem hiding this comment.
Fixed. Side note: Does this comment apply to the whole codebase? Was wondering about that in #12274
| """Get the latest data from WUnderground.""" | ||
| try: | ||
| result = requests.get(self._build_url(), timeout=10).json() | ||
| websession = async_get_clientsession(self._hass) |
There was a problem hiding this comment.
I think the session is persistent, so you should not do this in the update method but in async_setup_platform and pass it to the instance and store the session as an instance attribute.
| self.data = result | ||
| return True | ||
| self.data = result | ||
| return True |
There was a problem hiding this comment.
Nothing is checking this return value.
| _LOGGER.error("Error fetching WUnderground data: %s", repr(err)) | ||
| self.data = None | ||
| self.data = None | ||
| return False |
| aioclient_mock.get(INVALID_URL, | ||
| text=load_fixture('wunderground-error.json')) | ||
|
|
||
| result = yield from wunderground.async_setup_platform( |
There was a problem hiding this comment.
Instead setup the component and assert that a platform was setup with assert_setup_component.
|
|
||
| result = yield from wunderground.async_setup_platform(hass, VALID_CONFIG, | ||
| async_add_devices) | ||
| assert result |
There was a problem hiding this comment.
Assert that async_add_devices was called once.
|
|
||
| def add_devices(self, devices): | ||
| @callback | ||
| def async_add_devices(new_devices): |
There was a problem hiding this comment.
If you don't want to mock this function, append each call to this function to a list that is accessible from the outer scope, similar to devices. This will allow you to check that this function was called.
There was a problem hiding this comment.
I know the difference makes it difficult to see the changes ... but that's exactly what I'm doing at the moment 👀?
There was a problem hiding this comment.
I think you're only appending the devices currently, correct?
There was a problem hiding this comment.
Ah, you're right. Didn't understand your comment completely there 🙈
| self._unit_of_measurement = self._cfg_expand("unit_of_measurement") | ||
| self.rest.request_feature(SENSOR_TYPES[condition].feature) | ||
| self.entity_id = generate_entity_id( | ||
| self.entity_id = async_generate_entity_id( |
There was a problem hiding this comment.
All other platforms using async_generate_entity_id this way, but should we really be using an event loop callback from within __init__? Would async_added_to_hass also work or is the entity_id required before that method is called?
There was a problem hiding this comment.
Why not? It's not doing any I/O, so should be ok.
There was a problem hiding this comment.
In order to call an async_* function in Home Assistant we need to guarantee that the caller is inside the event loop. If we can't guarantee that, we usually wrap the async call inside a hass.add_job().
So, because of our direct call to async_generate_entity_id() we'd also need to guarantee that __init__ is executed from within the event loop. Can we guarantee that though? In this case, it's only used in asyn_setup_platform (therefore inside the event loop), but I mean the object could also be initialized from outside the event loop.
There was a problem hiding this comment.
Entities are supposed to be initialized in setup_platform/async_setup_platform. Also the caller is responsible to properly handle the objects it calls. So it's fine like this.
There was a problem hiding this comment.
async_added_to_hass is executed after entity_id is assigned. This is fine since the platform uses async_setup_platform
|
I've updated the code according to your comments now, though I've kind of hit a wall and need your advise:
|
|
If you add a second argument
|
|
Thank you very much! That's very good to know, and patching |
|
Just drop support for |
|
@balloob I've now removed |
| sensors.append(WUndergroundSensor(hass, rest, variable)) | ||
|
|
||
| rest.update() | ||
| yield from rest.async_update() |
There was a problem hiding this comment.
If you do this update before initializing the sensors, you will not do unnecessary work. It also means that you don't have to pass True to async_add_devices because the sensors have access to data the moment it is initialized.
There was a problem hiding this comment.
If you do this update before initializing the sensors, you will not do unnecessary work.
With the current design of this platform (which isn't ideal, imho), when a sensor entity is created it tells the data holder that its "feature" should be monitored, so doing WUndergroundData#async_update() before the sensors have added their features would result in an empty query. We could however instead just pass the features into the data holder directly and not through the sensors, but I tried to limit this PR for now as this platform has many improvements that could be made (using the dispatcher to not rely on polling, ...)
It also means that you don't have to pass
Truetoasync_add_devices
The reason I'm passing True to async_add_devices is that the only way for sensors to get new data is through their own WUndergroundSensor#async_update. They don't get notified by WUndergroundData that a request has been made. Instead, they periodically poll data from the data holder, which also takes care of throttling the request. Therefore the only way to have some data in the entities before async_update is automatically called is by passing True to async_add_devices. Again, this could be fixed by completely reworking this platform.
There was a problem hiding this comment.
I'm sorry if this was perceived as being a bit rude - that was not my intention. I wanted to explain why I did this so that you, and anybody attempting to change this part of the codebase in the future, can follow what's going on. It's always better to ask when some code snippet "smells wrong" 👍.
Anyway, the underlying issue of this component being weird with it using polling still persists. But I think changing that would make the most sense during a transition to the weather component (btw, I'm not using this platform too much, so I wouldn't be the one who would like to do that 🤓).
Description:
Makes WUnderground sensor platform async, using aiohttp's HTTP client.
Previous tests had to be updated quite significantly to use the
aiohttp_mockfixture. During that process, the JSON payloads were moved to file fixtures. I didn't know how aunittest.TestCaseclass could be transformed to handle async tests, so I had to move the tests to top-level coroutines and change theself.assert*calls.If someone knows how to fix the tests/make them aiohttp-aware without changing the whole
test_wunderground.pyfile and making reviewing more difficult, please tell me.Example entry for
configuration.yaml(if applicable):Checklist:
If the code does not interact with devices:
toxrun successfully. Your PR cannot be merged unless tests pass