Skip to content

Commit 07542be

Browse files
committed
added state.setattr() and direct attribute assignment; implements #49.
1 parent a1c02d1 commit 07542be

File tree

4 files changed

+79
-23
lines changed

4 files changed

+79
-23
lines changed

custom_components/pyscript/eval.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,9 +1212,17 @@ async def recurse_assign(self, lhs, val):
12121212
return
12131213
if not isinstance(var_name, str):
12141214
raise NotImplementedError(f"unknown lhs type {lhs} (got {var_name}) in assign")
1215-
if var_name.find(".") >= 0:
1215+
dot_count = var_name.count(".")
1216+
if dot_count == 1:
12161217
await State.set(var_name, val)
12171218
return
1219+
if dot_count == 2:
1220+
await State.setattr(var_name, val)
1221+
return
1222+
if dot_count > 0:
1223+
raise NameError(
1224+
f"invalid name '{var_name}' (should be 'domain.entity' or 'domain.entity.attr'"
1225+
)
12181226
if self.curr_func and var_name in self.curr_func.global_names:
12191227
self.global_sym_table[var_name] = val
12201228
return

custom_components/pyscript/state.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,19 @@ def notify_var_get(cls, var_names, new_vars):
126126
return notify_vars
127127

128128
@classmethod
129-
async def set(cls, var_name, value, new_attributes=None, **kwargs):
129+
async def set(cls, var_name, value=None, new_attributes=None, **kwargs):
130130
"""Set a state variable and optional attributes in hass."""
131131
if var_name.count(".") != 1:
132132
raise NameError(f"invalid name {var_name} (should be 'domain.entity')")
133-
if new_attributes is None:
133+
134+
state_value = None
135+
if value is None or new_attributes is None:
134136
state_value = cls.hass.states.get(var_name)
137+
138+
if value is None and state_value:
139+
value = state_value.state
140+
141+
if new_attributes is None:
135142
if state_value:
136143
new_attributes = state_value.attributes
137144
else:
@@ -152,6 +159,15 @@ async def set(cls, var_name, value, new_attributes=None, **kwargs):
152159

153160
cls.hass.states.async_set(var_name, value, new_attributes, context=context)
154161

162+
@classmethod
163+
async def setattr(cls, var_attr_name, value):
164+
"""Set a state variable's attribute in hass."""
165+
parts = var_attr_name.split(".")
166+
if len(parts) != 3:
167+
raise NameError(f"invalid name {var_attr_name} (should be 'domain.entity.attr')")
168+
169+
await cls.set(f"{parts[0]}.{parts[1]}", **{parts[2]: value})
170+
155171
@classmethod
156172
async def register_persist(cls, var_name):
157173
"""Register pyscript state variable to be persisted with RestoreState."""
@@ -253,7 +269,7 @@ async def service_call(*args, **kwargs):
253269
return value.attributes.get(parts[2])
254270

255271
@classmethod
256-
async def get_attr(cls, var_name):
272+
async def getattr(cls, var_name):
257273
"""Return a dict of attributes for a state variable."""
258274
if var_name.count(".") != 1:
259275
raise NameError(f"invalid name {var_name} (should be 'domain.entity')")
@@ -303,8 +319,10 @@ def register_functions(cls):
303319
functions = {
304320
"state.get": cls.get,
305321
"state.set": cls.set,
322+
"state.setattr": cls.setattr,
306323
"state.names": cls.names,
307-
"state.get_attr": cls.get_attr,
324+
"state.getattr": cls.getattr,
325+
"state.get_attr": cls.getattr, # deprecated form; to be removed
308326
"state.persist": cls.persist,
309327
"pyscript.config": cls.pyscript_config,
310328
}

docs/reference.rst

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,11 @@ you need their numeric value.
117117
State variables have attributes that can be accessed by adding the name of the attribute, as in
118118
``DOMAIN.name.attr``. The attribute names and their meaning depend on the component that sets them,
119119
so you will need to look at the State tab in the Developer Tools to see the available attributes.
120+
You can set an attribute directly by assigning ``DOMAIN.name.attr = value``.
120121

121122
In cases where you need to compute the name of the state variable dynamically, or you need to set or
122-
get the state attributes, you can use the built-in functions ``state.get()``, ``state.get_attr()``
123-
and ``state.set()``; see below.
123+
get all the state attributes, you can use the built-in functions ``state.get()``, ``state.getattr()``,
124+
``state.set()`` and ``state.setattr()``; see `State Functions <#state-variable-functions>`__.
124125

125126
The function ``state.names(domain=None)`` returns a list of all state variable names (ie,
126127
``entity_id``\ s) of a domain. If ``domain`` is not specified, it returns all HASS state
@@ -142,7 +143,7 @@ or call ``state.get("DOMAIN.entity.attr")``:
142143
- ``last_updated`` is the last UTC time the state entity was updated
143144

144145
Note that these two values take precedence over any entity attributes that have the same name. If an
145-
entity has attributes with those names and you need to access them, use ``state.get_attr(name)``.
146+
entity has attributes with those names and you need to access them, use ``state.getattr(name)``.
146147
If you need to compute how many seconds ago the ``binary_sensor.test1`` state changed, you could
147148
do this:
148149

