Skip to content

Commit 9220570

Browse files
committed
update time_trigger kwarg for startup and shutdown triggers per #103
1 parent 5343bcd commit 9220570

File tree

5 files changed

+52
-10
lines changed

5 files changed

+52
-10
lines changed

custom_components/pyscript/trigger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ def stop(self):
790790
Function.reaper_cancel(self.task)
791791
if self.run_on_shutdown:
792792
notify_type = "shutdown"
793-
notify_info = {"trigger_type": "shutdown", "trigger_time": None}
793+
notify_info = {"trigger_type": "time", "trigger_time": "shutdown"}
794794
action_future = self.call_action(notify_type, notify_info, run_task=False)
795795
Function.reaper_await(action_future)
796796

@@ -841,7 +841,7 @@ async def trigger_watch(self):
841841
# first time only - skip waiting for other triggers
842842
#
843843
notify_type = "startup"
844-
notify_info = {"trigger_type": "time", "trigger_time": None}
844+
notify_info = {"trigger_type": "time", "trigger_time": "startup"}
845845
self.run_on_startup = False
846846
elif check_state_expr_on_start:
847847
#

docs/new_features.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,32 @@ Planned new features post 1.0.0 include:
2929

3030
The new features since 1.0.0 in master include:
3131

32+
- Adding new decorator ``@mqtt_trigger`` by @dlashua (#98, #105).
3233
- ``pyscript.reload`` only reloads changed files (changed contents, mtime, or an app's yaml configuration).
3334
All files in an app or module are reloaded if any one has changed, and any script, app or module that
3435
imports a changed modules (directly or indirectly) is also reloaded. Setting the optional ``global_ctx``
3536
service parameter to ``*`` forces reloading all files (which is the behavior in 1.0.0 and earlier). See #106.
36-
- Adding new decorator ``@mqtt_trigger`` by @dlashua (#98, #105).
3737
- Added ``state_hold_false=None`` optional period in seconds to ``@state_trigger()`` and ``task.wait_until()``.
3838
This requires the trigger expression to be ``False`` for at least that period (including 0) before
3939
a successful trigger. Setting this optional parameter makes state triggers edge triggered (ie,
4040
triggers only on transition from ``False`` to ``True``), instead of the default level trigger
41-
(ie, only has to evaluate to ``True``). Proposed by @tchef69 (#89).
41+
(ie, only has to evaluate to ``True``). Proposed by @tchef69 (#89), with suggestions from @dlashua (#95).
42+
- ``@time_trigger`` now supports a ``"shutdown"`` trigger, which occurs on HASS shutdown or whenever
43+
the trigger function is no longer referenced (eg: during reload, or redefinition in Jupyter),
44+
requested by @dlashua (#103).
4245
- All .py files below the ``pyscript/scripts`` directory are autoloaded, recursively. Also, any
4346
file name or directory starting with ``#`` is skipped (including top-level and ``apps``), which is
4447
an in-place way of disabling a specific script, app or directory tree (#97).
4548
- ``del`` and new function ``state.delete()`` can delete state variables and state variable attributes.
4649

50+
Breaking changes since 1.0.0 include:
51+
52+
- The ``pyscript.reload`` service only reloads changed files; prior behavior of reloading all files can be
53+
requested by setting the optional ``global_ctx`` service parameter to ``*``.
54+
- The ``trigger_time`` trigger function argument is now set to ``startup`` for a startup trigger,
55+
rather than ``None``. This was done to be consistent with the new ``shutdown`` trigger, which
56+
calls the trigger function with `trigger_time="shutdown"``.
57+
4758
Bug fixes since 1.0.0 in master include:
4859
4960
- State setting now copies the attributes, to avoid a strange ``MappingProxyType`` recursion error

docs/reference.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,9 @@ with the following features:
515515

516516
In ``@time_trigger``, each string specification ``time_spec`` can take one of four forms:
517517

518-
- ``"startup"`` triggers on HASS start and reload.
518+
- ``"startup"`` triggers on HASS start and reload (ie, on function definition)
519+
- ``"shutdown"`` triggers on HASS shutdown and reload (ie, when the trigger function is
520+
no longer referenced)
519521
- ``"once(datetime)"`` triggers once on the date and time. If the year is
520522
omitted, it triggers once per year on the date and time (eg, birthday). If the date is just a day
521523
of week, it triggers once on that day of the week. If the date is omitted, it triggers once each
@@ -547,8 +549,8 @@ In ``@time_trigger``, each string specification ``time_spec`` can take one of fo
547549

548550
When the ``@time_trigger`` occurs and the function is called, the keyword argument ``trigger_type``
549551
is set to ``"time"``, and ``trigger_time`` is the exact ``datetime`` of the time specification that
550-
caused the trigger (it will be slightly before the current time), or ``None`` in the case of a
551-
``startup`` trigger.
552+
caused the trigger (it will be slightly before the current time), or ``startup`` or ``shutdown``
553+
in the case of a ``startup`` or ``shutdown`` trigger.
552554

553555
A final special form of ``@time_trigger`` has no arguments, which causes the function to run once
554556
automatically on startup or reload, which is the same as providing a single ``"startup"`` time
@@ -563,7 +565,16 @@ specification:
563565
564566
The function is not re-started after it returns, unless a reload occurs. Startup occurs when the
565567
``EVENT_HOMEASSISTANT_STARTED`` event is fired, which is after everything else is initialized and
566-
ready, so this function can call any services etc.
568+
ready, so this function can call any services etc. A startup trigger can also occur after
569+
HASS is started when a new trigger function is defined (eg, on reload, defined at run-time
570+
using inner function/closure, or interactively in Jupyter).
571+
572+
Similarly, the ``shutdown`` trigger occurs when the ``EVENT_HOMEASSISTANT_STOP`` event is fired,
573+
meaning HASS is shutting down. It also occurs whenever a trigger function is no longer referenced
574+
(meaning it's being deleted), which happens during reload or redefined interactively in Jupyter.
575+
It's important that any trigger function based on ``shutdown`` runs as quickly as possible,
576+
since the shutdown of HASS (or reload completion) will be stalled until your function
577+
completes.
567578

568579
@event_trigger
569580
^^^^^^^^^^^^^^

tests/test_function.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,7 @@ def func_startup_sync(trigger_type=None, trigger_time=None):
911911
global seq_num
912912
913913
seq_num += 1
914-
pyscript.done = seq_num
914+
pyscript.done = [seq_num, trigger_type, trigger_time]
915915
916916
def factory(trig_value):
917917
@@ -939,7 +939,7 @@ def func_trig(var_name=None, value=None):
939939
seq_num += 1
940940
# fire event to start triggers, and handshake when they are running
941941
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
942-
assert literal_eval(await wait_until_done(notify_q)) == seq_num
942+
assert literal_eval(await wait_until_done(notify_q)) == [seq_num, "time", "startup"]
943943

944944
#
945945
# trigger them one at a time to make sure each is working

tests/test_reload.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ def func3():
5555
other_abc = 987
5656
5757
log.info(f"{__name__} global_ctx={pyscript.get_global_ctx()}")
58+
59+
@time_trigger("shutdown")
60+
def shutdown(trigger_time=None):
61+
log.info(f"{__name__} global_ctx={pyscript.get_global_ctx()} shutdown trigger_time={trigger_time}")
5862
""",
5963
f"{conf_dir}/modules/xyz2/__init__.py": """
6064
from .other import xyz
@@ -193,6 +197,7 @@ def func20():
193197
conf["apps"]["world2"]["var1"] = 200
194198
await hass.services.async_call("pyscript", "reload", {}, blocking=True)
195199
assert "world2 global_ctx=apps.world2.__init__ var1=200, other_abc=987" in caplog.text
200+
assert "world2.other global_ctx=apps.world2.other shutdown trigger_time=shutdown" in caplog.text
196201

197202
#
198203
# change a module inside an app
@@ -203,12 +208,17 @@ def func20():
203208
other_abc = 654
204209
205210
log.info(f"{__name__} global_ctx={pyscript.get_global_ctx()}")
211+
212+
@time_trigger("shutdown")
213+
def shutdown(trigger_time=None):
214+
log.info(f"{__name__} global_ctx={pyscript.get_global_ctx()} shutdown_new trigger_time={trigger_time}")
206215
"""
207216
mock_open[f"{conf_dir}/apps/world2/other.py"].read_data = file_contents[
208217
f"{conf_dir}/apps/world2/other.py"
209218
]
210219
await hass.services.async_call("pyscript", "reload", {}, blocking=True)
211220
assert "world2 global_ctx=apps.world2.__init__ var1=200, other_abc=654" in caplog.text
221+
assert "world2.other global_ctx=apps.world2.other shutdown trigger_time=shutdown" in caplog.text
212222

213223
#
214224
# now confirm certain files reloaded the correct number of times,
@@ -220,5 +230,15 @@ def func20():
220230
assert caplog.text.count("hello global_ctx=file.hello xyz=") == 4 + i
221231
assert caplog.text.count("modules/xyz2/other global_ctx=modules.xyz2.other") == 2 + i
222232
assert caplog.text.count("modules/xyz2 global_ctx=modules.xyz2.__init__") == 2 + i
233+
assert (
234+
caplog.text.count("world2.other global_ctx=apps.world2.other shutdown trigger_time=shutdown")
235+
== 2
236+
)
237+
assert (
238+
caplog.text.count(
239+
"world2.other global_ctx=apps.world2.other shutdown_new trigger_time=shutdown"
240+
)
241+
== i
242+
)
223243
if i < 2:
224244
await hass.services.async_call("pyscript", "reload", {"global_ctx": "*"}, blocking=True)

0 commit comments

Comments
 (0)