Skip to content

Commit 8f11a8b

Browse files
committed
task.unique() names are now specific to each global context
1 parent 5a10c2c commit 8f11a8b

File tree

3 files changed

+46
-33
lines changed

3 files changed

+46
-33
lines changed

custom_components/pyscript/function.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def init(cls, hass):
5454
"task.executor": cls.task_executor,
5555
"event.fire": cls.event_fire,
5656
"task.sleep": cls.async_sleep,
57-
"task.unique": cls.task_unique,
5857
"service.call": cls.service_call,
5958
"service.has_service": cls.service_has_service,
6059
}
@@ -66,6 +65,7 @@ def init(cls, hass):
6665
"log.info": lambda ast_ctx: ast_ctx.get_logger().info,
6766
"log.warning": lambda ast_ctx: ast_ctx.get_logger().warning,
6867
"print": lambda ast_ctx: ast_ctx.get_logger().debug,
68+
"task.unique": lambda ast_ctx: cls.task_unique_factory(ast_ctx),
6969
}
7070
)
7171

@@ -101,30 +101,36 @@ async def event_fire(cls, event_type, **kwargs):
101101
cls.hass.bus.async_fire(event_type, kwargs)
102102

103103
@classmethod
104-
async def task_unique(cls, name, kill_me=False):
105-
"""Implement task.unique()."""
106-
if name in cls.unique_name2task:
107-
if kill_me:
108-
#
109-
# it seems we can't cancel ourselves, so we
110-
# tell the repeaer task to cancel us
111-
#
112-
Function.task_cancel(asyncio.current_task())
113-
# wait to be canceled
114-
await asyncio.sleep(100000)
115-
else:
116-
task = cls.unique_name2task[name]
117-
if task in cls.our_tasks:
118-
# only cancel tasks if they are ones we started
119-
try:
120-
task.cancel()
121-
await task
122-
except asyncio.CancelledError:
123-
pass
124-
task = asyncio.current_task()
125-
if task in cls.our_tasks:
126-
cls.unique_name2task[name] = task
127-
cls.unique_task2name[task] = name
104+
def task_unique_factory(cls, ctx):
105+
"""Define and return task.unique() for this context."""
106+
107+
async def task_unique(name, kill_me=False):
108+
"""Implement task.unique()."""
109+
name = f"{ctx.get_global_ctx_name()}.{name}"
110+
if name in cls.unique_name2task:
111+
if kill_me:
112+
#
113+
# it seems we can't cancel ourselves, so we
114+
# tell the repeaer task to cancel us
115+
#
116+
Function.task_cancel(asyncio.current_task())
117+
# wait to be canceled
118+
await asyncio.sleep(100000)
119+
else:
120+
task = cls.unique_name2task[name]
121+
if task in cls.our_tasks:
122+
# only cancel tasks if they are ones we started
123+
try:
124+
task.cancel()
125+
await task
126+
except asyncio.CancelledError:
127+
pass
128+
task = asyncio.current_task()
129+
if task in cls.our_tasks:
130+
cls.unique_name2task[name] = task
131+
cls.unique_task2name[task] = name
132+
133+
return task_unique
128134

129135
@classmethod
130136
async def task_executor(cls, func, *args, **kwargs):
@@ -134,8 +140,9 @@ async def task_executor(cls, func, *args, **kwargs):
134140
return await cls.hass.async_add_executor_job(functools.partial(func, **kwargs), *args)
135141

136142
@classmethod
137-
def unique_name_used(cls, name):
143+
def unique_name_used(cls, ctx, name):
138144
"""Return whether the current unique name is in use."""
145+
name = f"{ctx.get_global_ctx_name()}.{name}"
139146
return name in cls.unique_name2task
140147

141148
@classmethod

custom_components/pyscript/trigger.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def __init__(
448448
self.time_active = trig_cfg.get("time_active", {}).get("args", None)
449449
self.task_unique = trig_cfg.get("task_unique", {}).get("args", None)
450450
self.task_unique_kwargs = trig_cfg.get("task_unique", {}).get("kwargs", None)
451+
self.task_unique_func = None
451452
self.action = trig_cfg.get("action")
452453
self.action_ast_ctx = trig_cfg.get("action_ast_ctx")
453454
self.global_sym_table = trig_cfg.get("global_sym_table", {})
@@ -505,6 +506,9 @@ def __init__(
505506
return
506507
self.have_trigger = True
507508

509+
if self.task_unique:
510+
self.task_unique_func = Function.task_unique_factory(self.action_ast_ctx)
511+
508512
self.setup_ok = True
509513

510514
def stop(self):
@@ -530,8 +534,8 @@ async def trigger_watch(self):
530534
try:
531535

532536
async def do_func_call(func, ast_ctx, task_unique, kwargs=None):
533-
if task_unique:
534-
await Function.task_unique(task_unique)
537+
if task_unique and self.task_unique_func:
538+
await self.task_unique_func(task_unique)
535539
await func.call(ast_ctx, kwargs=kwargs)
536540
if ast_ctx.get_exception_obj():
537541
ast_ctx.get_logger().error(ast_ctx.get_exception_long())
@@ -630,7 +634,7 @@ async def do_func_call(func, ast_ctx, task_unique, kwargs=None):
630634
self.task_unique is not None
631635
and self.task_unique_kwargs
632636
and self.task_unique_kwargs["kill_me"]
633-
and Function.unique_name_used(self.task_unique)
637+
and Function.unique_name_used(self.action_ast_ctx, self.task_unique)
634638
):
635639
_LOGGER.debug(
636640
"trigger %s got %s trigger, @task_unique kill_me=True prevented new action",

docs/reference.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,10 +569,8 @@ Task unique
569569
killed if another task that is running previously called ``task.unique`` with the same
570570
``task_name``.
571571

572-
Note that ``task.unique`` applies across all global contexts. It’s up to you to use a convention for
573-
``task_name`` that avoids accidental collisions. For example, you could use a prefix of the script
574-
file name, so that all ``task_unique`` calls in ``FILENAME.py`` use a ``task_name`` that starts with
575-
``"FILENAME."``.
572+
Note that ``task.unique`` is specific to the current global context, so names used in one
573+
global context will not affect another.
576574

577575
``task.unique`` can also be called outside a function, for example in the preamble of a script file
578576
or interactively using Jupyter. That causes any currently running functions (ie, functions that have
@@ -582,6 +580,10 @@ is the mechanism you can use should you wish to terminate specific functions on
582580
outside a function or interactively with Jupyter, calling ``task.unique`` with ``kill_me=True``
583581
causes ``task.unique`` to do nothing.
584582

583+
The ``task.unique`` functionality is also provided via a decorator ``@task_unique``. If your
584+
function immediately and always calls ``task.unique``, you could choose instead to use the
585+
function decorator form.
586+
585587
Task waiting
586588
^^^^^^^^^^^^
587589

0 commit comments

Comments
 (0)