@@ -953,8 +953,9 @@ def _validate_task_inputs(self):
953
953
for task_spec in self .task_specs :
954
954
in_ = task_spec .get ("in" , None )
955
955
requires = task_spec .get ("requires" , None )
956
+ wait = task_spec .get ("wait" , None )
956
957
957
- for key , value in (["in" , in_ ], ["requires" , requires ]):
958
+ for key , value in (["in" , in_ ], ["requires" , requires ], [ "wait" , wait ] ):
958
959
if value is None :
959
960
continue
960
961
if not isinstance (value , list ):
@@ -970,7 +971,7 @@ def _get_task_from_spec(self, task_spec: dict):
970
971
"""Set up a pipeline task from the spec given in the tasks list."""
971
972
# Check that only the expected keys are in the task spec.
972
973
for key in task_spec .keys ():
973
- if key not in ["type" , "params" , "requires" , "in" , "out" ]:
974
+ if key not in ["type" , "params" , "requires" , "in" , "out" , "wait" ]:
974
975
raise config .CaputConfigError (
975
976
f"Task got an unexpected key '{ key } ' in 'tasks' list."
976
977
)
@@ -1073,9 +1074,10 @@ def _check_duplicate(key0: str, key1: str, d0: dict, d1: dict):
1073
1074
requires = _check_duplicate ("requires" , "requires" , task_spec , kwargs )
1074
1075
in_ = _check_duplicate ("in" , "in_" , task_spec , kwargs )
1075
1076
out = _check_duplicate ("out" , "out" , task_spec , kwargs )
1077
+ wait = _check_duplicate ("wait" , "wait" , task_spec , kwargs )
1076
1078
1077
1079
try :
1078
- task ._setup_keys (in_ , out , requires )
1080
+ task ._setup_keys (in_ , out , requires , wait )
1079
1081
# Want to blindly catch errors
1080
1082
except Exception as e :
1081
1083
raise config .CaputConfigError (
@@ -1126,12 +1128,17 @@ class TaskBase(config.Reader):
1126
1128
If true, signals to the pipeline runner to make a call to `breakpoint` each
1127
1129
time this task is run. This will drop the interpreter into pdb, allowing for
1128
1130
interactive debugging of the current pipeline and task state. Default is False.
1131
+ single_wait : bool
1132
+ If true, keys in the wait queue only have to be received once, even if `next`
1133
+ iterates multiple times. Otherwise, `wait` keys must be received prior to
1134
+ each iteration of `next`. Default is False.
1129
1135
"""
1130
1136
1131
1137
broadcast_inputs = config .Property (proptype = bool , default = False )
1132
1138
limit_outputs = config .Property (proptype = int , default = None )
1133
1139
base_priority = config .Property (proptype = int , default = 0 )
1134
1140
breakpoint = config .Property (proptype = bool , default = False )
1141
+ single_wait = config .Property (proptype = bool , default = False )
1135
1142
1136
1143
# Overridable Attributes
1137
1144
# -----------------------
@@ -1231,6 +1238,13 @@ def _pipeline_is_available(self):
1231
1238
# This task hasn't been initialized
1232
1239
return False
1233
1240
1241
+ if self ._wait is not None and not bool (
1242
+ min ((q .qsize () for q in self ._wait ), default = 1 )
1243
+ ):
1244
+ # If wait flags are required and have not been received,
1245
+ # this task can't be run
1246
+ return False
1247
+
1234
1248
if self ._pipeline_state == "setup" :
1235
1249
# True if all `requires` items have been provided
1236
1250
# This also returns True is `self._requires` is empty
@@ -1311,12 +1325,13 @@ def _from_config(cls, config):
1311
1325
1312
1326
return self
1313
1327
1314
- def _setup_keys (self , in_ = None , out = None , requires = None ):
1315
- """Setup the 'requires ', 'in' and 'out ' keys for this task."""
1328
+ def _setup_keys (self , in_ = None , out = None , requires = None , wait = None ):
1329
+ """Setup the 'in ', 'out', 'requires', and 'wait ' keys for this task."""
1316
1330
# Parse the task spec.
1317
1331
requires = _format_product_keys (requires )
1318
1332
in_ = _format_product_keys (in_ )
1319
1333
out = _format_product_keys (out )
1334
+ wait = _format_product_keys (wait )
1320
1335
1321
1336
# Inspect the `setup` method to see how many arguments it takes.
1322
1337
setup_argspec = inspect .getfullargspec (self .setup )
@@ -1380,6 +1395,11 @@ def _setup_keys(self, in_=None, out=None, requires=None):
1380
1395
# produce multiple values, queue up items which may be used in the
1381
1396
# future
1382
1397
self ._in = [queue .Queue () for _ in range (n_in )]
1398
+ # Store wait keys
1399
+ self ._wait_keys = wait
1400
+ # Make a list with a queue for each wait key. Use queue because this can
1401
+ # be buffered similarly to the inputs
1402
+ self ._wait = [queue .Queue () for _ in range (len (wait ))]
1383
1403
# Store output keys
1384
1404
self ._out_keys = out
1385
1405
# Keep track of the number of times this task has produced output
@@ -1434,6 +1454,7 @@ def _pipeline_advance_state(self):
1434
1454
)
1435
1455
1436
1456
self ._in = None
1457
+ self ._wait = None
1437
1458
self ._pipeline_state = "finish"
1438
1459
1439
1460
elif self ._pipeline_state == "finish" :
@@ -1476,6 +1497,10 @@ def _pipeline_next(self):
1476
1497
else : # noqa RET506
1477
1498
# Get the next set of data to be run.
1478
1499
args = tuple (in_ .get () for in_ in self ._in )
1500
+ # If `wait` flags are not pinned, remove them
1501
+ # from the queue
1502
+ if not self .single_wait :
1503
+ _ = [w .get () for w in self ._wait ]
1479
1504
1480
1505
# Call the next iteration of `next`. If it is done running,
1481
1506
# advance the task state and continue
@@ -1548,13 +1573,24 @@ def _pipeline_queue_product(self, key, product):
1548
1573
logger .debug (f"{ self !s} stowing data product with key { key } for `in`." )
1549
1574
if self ._in is None :
1550
1575
raise PipelineRuntimeError (
1551
- "Tried to queue 'in' data product, but `next()` already run ."
1576
+ f "Tried to queue 'in' data product, but task state is ` { self . _pipeline_state } ` ."
1552
1577
)
1553
1578
1554
1579
self ._in [ii ].put (product )
1555
1580
1556
1581
result = True
1557
1582
1583
+ if key in self ._wait_keys :
1584
+ ii = self ._wait_keys .index (key )
1585
+ logger .debug (f"{ self !s} setting wait flag with key { key } ." )
1586
+ if self ._wait is None :
1587
+ raise PipelineRuntimeError (
1588
+ f"Tried to queue `wait` flag, but task state is `{ self ._pipeline_state } `."
1589
+ )
1590
+ # This data product isn't needed here - just have to record
1591
+ # that it was received
1592
+ self ._wait [ii ].put (True )
1593
+
1558
1594
return result
1559
1595
1560
1596
@@ -2089,10 +2125,10 @@ def next(self, in_):
2089
2125
def _format_product_keys (keys ):
2090
2126
"""Formats the pipeline task product keys.
2091
2127
2092
- In the pipeline config task list, the values of 'requires', 'in' and 'out'
2093
- are keys representing data products. This function gets that key from the
2094
- task's entry of the task list, defaults to zero, and ensures it's formated
2095
- as a sequence of strings.
2128
+ In the pipeline config task list, the values of 'requires', 'in', 'out' and
2129
+ 'wait' are keys representing data products. This function gets that key
2130
+ from the task's entry of the task list, defaults to zero, and ensures it's
2131
+ formated as a sequence of strings.
2096
2132
"""
2097
2133
if keys is None :
2098
2134
return []
0 commit comments