Style change follow up#13707
Style change follow up#13707kellerza merged 12 commits intohome-assistant:devfrom cdce8p:homekit-static
Conversation
There was a problem hiding this comment.
We should not do this.
All of Home Assistant is coded in a way that binds all gobal state to the hass object. Class variables go against this. Pass it into the constructor or set it as an instance variable. Don't set it as a class variable.
There was a problem hiding this comment.
This signature looks much better, if you add hass here, it should adress @balloob's concerns as well.
Also means no change required in __init__.py
There was a problem hiding this comment.
👍 This is a good idea and seem to simplify things as well
There was a problem hiding this comment.
Also leave hass in here, and try to keep same signature as __init__ in HomeAccessory (at the moment you swap name & entity_id).
Maybe keep name first, since Accessory's signature is (name, **kwargs)
*same for other HomeAccesories
There was a problem hiding this comment.
If you call category with a keyword it also protects against future changes
super().__init__(name, entity_id, category=CATEGORY_*, **kwargs)
*same for all HomeAccessories
There was a problem hiding this comment.
Once all Accesory signature are the same you can simplify this:
def get_accessory(hass, state, aid, config):
"""Take state and return an accessory object if supported."""
if not aid:
_LOGGER.warning('The entitiy "%s" is not supported, since it '
'generates an invalid aid, please change it.',
state.entity_id)
return None
a_type = None
a_kwargs = {'aid'} = aid
if state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if unit in (TEMP_CELSIUS, TEMP_FAHRENHEIT):
a_type = 'TemperatureSensor'
elif unit == '%':
a_type = 'HumiditySensor'
elif state.domain == 'cover':
# Only add covers that support set_cover_position
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_SET_POSITION:
a_type = 'WindowCovering'
elif state.domain == 'alarm_control_panel':
a_type = 'SecuritySystem'
a_kwargs['alarm_code'] = config.get(ATTR_CODE)
elif state.domain == 'climate':
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
support_temp_range = SUPPORT_TARGET_TEMPERATURE_LOW | \
SUPPORT_TARGET_TEMPERATURE_HIGH
# Check if climate device supports auto mode
a_kwargs['support_auto'] = bool(features & support_temp_range)
a_type = 'Thermostat'
elif state.domain == 'light':
a_type = 'Light'
elif state.domain in ('switch', 'remote', 'input_boolean', 'script'):
a_type = 'Switch'
if a_type is None:
return None
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, state.name)
return TYPES[a_type](hass, state.name, state.entity_id, **a_kwargs)|
Thanks for the feedback. I wasn't home this weekend, so I haven't had the chance to do the improvements yet. Hopefully during the next few days, although I have already done a rebase. |
* Set argument order for HomeAccessory and HomeBridge * Added update_state_callback
|
I have update the commits to reverse the change to make |
| 'generates an invalid aid, please change it.', | ||
| state.entity_id) | ||
| return None | ||
| return |
There was a problem hiding this comment.
Although they are the same, I would suggest we keep return None where we care / intend to use the return of a function
There was a problem hiding this comment.
I have again added it to get_accessory and generate_aid. In cases where it is an abort condition (like HomeKit.start(), HomeKit.stop(), HomeAccessory.update_state_callback()) I would prefer to stay with just return.
| self.hass = hass | ||
| self.entity_id = entity_id | ||
| super().__init__(hass, name, entity_id, | ||
| category=CATEGORY_WINDOW_COVERING, **kwargs) |
There was a problem hiding this comment.
Personally I would really like to move these calls to the HomeAccessory class somehow. Any ideas who to improve that?
There was a problem hiding this comment.
On solution could be to use a decorator function for for the init method which calls super().__init__(). That would look like this:
def super_init(category):
def category_wrapper(func_init):
def wrapper(self, hass, name, entity_id, **kwargs):
super(self.__class__, self).__init__(
hass, name, entity_id, category=CATEGORY_LIGHT, **kwargs)
func_init(self)
return wrapper
return category_wrapper
@TYPES.register('Light')
class Light(HomeAccessory):
@super_init(CATEGORY_LIGHT)
def __init__(self):Should we do that or stay at the current:
def __init__(self, hass, name, entity_id, **kwargs):
super().__init__(hass, name, entity_id, category=CATEGORY_LIGHT, **kwargs)There was a problem hiding this comment.
I don't like the decorator and not sure how that will interfere with __init__
You could do this (note: category= at the end so they look the same at first glance)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, category=CATEGORY_LIGHT)but being explicit might give you some code editor hints, so I like this version more:
def __init__(self, hass, name, entity_id, **kwargs):
super().__init__(hass, name, entity_id, **kwargs,
category=CATEGORY_LIGHT)There was a problem hiding this comment.
The idea behind the decorator was to limit code redundancy between different types. You're right the it might be more difficult for editors to read, however I don't think that is that big of a deal since those attributes are already assigned in accessories.py/HomeAccessory and most inexperience developers will only copy existing types without fully understanding them.
Regarding the interference with __init__, it should change anything. The call order is as follows:
- HA start
- Module is loaded and
__init__call is replaced by decorater - Call to
light.__init__ super().__init__()__init__()
There was a problem hiding this comment.
If you dont like the super rather add a bootstrap/postinit method in the child classes that you call from the parent’s init. Type could be a class var on the child
There was a problem hiding this comment.
In HomeAccessory you then have:
def __init__(self, hass, name, entity_id, **kwargs):
"""Initialize a Accessory object."""
super().__init__(name, **kwargs)
set_accessory_info(self, name, model=entity_id)
self.entity_id = entity_id
self.hass = hass
cat = self.init_category()
self.category = getattr(Category, cat, Category.OTHER)with only def init_category(self) and no more __init__ in the rest, init_category then returns the category.
There was a problem hiding this comment.
Extending on your idea what do you think about the following:
- Making the
categorya static var of thetype - Changing the
__init__()of the types to something likeinit_setup()which is called fromHomeAccessory.__init__()
There was a problem hiding this comment.
init_setup() might not be the best name. If you have something else in mind, I'm happy to change it.
The only caveat with this solution is, that I had to add # pylint: disable=attribute-defined-outside-init to every type file.
|
@kellerza What do you think about a helper function to create # Class HomeAccessory
def setup_char(self, service, char_name, char_var_name,
value=None, callback=None):
char = self.__dict__[char_var_name] = \
service.get_characteristic(char_name)
if value:
char.value = value
if callback:
char.setter_callback = callback# Type lights (init / init_setup)
self.setup_char(serv_light, CHAR_ON, 'char_on',
value=self._state, callback=self.set_state) |
kellerza
left a comment
There was a problem hiding this comment.
From the code trying to remove super().__init__(x) is forced.
Rather just stick to
super().__init__(*args)
From you changes it does not seem like **kwargs is required any longer, only aid
| a_type = 'Switch' | ||
|
|
||
| if a_type is None: | ||
| else: |
There was a problem hiding this comment.
This change will cause issues....
state.domain == 'sensor' and unit is not %, or temp and a_type is undefined
Please revert to the original if a_type is None:
There was a problem hiding this comment.
You're right. Didn't thought about that.
| return None | ||
|
|
||
| a_type = None | ||
| a_kwargs = {'aid': aid} |
There was a problem hiding this comment.
Keep a_type = None and config = {}
| return TYPES['Switch'](hass, state.entity_id, state.name, aid=aid) | ||
| a_type = 'Switch' | ||
|
|
||
| else: |
| state.entity_id, 'HumiditySensor') | ||
| return TYPES['HumiditySensor'](hass, state.entity_id, state.name, | ||
| aid=aid) | ||
| if state.domain == 'alarm_control_panel': |
| @@ -1,4 +1,5 @@ | |||
| """Class to hold all cover accessories.""" | |||
| # pylint: disable=attribute-defined-outside-init | |||
There was a problem hiding this comment.
This should be move to def init_setup
| @@ -1,4 +1,5 @@ | |||
| """Class to hold all alarm control panel accessories.""" | |||
| # pylint: disable=attribute-defined-outside-init | |||
There was a problem hiding this comment.
This should be move to def init_setup
| @@ -1,4 +1,5 @@ | |||
| """Class to hold all sensor accessories.""" | |||
| # pylint: disable=attribute-defined-outside-init | |||
There was a problem hiding this comment.
This should be move to def init_setup
| @@ -1,4 +1,5 @@ | |||
| """Class to hold all switch accessories.""" | |||
| # pylint: disable=attribute-defined-outside-init | |||
There was a problem hiding this comment.
This should be move to def init_setup
| @@ -1,13 +1,16 @@ | |||
| """Class to hold all thermostat accessories.""" | |||
| # pylint: disable=attribute-defined-outside-init | |||
There was a problem hiding this comment.
This should be move to def init_setup
|
|
||
| def __init__(self, name=ACCESSORY_NAME, model=ACCESSORY_MODEL, | ||
| category='OTHER', **kwargs): | ||
| def __init__(self, hass, name, entity_id, config, **kwargs): |
There was a problem hiding this comment.
With config, can you not change **kwargs into aid?
|
Regarding |
|
Update: In theory a combination with Regarding |
|
pylint has scope.... decorator would be preferred to the current disable so rather give that a go For characteristics, I would suggest it so you can use it as follows, This is clear you are creating self.char_on: self.char_on = self._get_characteristic(
serv_light, CHAR_ON, value=self._state, callback=self.set_state)Maybe even add it to service.get_charactristic's call in the long run. Are you sure you are returning a copy, or you ok mutating its value and callback? |
* HomeAccessory, HomeBridge: removed **kwargs * Added defaults for a_type, config
|
@kellerza I tested the decorator. Unfortunately it as well throws a pylint error as well: def __init__(self, *args, config):
super().__init__(*args, category=CATEGORY_LIGHT)Where |
|
Regarding Update:
|
| """Initialize a Accessory object.""" | ||
| super().__init__(name, **kwargs) | ||
| set_accessory_info(self, name, model) | ||
| super().__init__(name, aid=aid) |
There was a problem hiding this comment.
Are you sure you never need config to be passed to the Accessory?
There was a problem hiding this comment.
About 90% sure that it isn't needed. The intention behind config was/is to use it for corner cases (e.g. the alarm_code) or to be able to define which type should be used there so it won't have to be in customize.
There was a problem hiding this comment.
Ok, because you used to pass it and stopped with the new code... otherwise all seems similar (hate to ask the obvious, but you did test it? 😄)
There was a problem hiding this comment.
Test what?
The config was never passed to super().__init__(), since they haven't been keyword arguments before, only normal ones. **kwargs was there to cover any case that I need to pass more arguments from get_accessory to the HAP-python accessory. However only used for aid.
Edit:
I did test all changes for lights at least. That works. The changes in the other types are mostly identical. I also change the same line in the tests. As you know with PR's that change the base structure it's almost impossible to test everything, but I'm quite confident that it won't introduce new errors.
kellerza
left a comment
There was a problem hiding this comment.
One last question on config and then it should be ok.
It is much clearer & you removed ~60 lines, nice work! 🎉
|
@kellerza Thanks for the help 👍 |
Follow up to: #13654
Description:
During the last style change @kellerza suggested to rework what is passed into the
HomeAccessoryconstructor. I think to following changes might make sense:hassis now a class variable ofHomeAccessoryandHomeBridgeand set during the HomeKit setup. That way it doesn't need to be passed to each accessory individually.HomeAccessoryI added the methodupdate_state_callbackwhich checks that the new state isn'tNone(previously done in each type) and calls theupdate_statemethod.Checklist:
tox. Your PR cannot be merged unless tests pass