AppDaemon / appdaemon

:page_facing_up: Python Apps for Home Automation
Other
846 stars 418 forks source link

[Feature Request] Type stubs for Hass #932

Open Jolanrensen opened 4 years ago

Jolanrensen commented 4 years ago

Type hints have been around for a few Python versions now and they can save a huge amount of time scouring the docs (or mindlessly printing every statement) to find out which attributes can be found where and whether the function will actually work if I put in this type of object. Personally I use Pycharm for AppDaemon and, while in typed projects it's great at showing me what my options are at any time, when programming for AppDaemon however I feel like I'm working in the dark. My suggestion is to create type subs or classes so that all options for entities become visible to me while programming. This might also enhance the readability of the code.

Let me give an example, the base Entity class(es) would look a bit like this:

class EntityAttributes:
    friendly_name: str

class EntityContext:
    id: str
    parent_id: Union[str, None]
    user_id: Union[str, None]

class Entity:
    attributes: EntityAttributes
    context: EntityContext
    entity_id: str
    last_changed: str
    last_updated: str
    state: str

When used like this:

bedroom_lamp: Entity = self.entities.light.bedroom_lamp
bedroom_lamp.

this would pop up all the available options from now on, like state, attributes and so on. Now Enitity would be the base-class, as all these attributes are available to all entities, however, certain type of entities have more specific attributes, so we can build from this and add some inheritance:

class LightAttributes(EntityAttributes):
    brightness: int
    effect_list: List[str]
    hs_color: List[float]
    max_mireds: int
    min_mireds: int
    rgb_color: List[int]
    supported_features: int
    white_value: int
    xy_color: List[float]

class Light(Entity):
    attributes: LightAttributes

Now, when we're defining

bedroom_lamp: Light = self.entities.light.bedroom_lamp
bedroom_lamp.

we're not only getting the base attributes state etc, but we also get the proper light attributes like bedroom_lamp.attributes.effect_list and the IDE will know this is a list of strings.

Now I know this would require work that on the first hand seems redundant, but trust me, when projects are scaling up, you'll be glad to find out that your do_auto_lights.attributes.editable doesn't work because do_auto_lights is a switch instead of an input_boolean and it doesn't have that attribute, just by looking at it.

Edit: To clarify, in my opinion, the way entities are addressed is really odd and hacky (maybe that's the python way, in any case, I don't like it). By adding attributes to a class at runtime you'll never know what you're able to call unless you run the code. However, some things are know, like the types of entities and the attributes, so they can be defined, while the unknown things like the names of entities can be queried from a dict. This would result in:

e: Entities = self.entities   # e is now detected as a class which has `lights: Dict[str, Light], `switches: Dict[str, Switch]` etc
lamp: Light = e.lights["bedroom_lamp"]  # because lights only contains lights, lamp is now seen as a Light object
lamp.turn_on()  # functions like these would now be possible, because we know for certain it's a Light
ReneTode commented 4 years ago

besides the fact that it would be a load of work, it would also cost a lot of time checking every 2 weeks if HA has changed anything. it also wouldnt work for AD entities and for entities from other plugins.

besides the fact that it would only be helpfull for people who use an editor that uses it.

Jolanrensen commented 4 years ago

Don't all (Hass) entities have a base set of attributes? Like state and entity_id? That likely won't change and will definitely work for all components, right? It's okay if there are exceptions, they're just type hints, not enforcements. However, people like me tend to just try around in the code to see what works instead of trying to understand (and having to search through) a huge wiki. That's where IDEs come in. I highly doubt that many people code python in plain text editors. I think most use something like Vscode, pycharm, atom or something like that, which all have code suggestions. However, currently these code suggestions suck as any variable can be anything and can be used in any way and the only way to know if it will work is to test it in all ways possible. Of course, if something only happens once a month, like a value returns a string instead of an int, this can break your code and you'll never know why. However when it's typed like Union[str, int] you'll know and take precautions.

Jolanrensen commented 4 years ago

When looking at the homeassistant package itself I can however find homeassistant.components.light.Light which inherits from ToggleEntity which inherits from Entity, just as it should be. These also have functions to get the brightness, toggle etc.

I however do assume that this works differently than appdaemon because AD implements the open API of hass instead of directly interfacing with the python of hass. Am I right?

I wonder if it would be possible/useful to program directly for hass on their framework, because to understand how the code works, that would be ideal...

I'd also like to add that Home Assistant itself is also pushing for type hints: https://developers.home-assistant.io/docs/development_typing

ReneTode commented 4 years ago

you forget that AD is not just HA. you can create any entity you want in AD. you can create a plugin for any other program then HA (for example openhab)

its no use looking at HA code directly, because AD get everything through api, if you use HA at all (which isnt needed for AD).

and yeah hass entities have attributes based on the component. and they change them once in a while, if it fits them better. thats why it would need a set of hints based on the HA version. and it would need to be updated every time.

the things like state are obvious there for every entity, but if you know that you dont need hints.

to get a state from HA you use get_state normally. so you would need a hint when you use self.get_state(entity, attribute = ... and that would be very difficult, if not impossible.

Jolanrensen commented 4 years ago

I'm only talking about the Hass plugin of AD of course, the other plugins could have their own type hints. So what you could do is optionally type hint them, so if you know you're going to get the state of a switch entity you could type: switch_state: SwitchState = self.get_state("switch.your_switch") and you'll get further hints for switch_state as you go on. It would be completely optional. However, where type hints would shine is when getting the state from self.entities like I described above. Because you know that everything self.entities.light.* is a Light and thus has certain attributes. So it doesn't matter if certain calls are non-typable, as long as you type as much as you can.

ReneTode commented 4 years ago

even for hass i wouldnt find that helpfull.

with self.entities.light.* i would get a lot of possible attributes, but for one light i can use attribute a,b and c and not d, e and f. for another light i could use b,c and f, but not the others.

so i get a lot of unusefull hints, and people would expect that they can use what is hinted.

i wouldnt make the trouble, but maybe you can convince the devs. (and like i said, i am not 1 anymore) it might help if you volunteer to do the efford to create and maintain a list.

Jolanrensen commented 4 years ago

I see. You've convinced me with the attributes issue, however, attributes can be typed as Optional[int] for instance, so that would solve that problem. Although I'm not sure how to easily check if the optional one is given...

I've started on a project myself actually, but in Kotlin, a strictly typed language based on Java (most familiar around android / java devs), but it can also compile to javascript and native code. It's just in concept fase now (and I can only work on it when I have time), but eventually, I think it will as easy as (or even easier than) AppDaemon. If only because you won't have to go through the docs for every line you type. It has null safety, so you can easily check whether attributes exist. Lambda's are used for callbacks which are much more readable than callbacks in python. Plus, as a library, it can be easily integrated in future android apps, web apps etc.

So yeah, I don't feel like extending AppDaemon currently anymore, but I do still think that type stubs would improve AppDaemon for python programmers in the future.

ReneTode commented 4 years ago

depending on the editor that is used you can create the hints yourself. i know that there are people who type: get_... and get hints for all AD commands i also know that its possible to get all your created entities with their attributes in an editor.

so everything that you like is possible already without changing anything, depending on the editor that is used and the right configuration from that editor (giving libraries, urls, etc)

dont ask me how, because i dont care for that myself. i program in a plain blind editor (like wordpad) but i know its possible.

ask on discord and/or the forum for help how to do it in which editor.