From a4fc3ad9f46f1a6e5c7a25974d087b758a63ecae Mon Sep 17 00:00:00 2001 From: KamenDimitrov97 Date: Mon, 5 Aug 2024 09:53:57 +0300 Subject: [PATCH 1/4] feat: Updated workflows documentation for issue #47 --- docs/workflows.md | 75 ++++++++++++++++++++++++++++++++++--- example/workflow_complex.py | 17 +++++++-- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/docs/workflows.md b/docs/workflows.md index e44f10ea..74eab170 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -123,6 +123,26 @@ Notice that the `increment` tasks appears twice in the CWL workflow definition, This duplication can be avoided by explicitly indicating that the parameters are the same, with the `param` function. ```python +>>> @task() +... def increment(num: int) -> int: +... """Increment an integer.""" +... return num + 1 +>>> +>>> @task() +... def double(num: int) -> int: +... """Double an integer.""" +... return 2 * num +>>> +>>> @task() +... def mod10(num: int) -> int: +... """Take num mod 10.""" +... return num % 10 +>>> +>>> @task() +... def sum(left: int, right: int) -> int: +... """Add two integers.""" +... return left + right +>>> >>> num = param("num", default=3) >>> result = sum( ... left=double(num=increment(num=num)), @@ -249,6 +269,12 @@ As code: ```python >>> from dewret.tasks import nested_task +>>> INPUT_NUM = 3 +>>> @task() +... def rotate(num: int) -> int: +... """Rotate an integer.""" +... return (num + INPUT_NUM) % INPUT_NUM +>>> >>> @nested_task() ... def double_rotate(num: int) -> int: ... """Rotate an integer twice.""" @@ -531,8 +557,27 @@ A special form of nested task is available to help divide up more complex workflows: the *subworkflow*. By wrapping logic in subflows, dewret will produce multiple output workflows that reference each other. -``` +```python >>> from dewret.tasks import subworkflow +>>> from attrs import define +>>> from numpy import random +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> +>>> @task() +... def shuffle(max_cards_per_suit: int) -> PackResult: +... """Fill a random pile from a card deck, suit by suit.""" +... return PackResult( +... hearts=random.randint(max_cards_per_suit), +... clubs=random.randint(max_cards_per_suit), +... spades=random.randint(max_cards_per_suit), +... diamonds=random.randint(max_cards_per_suit) +... ) +>>> >>> my_param = param("num", typ=int) >>> @subworkflow() ... def red_total(): @@ -546,6 +591,10 @@ dewret will produce multiple output workflows that reference each other. ... left=shuffle(max_cards_per_suit=13).spades, ... right=shuffle(max_cards_per_suit=13).clubs ... ) +>>> @task() +... def sum(left: int, right: int) -> int: +... return left + right +>>> >>> total = sum(left=red_total(), right=black_total()) >>> workflow = construct(total, simplify_ids=True) >>> cwl, subworkflows = render(workflow) @@ -585,7 +634,7 @@ As we have used subworkflow to wrap the colour totals, the outer workflow contains references to them only. The subworkflows are now returned by `render` as a second term. -``` +```python >>> yaml.dump(subworkflows["red_total-1"], sys.stdout, indent=2) class: Workflow cwlVersion: 1.2 @@ -662,11 +711,18 @@ the chosen renderer has the capability. Below is the default output, treating `Pack` as a task. -``` +```python >>> from dewret.tasks import subworkflow, factory +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> >>> my_param = param("num", typ=int) >>> Pack = factory(PackResult) ->>> @nested_task() +>>> @subworkflow() ... def black_total(pack: PackResult): ... return sum( ... left=pack.spades, @@ -740,11 +796,18 @@ steps: The CWL renderer is also able to treat `pack` as a parameter, if complex types are allowed. -``` +```python >>> from dewret.tasks import subworkflow, factory +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> >>> my_param = param("num", typ=int) >>> Pack = factory(PackResult) ->>> @nested_task() +>>> @subworkflow() ... def black_total(pack: PackResult): ... return sum( ... left=pack.spades, diff --git a/example/workflow_complex.py b/example/workflow_complex.py index 764c9b06..a0a2dd28 100644 --- a/example/workflow_complex.py +++ b/example/workflow_complex.py @@ -7,15 +7,26 @@ ``` """ -from dewret.tasks import nested_task +from dewret.tasks import subworkflow from workflow_tasks import sum, double, increase STARTING_NUMBER: int = 23 -@nested_task() +@subworkflow() def nested_workflow() -> int | float: - """Creates a graph of task calls.""" + """Creates a complex workflow with a nested task. + + Workflow Steps: + 1. **Increase**: The starting number (`STARTING_NUMBER`) is incremented by 1 using the `increase` task. + 2. **Double**: The result from the first step is then doubled using the `double` task. + 3. **Increase Again**: Separately, the number 17 is incremented twice using the `increase` task. + 4. **Sum**: Finally, the results of the two branches (left and right) are summed together using the `sum` task. + + Returns: + - `int | float`: The result of summing the doubled and increased values, which may be an integer or a float depending on the operations. + + """ left = double(num=increase(num=STARTING_NUMBER)) right = increase(num=increase(num=17)) return sum(left=left, right=right) From a168b53473739f61536e0caf4141d9b53da833cc Mon Sep 17 00:00:00 2001 From: KamenDimitrov97 Date: Mon, 5 Aug 2024 11:47:27 +0300 Subject: [PATCH 2/4] wip: Reverted changes in previous commit due to doctest errors. Awaiting discussion on how to proceed. --- docs/workflows.md | 49 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/docs/workflows.md b/docs/workflows.md index 74eab170..eb274590 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -479,6 +479,7 @@ Here, we show the same example with `dataclasses`. >>> @task() ... def sum(left: int, right: int) -> int: ... return left + right +>>> >>> red_total = sum( ... left=shuffle(max_cards_per_suit=13).hearts, ... right=shuffle(max_cards_per_suit=13).diamonds @@ -559,26 +560,7 @@ dewret will produce multiple output workflows that reference each other. ```python >>> from dewret.tasks import subworkflow ->>> from attrs import define ->>> from numpy import random ->>> @define -... class PackResult: -... hearts: int -... clubs: int -... spades: int -... diamonds: int ->>> ->>> @task() -... def shuffle(max_cards_per_suit: int) -> PackResult: -... """Fill a random pile from a card deck, suit by suit.""" -... return PackResult( -... hearts=random.randint(max_cards_per_suit), -... clubs=random.randint(max_cards_per_suit), -... spades=random.randint(max_cards_per_suit), -... diamonds=random.randint(max_cards_per_suit) -... ) ->>> ->>> my_param = param("num", typ=int) +>>> my_param = param(name="num", typ=int) >>> @subworkflow() ... def red_total(): ... return sum( @@ -591,10 +573,6 @@ dewret will produce multiple output workflows that reference each other. ... left=shuffle(max_cards_per_suit=13).spades, ... right=shuffle(max_cards_per_suit=13).clubs ... ) ->>> @task() -... def sum(left: int, right: int) -> int: -... return left + right ->>> >>> total = sum(left=red_total(), right=black_total()) >>> workflow = construct(total, simplify_ids=True) >>> cwl, subworkflows = render(workflow) @@ -634,7 +612,7 @@ As we have used subworkflow to wrap the colour totals, the outer workflow contains references to them only. The subworkflows are now returned by `render` as a second term. -```python +``` >>> yaml.dump(subworkflows["red_total-1"], sys.stdout, indent=2) class: Workflow cwlVersion: 1.2 @@ -711,18 +689,11 @@ the chosen renderer has the capability. Below is the default output, treating `Pack` as a task. -```python +``` >>> from dewret.tasks import subworkflow, factory ->>> @define -... class PackResult: -... hearts: int -... clubs: int -... spades: int -... diamonds: int ->>> >>> my_param = param("num", typ=int) >>> Pack = factory(PackResult) ->>> @subworkflow() +>>> @nested_task() ... def black_total(pack: PackResult): ... return sum( ... left=pack.spades, @@ -796,18 +767,12 @@ steps: The CWL renderer is also able to treat `pack` as a parameter, if complex types are allowed. +In this example we take ```python >>> from dewret.tasks import subworkflow, factory ->>> @define -... class PackResult: -... hearts: int -... clubs: int -... spades: int -... diamonds: int ->>> >>> my_param = param("num", typ=int) >>> Pack = factory(PackResult) ->>> @subworkflow() +>>> @nested_task() ... def black_total(pack: PackResult): ... return sum( ... left=pack.spades, From bcc6c6f08ddc868488e2b64e8763185e6b5f472c Mon Sep 17 00:00:00 2001 From: KamenDimitrov97 Date: Wed, 7 Aug 2024 11:53:33 +0300 Subject: [PATCH 3/4] docs: Updated doc examples issue 47, making sure examples are easily followable. --- docs/workflows.md | 145 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/docs/workflows.md b/docs/workflows.md index eb274590..94b05efb 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -35,6 +35,10 @@ graph TD In code, this would be: ```python +>>> import sys +>>> import yaml +>>> from dewret.tasks import task, construct +>>> from dewret.renderers.cwl import render >>> @task() ... def increment(num: int) -> int: ... """Increment an integer.""" @@ -123,6 +127,11 @@ Notice that the `increment` tasks appears twice in the CWL workflow definition, This duplication can be avoided by explicitly indicating that the parameters are the same, with the `param` function. ```python +>>> import sys +>>> import yaml +>>> from dewret.workflow import param +>>> from dewret.tasks import task, construct +>>> from dewret.renderers.cwl import render >>> @task() ... def increment(num: int) -> int: ... """Increment an integer.""" @@ -211,6 +220,11 @@ While global variables are implicit input to the Python function **note that**: For example: ```python +>>> import sys +>>> import yaml +>>> from dewret.workflow import param +>>> from dewret.tasks import task, construct +>>> from dewret.renderers.cwl import render >>> INPUT_NUM = 3 >>> @task() ... def rotate(num: int) -> int: @@ -268,7 +282,10 @@ graph TD As code: ```python ->>> from dewret.tasks import nested_task +>>> import sys +>>> import yaml +>>> from dewret.tasks import task, construct, nested_task +>>> from dewret.renderers.cwl import render >>> INPUT_NUM = 3 >>> @task() ... def rotate(num: int) -> int: @@ -330,6 +347,7 @@ nested task does not have an impact on the return value, it will disappear__. For example, the following code renders the same workflow as in the previous example: + ```python @nested_task() def double_rotate(num: int) -> int: @@ -362,8 +380,12 @@ graph TD As code: ```python +>>> import sys +>>> import yaml >>> from attrs import define >>> from numpy import random +>>> from dewret.tasks import task, construct +>>> from dewret.renderers.cwl import render >>> @define ... class PackResult: ... hearts: int @@ -457,9 +479,13 @@ steps: Here, we show the same example with `dataclasses`. -```python +```python +>>> import sys +>>> import yaml >>> from dataclasses import dataclass >>> from numpy import random +>>> from dewret.tasks import task, construct +>>> from dewret.renderers.cwl import render >>> @dataclass ... class PackResult: ... hearts: int @@ -559,8 +585,32 @@ more complex workflows: the *subworkflow*. By wrapping logic in subflows, dewret will produce multiple output workflows that reference each other. ```python ->>> from dewret.tasks import subworkflow ->>> my_param = param(name="num", typ=int) +>>> import sys +>>> import yaml +>>> from attrs import define +>>> from numpy import random +>>> from dewret.tasks import task, construct, subworkflow +>>> from dewret.renderers.cwl import render +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> +>>> @task() +... def sum(left: int, right: int) -> int: +... return left + right +>>> +>>> @task() +... def shuffle(max_cards_per_suit: int) -> PackResult: +... """Fill a random pile from a card deck, suit by suit.""" +... return PackResult( +... hearts=random.randint(max_cards_per_suit), +... clubs=random.randint(max_cards_per_suit), +... spades=random.randint(max_cards_per_suit), +... diamonds=random.randint(max_cards_per_suit) +... ) >>> @subworkflow() ... def red_total(): ... return sum( @@ -612,7 +662,48 @@ As we have used subworkflow to wrap the colour totals, the outer workflow contains references to them only. The subworkflows are now returned by `render` as a second term. -``` +```python +>>> import sys +>>> import yaml +>>> from attrs import define +>>> from numpy import random +>>> from dewret.tasks import task, construct, subworkflow +>>> from dewret.renderers.cwl import render +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> +>>> @task() +... def shuffle(max_cards_per_suit: int) -> PackResult: +... """Fill a random pile from a card deck, suit by suit.""" +... return PackResult( +... hearts=random.randint(max_cards_per_suit), +... clubs=random.randint(max_cards_per_suit), +... spades=random.randint(max_cards_per_suit), +... diamonds=random.randint(max_cards_per_suit) +... ) +>>> @task() +... def sum(left: int, right: int) -> int: +... return left + right +>>> +>>> @subworkflow() +... def red_total(): +... return sum( +... left=shuffle(max_cards_per_suit=13).hearts, +... right=shuffle(max_cards_per_suit=13).diamonds +... ) +>>> @subworkflow() +... def black_total(): +... return sum( +... left=shuffle(max_cards_per_suit=13).spades, +... right=shuffle(max_cards_per_suit=13).clubs +... ) +>>> total = sum(left=red_total(), right=black_total()) +>>> workflow = construct(total, simplify_ids=True) +>>> cwl, subworkflows = render(workflow) >>> yaml.dump(subworkflows["red_total-1"], sys.stdout, indent=2) class: Workflow cwlVersion: 1.2 @@ -689,10 +780,25 @@ the chosen renderer has the capability. Below is the default output, treating `Pack` as a task. -``` ->>> from dewret.tasks import subworkflow, factory ->>> my_param = param("num", typ=int) +```python +>>> import sys +>>> import yaml +>>> from dewret.tasks import subworkflow, factory, nested_task, construct, task +>>> from attrs import define +>>> from dewret.renderers.cwl import render +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> >>> Pack = factory(PackResult) +>>> +>>> @task() +... def sum(left: int, right: int) -> int: +... return left + right +>>> >>> @nested_task() ... def black_total(pack: PackResult): ... return sum( @@ -767,11 +873,24 @@ steps: The CWL renderer is also able to treat `pack` as a parameter, if complex types are allowed. -In this example we take ```python ->>> from dewret.tasks import subworkflow, factory ->>> my_param = param("num", typ=int) +>>> import sys +>>> import yaml +>>> from dewret.tasks import task, factory, nested_task, construct +>>> from attrs import define +>>> from dewret.renderers.cwl import render +>>> @define +... class PackResult: +... hearts: int +... clubs: int +... spades: int +... diamonds: int +>>> >>> Pack = factory(PackResult) +>>> @task() +... def sum(left: int, right: int) -> int: +... return left + right +>>> >>> @nested_task() ... def black_total(pack: PackResult): ... return sum( @@ -787,7 +906,7 @@ cwlVersion: 1.2 inputs: PackResult-1: label: PackResult-1 - type: PackResult + type: record outputs: out: label: out @@ -804,4 +923,4 @@ steps: - out run: sum -``` +``` \ No newline at end of file From 573de66a73d4a6f6a1ab4fe1c70322afce148b4f Mon Sep 17 00:00:00 2001 From: KamenDimitrov97 Date: Wed, 7 Aug 2024 12:10:28 +0300 Subject: [PATCH 4/4] fix(condaCI): Added -y flag to skip a couple of seconds waiting for a response in CI. --- .github/workflows/python-test-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test-ci.yml b/.github/workflows/python-test-ci.yml index 24ac5a87..2cba3af2 100644 --- a/.github/workflows/python-test-ci.yml +++ b/.github/workflows/python-test-ci.yml @@ -39,7 +39,7 @@ jobs: - uses: s-weigand/setup-conda@v1 - name: Install built package run: | - conda install -c /tmp/output/noarch/*.conda --update-deps --use-local dewret + conda install -c /tmp/output/noarch/*.conda --update-deps --use-local dewret -y conda install pytest $CONDA/bin/pytest python -m pytest --doctest-modules --ignore=example