Skip to content

Commit

Permalink
update userguide with python API docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Vasu Jaganath committed Jun 20, 2024
1 parent ae7adf8 commit 11c5988
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 1 deletion.
195 changes: 194 additions & 1 deletion docs/userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,197 @@ steps:

Note that this is one key difference between WIC and CWL. In CWL, all inputs must be given in a separate file. In WIC, inputs can be given inline with !ii and after compilation they will be automatically extracted into the separate file.

(NOTE: raw CWL is still supported with the --allow_raw_cwl flag.)
(NOTE: raw CWL is still supported with the --allow_raw_cwl flag.)

## Python API (experimental)
In addition to YAML based language for building workflows Sophios also provides a python API. The aspirational goal of this API is to be close to regular
usage of python. This API leverages YAML based syntax by transforming the Python workflow internally into a regular Sophios YAML workflow.

### basics
Let us take the most basic workflow *`hello world`*. This is how we write it in YAML syntax.

```
steps:
- echo:
in:
message: !ii Hello World
```

The Python API closely follows the YAML syntax. We create steps and from steps we create workflows. The API exposes the means to create Step and Workflow objects. The steps and workflows are just plain objects in Python which can be passed around, manipulated, composed and reused. We can write the above workflow as follows using python API.

```
from sophios.api.pythonapi import Step, Workflow
def workflow() -> Workflow:
# step echo
echo = Step(clt_path='../../cwl_adapters/echo.cwl')
echo.message = 'hello world'
# arrange steps
steps = [echo]
# create workflow
filename = 'helloworld_pyapi_py'
wkflw = Workflow(steps, filename)
return wkflw
# Do NOT .run() here
if __name__ == '__main__':
helloworld = workflow()
helloworld.run() # .run() here inside main
```

Here `echo` is a step object created by specifying the path of cwl adapter (a basic cwl workflow) `echo.cwl`. The the input to `echo` is `message` the user can assign value directly (i.e, inline) or create another cwl object compatible with the type of `message`. It is to be noted that we didn't have to specify if `message` is an input type, the specified attributes of the step object gets mapped to the corresponding `input` or `output` of the cwl step if it exists.

A workflow object is created using a list of steps in **`correct order`** and a unique filename. As there is only one step in this example hence the workflow object is created with a list containing only one step `echo`.

### multistep

```
steps:
- id: touch
in:
filename: !ii empty.txt
- id: append
in:
str: !ii Hello
- id: cat
```
We can write the above workflow as follows using python API.

```
from sophios.api.pythonapi import Step, Workflow
def workflow() -> Workflow:
# step echo
touch = Step(clt_path='../../cwl_adapters/touch.cwl')
echo.filename = 'empty.txt'
append = Step(clt_path='../../cwl_adapters/append.cwl')
append.str = 'Hello'
cat = Step(clt_path='../../cwl_adapters/cat.cwl')
# arrange steps
steps = [echo,append,cat]
# create workflow
filename = 'multistep1_pyapi_py'
wkflw = Workflow(steps, filename)
return wkflw
# Do NOT .run() here
if __name__ == '__main__':
mutistep1 = workflow()
multistep1.run() # .run() here inside main
```

### scattering

```
# Demonstrates scattering on a subset of inputs and a non default scattering method
steps:
- id: array_indices
in:
input_array: !ii ["hello world", "not", "what world?"]
input_indices: !ii [0,2]
out:
- output_array: !& filt_message
- id: echo_3
scatter: [message1,message2]
scatterMethod: flat_crossproduct
in:
message1: !* filt_message
message2: !* filt_message
message3: !ii scalar
```
We can write the above workflow as follows using python API.

```
from sophios.api.pythonapi import Step, Workflow
def workflow() -> Workflow:
# scatter on a subset of inputs
# step array_indices
array_ind = Step(clt_path='../../cwl_adapters/array_indices.cwl')
array_ind.input_array = ["hello world", "not", "what world?"]
array_ind.input_indices = [0, 2]
# step echo_3
echo_3 = Step(clt_path='../../cwl_adapters/echo_3.cwl')
echo_3.message1 = array_ind.output_array
echo_3.message2 = array_ind.output_array
echo_3.message3 = 'scalar'
# set up inputs for scattering
msg1 = echo_3.inputs[0]
msg2 = echo_3.inputs[1]
# assign the scatter and scatterMethod fields
echo_3.scatter = [msg1, msg2]
echo_3.scatterMethod = 'flat_crossproduct'
# arrange steps
steps = [array_ind, echo_3]
# create workflow
filename = 'scatter_pyapi_py' # .yml
wkflw = Workflow(steps, filename)
return wkflw
# Do NOT .run() here
if __name__ == '__main__':
scatter_wic = workflow()
scatter_wic.run() # .run() here inside main
```

### conditional

```
steps:
toString:
in:
input: !ii 27
out:
- output: !& string_int
echo:
when: '$(inputs.message < "27")'
in:
message: !* string_int
```

We can write the above workflow as follows using python API.

```
from sophios.api.pythonapi import Step, Workflow
def workflow() -> Workflow:
# conditional on input
# step toString
toString = Step(clt_path='../../cwl_adapters/toString.cwl')
toString.input = 27
# step echo
echo = Step(clt_path='../../cwl_adapters/echo.cwl')
echo.message = toString.output
# add a when clause
# alternate js syntax
# echo.when = '$(inputs["message"] < 27)'
echo.when = '$(inputs.message < 27)'
# since the condition is not met the echo step is skipped!
# arrange steps
steps = [toString, echo]
# create workflow
filename = 'when_pyapi_py' # .yml
wkflw = Workflow(steps, filename)
return wkflw
# Do NOT .run() here
if __name__ == '__main__':
when_wic = workflow()
when_wic.run() # .run() here inside main
```
21 changes: 21 additions & 0 deletions examples/scripts/helloworld_pyapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from sophios.api.pythonapi import Step, Workflow


def workflow() -> Workflow:
# step echo
echo = Step(clt_path='../../cwl_adapters/echo.cwl')
echo.message = 'hello world'
# arrange steps
steps = [echo]

# create workflow
filename = 'helloworld_pyapi_py'
wkflw = Workflow(steps, filename)
return wkflw

# Do NOT .run() here


if __name__ == '__main__':
scatter_wic = workflow()
scatter_wic.run() # .run() here inside main

0 comments on commit 11c5988

Please sign in to comment.