@@ -624,9 +625,10 @@ which you can’t do if you are directly assigning to the variable:
624625
is thrown if the name doesn't exist. If ``name`` is a string of the form ``DOMAIN.entity.attr``
625626
then the attribute ``attr`` of the state variable ``DOMAIN.entity`` is returned; an
626627
``AttributeError`` exception is thrown if that attribute doesn't exist.
627-
``state.get_attr(name)``
628-
Returns a ``dict`` of attribute values for the state variable, or ``None``
629-
if it doesn’t exist
628+
``state.getattr(name)``
629+
Returns a ``dict`` of attribute values for the state variable, or ``None`` if it doesn’t exist.
630+
In pyscript versions 0.32 and earlier, this function was ``state.get_attr()``. That deprecated
631+
name is still supported, but will be removed in a future version.
630632
``state.names(domain=None)``
631633
Returns a list of all state variable names (ie, ``entity_id``\ s) of a
632634
domain. If ``domain`` is not specified, it returns all HASS state variable (``entity_id``) names.
@@ -637,13 +639,17 @@ which you can’t do if you are directly assigning to the variable:
637639
are preserved across HASS restarts. This only applies to entities in the ``pyscript``
638640
domain (ie, name starts with ``pyscript.``). See `this section <#persistent-state>`__ for
639641
more information
640-
``state.set(name, value, new_attributes=None, **kwargs)``
642+
``state.set(name, value=None, new_attributes=None, **kwargs)``
641643
Sets the state variable to the given value, with the optional attributes. The optional 3rd
642644
argument, ``new_attributes``, should be a ``dict`` and it will overwrite all the existing
643-
attributes if specified. If instead attributes are specified using keyword arguments, then other
644-
attributes will not be affected. If no optional arguments are provided, just the state variable
645-
value is set and the attributes are not changed. To clear the attributes, set
646-
``new_attributes={}``.
645+
attributes if specified. If instead attributes are specified using keyword arguments, then
646+
just those attributes will be set and other attributes will not be affected. If no optional
647+
arguments are provided, just the state variable value is set and the attributes are not changed.
648+
If no value is provided, just the state attributes are set and the value isn't changed.
649+
To clear all the attributes, set ``new_attributes={}``.
650+
``state.setattr(name, value)``
651+
Sets a state variable attribute to the given value. The ``name`` should fully specify the
652+
state variable and attribute as a string in the form ``DOMAIN.entity.attr``.
647653

648654
Note that in HASS, all state variable values are coerced into strings. For example, if a state
649655
variable has a numeric value, you might want to convert it to a numeric type (eg, using ``int()``

tests/test_unit_eval.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,41 @@
139139
[
140140
"""
141141
state.set("pyscript.var1", 100, attr1=1, attr2=3.5)
142-
chk1 = [pyscript.var1.attr1, pyscript.var1.attr2]
142+
chk = [[pyscript.var1.attr1, pyscript.var1.attr2]]
143143
pyscript.var1 += "xyz"
144-
chk2 = [pyscript.var1.attr1, pyscript.var1.attr2]
144+
chk.append([pyscript.var1.attr1, pyscript.var1.attr2])
145145
state.set("pyscript.var1", 200, attr3 = 'abc')
146-
chk3 = [pyscript.var1.attr1, pyscript.var1.attr2, pyscript.var1.attr3]
147-
chk4 = state.get_attr("pyscript.var1")
146+
chk.append([pyscript.var1.attr1, pyscript.var1.attr2, pyscript.var1.attr3])
147+
chk.append(state.getattr("pyscript.var1"))
148148
state.set("pyscript.var1", pyscript.var1, {})
149-
chk5 = state.get_attr("pyscript.var1")
150-
[chk1, chk2, chk3, chk4, chk5]
149+
chk.append(state.getattr("pyscript.var1"))
150+
state.set("pyscript.var1", pyscript.var1, new_attributes={"attr2": 8.5, "attr3": "xyz"})
151+
chk.append(state.getattr("pyscript.var1"))
152+
pyscript.var1.attr2 = "abc"
153+
chk.append(state.getattr("pyscript.var1"))
154+
state.set("pyscript.var1", attr1=123)
155+
state.set("pyscript.var1", attr3="def")
156+
chk.append(state.getattr("pyscript.var1"))
157+
pyscript.var1.attr4 = 987
158+
chk.append(state.getattr("pyscript.var1"))
159+
state.set("pyscript.var1", new_attributes={"attr2": 9.5, "attr3": "xyz"})
160+
chk.append(state.getattr("pyscript.var1"))
161+
chk.append(pyscript.var1)
162+
chk
151163
""",
152-
[[1, 3.5], [1, 3.5], [1, 3.5, "abc"], {"attr1": 1, "attr2": 3.5, "attr3": "abc"}, {}],
164+
[
165+
[1, 3.5],
166+
[1, 3.5],
167+
[1, 3.5, "abc"],
168+
{"attr1": 1, "attr2": 3.5, "attr3": "abc"},
169+
{},
170+
{"attr2": 8.5, "attr3": "xyz"},
171+
{"attr2": "abc", "attr3": "xyz"},
172+
{"attr1": 123, "attr2": "abc", "attr3": "def"},
173+
{"attr1": 123, "attr2": "abc", "attr3": "def", "attr4": 987},
174+
{"attr2": 9.5, "attr3": "xyz"},
175+
"200",
176+
],
153177
],
154178
["eval('1+2')", 3],
155179
["x = 5; eval('2 * x')", 10],

0 commit comments

Comments
 (0)