Skip to content

Commit b380b9d

Browse files
committed
del and state.delete() can delete state variables or attributes
1 parent 50ced30 commit b380b9d

File tree

5 files changed

+65
-8
lines changed

5 files changed

+65
-8
lines changed

custom_components/pyscript/eval.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,7 @@ async def recurse_assign(self, lhs, val):
12211221
return
12221222
if dot_count > 0:
12231223
raise NameError(
1224-
f"invalid name '{var_name}' (should be 'domain.entity' or 'domain.entity.attr'"
1224+
f"invalid name '{var_name}' (should be 'domain.entity' or 'domain.entity.attr')"
12251225
)
12261226
if self.curr_func and var_name in self.curr_func.global_names:
12271227
self.global_sym_table[var_name] = val
@@ -1284,6 +1284,11 @@ async def ast_delete(self, arg):
12841284
del self.sym_table[arg1.id]
12851285
else:
12861286
raise NameError(f"name '{arg1.id}' is not defined")
1287+
elif isinstance(arg1, ast.Attribute):
1288+
var_name = await self.ast_attribute_collapse(arg1, check_undef=False)
1289+
if not isinstance(var_name, str):
1290+
raise NameError("state name should be 'domain.entity' or 'domain.entity.attr'")
1291+
State.delete(var_name)
12871292
else:
12881293
raise NotImplementedError(f"unknown target type {arg1} in del")
12891294

custom_components/pyscript/state.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class State:
4444
# Last value of state variable notifications. We maintain this
4545
# so that trigger evaluation can use the last notified value,
4646
# rather than fetching the current value, which is subject to
47-
# race conditions when multiple state variables are set.
47+
# race conditions when multiple state variables are set quickly.
4848
#
4949
notify_var_last = {}
5050

@@ -307,6 +307,36 @@ async def service_call(*args, **kwargs):
307307
f"state '{parts[0]}.{parts[1]}' has no attribute '{parts[2]}'"
308308
)
309309

310+
@classmethod
311+
def delete(cls, var_name, context=None):
312+
"""Delete a state variable or attribute from hass."""
313+
parts = var_name.split(".")
314+
if not context:
315+
context = Function.task2context.get(asyncio.current_task(), None)
316+
context_arg = {"context": context} if context else {}
317+
if len(parts) == 2:
318+
if var_name in cls.notify_var_last or var_name in cls.notify:
319+
#
320+
# immediately update a variable we are monitoring since it could take a while
321+
# for the state changed event to propagate
322+
#
323+
cls.notify_var_last[var_name] = None
324+
if not cls.hass.states.async_remove(var_name, **context_arg):
325+
raise NameError(f"name '{var_name}' not defined")
326+
return
327+
if len(parts) == 3:
328+
var_name = f"{parts[0]}.{parts[1]}"
329+
value = cls.hass.states.get(var_name)
330+
if value is None:
331+
raise NameError(f"state {var_name} doesn't exist")
332+
new_attr = value.attributes.copy()
333+
if parts[2] not in new_attr:
334+
raise AttributeError(f"state '{var_name}' has no attribute '{parts[2]}'")
335+
del new_attr[parts[2]]
336+
cls.set(f"{var_name}", value.state, new_attributes=new_attr, **context_arg)
337+
return
338+
raise NameError(f"invalid name '{var_name}' (should be 'domain.entity' or 'domain.entity.attr')")
339+
310340
@classmethod
311341
def getattr(cls, var_name):
312342
"""Return a dict of attributes for a state variable."""
@@ -374,6 +404,7 @@ def register_functions(cls):
374404
"state.getattr": cls.getattr,
375405
"state.get_attr": cls.get_attr, # deprecated form; to be removed
376406
"state.persist": cls.persist,
407+
"state.delete": cls.delete,
377408
"pyscript.config": cls.pyscript_config,
378409
}
379410
Function.register(functions)

docs/new_features.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ to work at any random time.
1111
The latest release is 1.0.0, released on November 9, 2020. Here is the `stable documentation <https://hacs-pyscript.readthedocs.io/en/stable>`__
1212
for that release.
1313

14-
Since that release, the master (head of tree) version in GitHub has several new features and bug fixes.
15-
Here is the master `latest documentation <https://hacs-pyscript.readthedocs.io/en/latest>`__.
14+
Over time, the master (head of tree) version in GitHub will include new features and bug fixes.
15+
Here is the master `latest documentation <https://hacs-pyscript.readthedocs.io/en/latest>`__
16+
if you want to see the new features or bug fixes.
1617

1718
Planned new features post 1.0.0 include:
1819

