Skip to content

Commit a30192c

Browse files
authored
165: Add workflow-level option support (#167)
* feat: add workflow_options to process definition * docs: add workflow_options to README * docs: update CHANGELOG * refactor: add properties for workflow_options and task_options * docs: update CHANGELOG to reflect the refactored Task class properties
1 parent 061090d commit a30192c

File tree

4 files changed

+96
-21
lines changed

4 files changed

+96
-21
lines changed

CHANGELOG.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- ([#167](https://github.com/stac-utils/stac-task/issues/167)) Adds workflow-level
13+
options to the ProcessDefinition object in a new `workflow_options` field. They are
14+
combined with each task's options, giving precedence to the task options on conflict.
15+
- ([#167](https://github.com/stac-utils/stac-task/issues/167)) Adds a `workflow_options`
16+
property to the `Task` class that returns the `workflow_options` dictionary from the
17+
`ProcessDefinition` object.
18+
- ([#167](https://github.com/stac-utils/stac-task/issues/167)) Adds a `task_options`
19+
property to the `Task` class that returns the task options from the `tasks` dictionary
20+
in the `ProcessDefinition` object.
21+
1022
### Deprecated
1123

12-
- ([#123](https://github.com/stac-utils/stac-task/issues/123)) Bare `ProcessDefinition`
24+
- ([#166](https://github.com/stac-utils/stac-task/issues/123)) Bare `ProcessDefinition`
1325
objects are deprecated in favor of arrays of `ProcessDefinition` objects.
1426

1527
## [0.6.0]

README.md

+14-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [collections](#collections)
1616
- [tasks](#tasks)
1717
- [TaskConfig Object](#taskconfig-object)
18+
- [workflow_options](#workflow_options)
1819
- [Full ProcessDefinition Example](#full-processdefinition-example)
1920
- [Migration](#migration)
2021
- [0.4.x -\> 0.5.x](#04x---05x)
@@ -76,12 +77,13 @@ Task input is often referred to as a 'payload'.
7677
A Task can be provided additional configuration via the 'process' field in the input
7778
payload.
7879

79-
| Field Name | Type | Description |
80-
| -------------- | ------------------ | ---------------------------------------------- |
81-
| description | string | Description of the process configuration |
82-
| upload_options | `UploadOptions` | An `UploadOptions` object |
83-
| tasks | Map<str, Map> | Dictionary of task configurations. |
84-
| ~~tasks~~ | ~~[`TaskConfig`]~~ | **DEPRECATED** A list of `TaskConfig` objects. |
80+
| Field Name | Type | Description |
81+
| ---------------- | ------------------ | ------------------------------------------------------------------------ |
82+
| description | string | Description of the process configuration |
83+
| upload_options | `UploadOptions` | An `UploadOptions` object |
84+
| tasks | Map<str, Map> | Dictionary of task configurations. |
85+
| ~~tasks~~ | ~~[`TaskConfig`]~~ | **DEPRECATED** A list of `TaskConfig` objects. |
86+
| workflow_options | Map<str, Any> | Dictionary of configuration options applied to all tasks in the workflow |
8587

8688

8789
#### UploadOptions Object
@@ -162,6 +164,12 @@ for backwards compatibility.
162164
| name | str | **REQUIRED** Name of the task |
163165
| parameters | Map<str, str> | Dictionary of keyword parameters that will be passed to the Task `process` function |
164166

167+
#### workflow_options
168+
169+
The 'workflow_options' field is a dictionary of options that apply to all tasks in the
170+
workflow. The 'workflow_options' dictionary is combined with each task's option
171+
dictionary. If a key in the 'workflow_options' dictionary conflicts with a key in a
172+
task's option dictionary, the task option value takes precedence.
165173

166174
### Full ProcessDefinition Example
167175

stactask/task.py

+35-14
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,33 @@ def process_definition(self) -> dict[str, Any]:
136136
return process[0]
137137

138138
@property
139-
def parameters(self) -> dict[str, Any]:
140-
task_configs = self.process_definition.get("tasks", {})
141-
if isinstance(task_configs, list):
139+
def workflow_options(self) -> dict[str, Any]:
140+
workflow_options_ = self.process_definition.get("workflow_options", {})
141+
if not isinstance(workflow_options_, dict):
142+
raise TypeError("unable to parse `workflow_options`: must be type dict")
143+
return workflow_options_
144+
145+
@property
146+
def task_options(self) -> dict[str, Any]:
147+
task_options_ = self.process_definition.get("tasks", {})
148+
if not isinstance(task_options_, (dict, list)):
149+
raise TypeError(
150+
"unable to parse `tasks`: must be type dict or type list (deprecated)"
151+
)
152+
153+
if isinstance(task_options_, list):
142154
warnings.warn(
143-
"task configs is list, use a dictionary instead",
155+
(
156+
"`tasks` as a list of TaskConfig objects will be unsupported in a "
157+
"future version; use a dictionary of task options to remove this "
158+
"warning"
159+
),
144160
DeprecationWarning,
145161
stacklevel=2,
146162
)
147-
task_config_list = [cfg for cfg in task_configs if cfg["name"] == self.name]
163+
task_config_list = [
164+
cfg for cfg in task_options_ if cfg["name"] == self.name
165+
]
148166
if len(task_config_list) == 0:
149167
return {}
150168
else:
@@ -153,17 +171,20 @@ def parameters(self) -> dict[str, Any]:
153171
if isinstance(parameters, dict):
154172
return parameters
155173
else:
156-
raise ValueError(f"parameters is not a dict: {type(parameters)}")
157-
elif isinstance(task_configs, dict):
158-
config = task_configs.get(self.name, {})
159-
if isinstance(config, dict):
160-
return config
174+
raise TypeError("unable to parse `parameters`: must be type dict")
175+
176+
if isinstance(task_options_, dict):
177+
options = task_options_.get(self.name, {})
178+
if isinstance(options, dict):
179+
return options
161180
else:
162-
raise ValueError(
163-
f"task config for {self.name} is not a dict: {type(config)}"
181+
raise TypeError(
182+
f"unable to parse options for task '{self.name}': must be type dict"
164183
)
165-
else:
166-
raise ValueError(f"unexpected value for 'tasks': {task_configs}")
184+
185+
@property
186+
def parameters(self) -> dict[str, Any]:
187+
return {**self.workflow_options, **self.task_options}
167188

168189
@property
169190
def upload_options(self) -> dict[str, Any]:

tests/test_task.py

+34
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,40 @@ def test_deprecated_payload_dict(nothing_task: Task) -> None:
5454
nothing_task.process_definition
5555

5656

57+
def test_workflow_options_append_task_options(nothing_task: Task) -> None:
58+
nothing_task._payload["process"][0]["workflow_options"] = {
59+
"workflow_option": "workflow_option_value"
60+
}
61+
parameters = nothing_task.parameters
62+
assert parameters == {
63+
"do_nothing": True,
64+
"workflow_option": "workflow_option_value",
65+
}
66+
67+
68+
def test_workflow_options_populate_when_no_task_options(nothing_task: Task) -> None:
69+
nothing_task._payload["process"][0]["tasks"].pop("nothing-task")
70+
nothing_task._payload["process"][0]["workflow_options"] = {
71+
"workflow_option": "workflow_option_value"
72+
}
73+
parameters = nothing_task.parameters
74+
assert parameters == {
75+
"workflow_option": "workflow_option_value",
76+
}
77+
78+
79+
def test_task_options_supersede_workflow_options(nothing_task: Task) -> None:
80+
nothing_task._payload["process"][0]["workflow_options"] = {
81+
"do_nothing": False,
82+
"workflow_option": "workflow_option_value",
83+
}
84+
parameters = nothing_task.parameters
85+
assert parameters == {
86+
"do_nothing": True,
87+
"workflow_option": "workflow_option_value",
88+
}
89+
90+
5791
def test_edit_items(nothing_task: Task) -> None:
5892
nothing_task.process_definition["workflow"] = "test-task-workflow"
5993
assert nothing_task._payload["process"][0]["workflow"] == "test-task-workflow"

0 commit comments

Comments
 (0)