Skip to content

Commit a675670

Browse files
committed
override task runtime values from input JSON
1 parent 94bfedf commit a675670

File tree

6 files changed

+79
-10
lines changed

6 files changed

+79
-10
lines changed

WDL/CLI.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ def bold(line):
971971
ans.append(bold(f" {str(b.value.type)} {b.name}"))
972972
add_wrapped_parameter_meta(target, b.name, ans)
973973
optional_inputs = target.available_inputs.subtract(target.required_inputs)
974+
optional_inputs = optional_inputs.filter(lambda b: not b.value.name.startswith("_"))
974975
if target.inputs is None:
975976
# if the target doesn't have an input{} section (pre WDL 1.0), exclude
976977
# declarations bound to a non-constant expression (heuristic)

WDL/Tree.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ def available_inputs(self) -> Env.Bindings[Decl]:
316316
Each input is at the top level of the Env, with no namespace.
317317
"""
318318
ans = Env.Bindings()
319+
320+
if self.effective_wdl_version not in ["draft-2", "1.0"]:
321+
# synthetic placeholder to expose runtime & hints overrides
322+
ans = ans.bind("_runtime", Decl(self.pos, Type.Any(), "_runtime"))
323+
319324
for decl in reversed(self.inputs if self.inputs is not None else self.postinputs):
320325
ans = ans.bind(decl.name, decl)
321326
return ans
@@ -333,7 +338,7 @@ def required_inputs(self) -> Env.Bindings[Decl]:
333338
for b in reversed(list(self.available_inputs)):
334339
assert isinstance(b, Env.Binding)
335340
d: Decl = b.value
336-
if d.expr is None and d.type.optional is False:
341+
if d.expr is None and d.type.optional is False and not d.name.startswith("_"):
337342
ans = Env.Bindings(b, ans)
338343
return ans
339344

WDL/__init__.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,33 @@ def values_from_json(
242242
key2 = key
243243
if namespace and key.startswith(namespace):
244244
key2 = key[len(namespace) :]
245-
if key2 not in available:
246-
# attempt to simplify <call>.<subworkflow>.<input>
245+
246+
ty = None
247+
if key2 in available:
248+
ty = available[key2]
249+
else:
247250
key2parts = key2.split(".")
248-
if len(key2parts) == 3 and key2parts[0] and key2parts[1] and key2parts[2]:
251+
252+
runtime_idx = next(
253+
(i for i, term in enumerate(key2parts) if term in ("runtime", "hints")), -1
254+
)
255+
if (
256+
runtime_idx >= 0
257+
and len(key2parts) > (runtime_idx + 1)
258+
and ".".join(key2parts[:runtime_idx] + ["_runtime"]) in available
259+
):
260+
# allow arbitrary keys for runtime/hints
261+
ty = Type.Any()
262+
elif len(key2parts) == 3 and key2parts[0] and key2parts[1] and key2parts[2]:
263+
# attempt to simplify <call>.<subworkflow>.<input> from old Cromwell JSON
249264
key2 = ".".join([key2parts[0], key2parts[2]])
250-
try:
251-
ty = available[key2]
252-
except KeyError:
265+
if key2 in available:
266+
ty = available[key2]
267+
if not ty:
253268
raise Error.InputError("unknown input/output: " + key) from None
254269
if isinstance(ty, Tree.Decl):
255270
ty = ty.type
271+
256272
assert isinstance(ty, Type.Base)
257273
try:
258274
ans = ans.bind(key2, Value.from_json(ty, values_json[key]))

WDL/runtime/task.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def run_local_task(
167167
# evaluate runtime fields
168168
stdlib = InputStdLib(task.effective_wdl_version, logger, container)
169169
container.runtime_values = _eval_task_runtime(
170-
cfg, logger, task, container, container_env, stdlib
170+
cfg, logger, task, posix_inputs, container, container_env, stdlib
171171
)
172172

173173
# interpolate command
@@ -409,6 +409,7 @@ def _eval_task_runtime(
409409
cfg: config.Loader,
410410
logger: logging.Logger,
411411
task: Tree.Task,
412+
inputs: Env.Bindings[Value.Base],
412413
container: TaskContainer,
413414
env: Env.Bindings[Value.Base],
414415
stdlib: StdLib.Base,
@@ -421,8 +422,10 @@ def _eval_task_runtime(
421422
runtime_values[key] = Value.Int(v)
422423
else:
423424
raise Error.InputError(f"invalid default runtime setting {key} = {v}")
424-
for key, expr in task.runtime.items():
425+
for key, expr in task.runtime.items(): # evaluate expressions in source code
425426
runtime_values[key] = expr.eval(env, stdlib)
427+
for b in inputs.enter_namespace("runtime"):
428+
runtime_values[b.name] = b.value # input overrides
426429
logger.debug(_("runtime values", **dict((key, str(v)) for key, v in runtime_values.items())))
427430
ans = {}
428431

WDL/runtime/workflow.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,14 @@ def _do_job(
371371
assert isinstance(job.node.callee, (Tree.Task, Tree.Workflow))
372372
callee_inputs = job.node.callee.available_inputs
373373
call_inputs = call_inputs.map(
374-
lambda b: Env.Binding(b.name, b.value.coerce(callee_inputs[b.name].type))
374+
lambda b: Env.Binding(
375+
b.name,
376+
(
377+
b.value.coerce(callee_inputs[b.name].type)
378+
if b.name in callee_inputs
379+
else b.value
380+
),
381+
)
375382
)
376383
# check input files against whitelist
377384
disallowed_filenames = _fspaths(call_inputs) - self.filename_whitelist

tests/test_7runner.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,43 @@ def test_directory(self, capture):
678678
logs += new_logs
679679

680680

681+
class RuntimeOverride(RunnerTestCase):
682+
def test_runtime_override(self):
683+
wdl = """
684+
version development
685+
workflow w {
686+
input {
687+
String who
688+
}
689+
call t {
690+
input:
691+
who = who
692+
}
693+
}
694+
task t {
695+
input {
696+
String who
697+
}
698+
command {
699+
cp /etc/issue issue
700+
echo "Hello, ~{who}!"
701+
}
702+
output {
703+
String msg = read_string(stdout())
704+
String issue = read_string("issue")
705+
}
706+
runtime {
707+
docker: "ubuntu:20.04"
708+
}
709+
}
710+
"""
711+
outp = self._run(wdl, {
712+
"who": "Alice",
713+
"t.runtime.docker": "ubuntu:20.10"
714+
})
715+
assert "20.10" in outp["t.issue"]
716+
717+
681718
class MiscRegressionTests(RunnerTestCase):
682719
def test_repeated_file_rewriting(self):
683720
wdl = """

0 commit comments

Comments
 (0)