19-
- ``del`` can delete state variables and state variable attributes
20+
- support mqtt triggers (#98, #105)
2021
- use ``aionofity`` to auto-reload newly written script files, at least on linux (#74)
2122
- consider allowing native Python functions inside pyscript (#71)
2223
- consider implementing function decorators (#43)
@@ -28,15 +29,18 @@ Planned new features post 1.0.0 include:
2829

2930
The new features since 1.0.0 in master include:
3031

31-
- Added ``state_hold_false=None`` optional period in seconds to ``@state_trigger`` and ``task.wait_until()``.
32+
- Added ``state_hold_false=None`` optional period in seconds to ``@state_trigger()`` and ``task.wait_until()``.
3233
This requires the trigger expression to be ``False`` for at least that period (including 0) before a
33-
successful trigger. Proposed by @tchef69 (#89).
34+
successful trigger. Setting this optional parameter makes state triggers edge triggered (ie,
35+
triggers only on transition from ``False`` to ``True``), instead of the default level trigger (ie,
36+
only has to evaluate to ``True``). Proposed by @tchef69 (#89).
37+
- ``del`` and new function ``state.delete()` can delete state variables and state variable attributes
3438
3539
Bug fixes since 1.0.0 in master include:
3640
3741
- state setting now copies the attributes, to avoid a strange ``MappingProxyType`` recursion error
3842
inside HASS, reported by @github392 (#87).
39-
- the deprecated function ``state.get_attr`` was missing an ``await``, which caused an exception; in 1.0.0 use
43+
- the deprecated function ``state.get_attr`` was missing an ``await``, which causes an exception; in 1.0.0 use
4044
``state.getattr``, reported and fixed by @dlashua (#88).
4145
- the ``packaging`` module is installed if not found, since certain HASS configurations might not include it;
4246
fixed by @raman325 (#90, #91).

docs/reference.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,9 @@ where the state variable name is computed dynamically). These functions allow yo
710710
variable using its string name. The set function also allows you to optionally set the attributes,
711711
which you can’t do if you are directly assigning to the variable:
712712

713+
``state.delete(name)``
714+
Deletes the given state variable or attribute. The Python ``del`` statement can also be used
715+
to delete a state variable or attribute.
713716
``state.get(name)``
714717
Returns the value of the state variable given its string ``name``. A ``NameError`` exception
715718
is thrown if the name doesn't exist. If ``name`` is a string of the form ``DOMAIN.entity.attr``

tests/test_unit_eval.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@
145145
state.set("pyscript.var1", 200, attr3 = 'abc')
146146
chk.append([pyscript.var1.attr1, pyscript.var1.attr2, pyscript.var1.attr3])
147147
chk.append(state.getattr("pyscript.var1"))
148+
del pyscript.var1.attr3
149+
chk.append(state.getattr("pyscript.var1"))
150+
state.delete("pyscript.var1.attr2")
151+
chk.append(state.getattr("pyscript.var1"))
148152
state.set("pyscript.var1", pyscript.var1, {})
149153
chk.append(state.getattr("pyscript.var1"))
150154
state.set("pyscript.var1", pyscript.var1, new_attributes={"attr2": 8.5, "attr3": "xyz"})
@@ -166,6 +170,8 @@
166170
["100xyz", 1, 3.5],
167171
[1, 3.5, "abc"],
168172
{"attr1": 1, "attr2": 3.5, "attr3": "abc"},
173+
{"attr1": 1, "attr2": 3.5},
174+
{"attr1": 1},
169175
{},
170176
{"attr2": 8.5, "attr3": "xyz"},
171177
{"attr2": "abc", "attr3": "xyz"},
@@ -963,6 +969,14 @@ async def test_eval(hass):
963969
["f'xxx{'", "syntax error f-string: expecting '}' (test, line 1)"],
964970
["f'xxx{foo() i}'", "syntax error invalid syntax (<fstring>, line 1)"],
965971
["del xx", "Exception in test line 1 column 0: name 'xx' is not defined"],
972+
[
973+
"pyscript.var1 = 1; del pyscript.var1; pyscript.var1",
974+
"Exception in test line 1 column 38: name 'pyscript.var1' is not defined",
975+
],
976+
[
977+
"pyscript.var1 = 1; state.delete('pyscript.var1'); pyscript.var1",
978+
"Exception in test line 1 column 50: name 'pyscript.var1' is not defined",
979+
],
966980
["return", "Exception in test line 1 column 0: return statement outside function"],
967981
["break", "Exception in test line 1 column 0: break statement outside loop"],
968982
["continue", "Exception in test line 1 column 0: continue statement outside loop"],

0 commit comments

Comments
 (0)