Skip to content

Commit 08666ea

Browse files
authored
Merge pull request #13 from antmendoza/fix-inject-state-hydration
fix-inject-state-hydration
2 parents bec19d9 + 1963b6c commit 08666ea

11 files changed

+148
-60
lines changed

README.md

+36-14
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
Provides the Python API/SPI for the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification)
44

5-
> This is a WIP implementation
6-
75
With the SDK you can:
8-
* **WIP** Programmatically build workflow definitions
6+
* Programmatically build workflow definitions
97
* Parse workflow JSON and YAML definitions
108
* Validate workflow definitions
119

@@ -17,26 +15,50 @@ With the SDK you can:
1715
- pipenv required `pip install pipenv`
1816

1917
```
20-
pipenv install
18+
pipenv install --dev
2119
2220
pipenv shell
2321
2422
python setup.py pytest
2523
```
2624

27-
## **WIP** Programmatically build workflow definitions
25+
## Programmatically build workflow definitions
2826

2927
```
30-
workflow = Workflow(id_="greeting",
31-
name="Greeting Workflow",
32-
description="Greet Someone",
33-
version='1.0',
34-
specVersion='0.8',
35-
start="Greet",
36-
states=[],
37-
functions=[]
38-
)
28+
workflow = Workflow(
29+
id_="greeting",
30+
name="Greeting Workflow",
31+
description="Greet Someone",
32+
version='1.0',
33+
specVersion='0.8',
34+
start="Greet",
35+
states=[
36+
OperationState(
37+
name="Greet",
38+
type="operation",
39+
actions=[
40+
Action(
41+
functionRef=FunctionRef(
42+
refName="greetingFunction",
43+
arguments={
44+
"name": "${ .person.name }"
45+
}
46+
),
47+
actionDataFilter=ActionDataFilter(
48+
results="${ .greeting }"
49+
)
50+
)
51+
],
52+
end=True
53+
)
54+
],
55+
functions=[
56+
Function(name="greetingFunction",
57+
operation="file://myapis/greetingapis.json#greeting")
58+
]
59+
)
3960
```
61+
You can see a full example in the [test_workflow.py](tests/serverlessworkflow/sdk/test_workflow.py) file
4062

4163
## Parse workflow JSON and YAML definitions
4264

serverlessworkflow/sdk/databased_switch_state.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from serverlessworkflow.sdk.default_condition_def import DefaultConditionDef
77
from serverlessworkflow.sdk.end_data_condition import EndDataCondition
88
from serverlessworkflow.sdk.error import Error
9-
from serverlessworkflow.sdk.hydration import HydratableParameter, UnionTypeOf, ComplexTypeOf, ArrayTypeOf, \
9+
from serverlessworkflow.sdk.hydration import HydratableParameter, ComplexTypeOf, ArrayTypeOf, \
1010
Fields
1111
from serverlessworkflow.sdk.metadata import Metadata
1212
from serverlessworkflow.sdk.state import State
@@ -52,7 +52,8 @@ def f_hydration(p_key, p_value):
5252
return HydratableParameter(value=p_value).hydrateAs(ComplexTypeOf(DataBasedSwitchStateTime0ut))
5353

5454
if p_key == 'dataConditions':
55-
return [DataBasedSwitchState.hydrate_state(v) if type(v) is not (TransitionDataCondition or EndDataCondition) else v for v in p_value]
55+
return [DataBasedSwitchState.hydrate_state(v) if not (
56+
isinstance(v, TransitionDataCondition or EndDataCondition)) else v for v in p_value]
5657

5758
if p_key == 'onErrors':
5859
return HydratableParameter(value=p_value).hydrateAs(ArrayTypeOf(Error))

serverlessworkflow/sdk/event_based_switch_state.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from serverlessworkflow.sdk.end_event_condition import EndEventCondition
77
from serverlessworkflow.sdk.error import Error
88
from serverlessworkflow.sdk.event_based_switch_state_timeout import EventBasedSwitchStateTimeOut
9-
from serverlessworkflow.sdk.hydration import HydratableParameter, ComplexTypeOf, UnionTypeOf, ArrayTypeOf, \
9+
from serverlessworkflow.sdk.hydration import HydratableParameter, ComplexTypeOf, ArrayTypeOf, \
1010
Fields
1111
from serverlessworkflow.sdk.metadata import Metadata
1212
from serverlessworkflow.sdk.state import State
@@ -52,7 +52,8 @@ def f_hydration(p_key, p_value):
5252
return HydratableParameter(value=p_value).hydrateAs(ComplexTypeOf(EventBasedSwitchStateTimeOut))
5353

5454
if p_key == 'eventConditions':
55-
return [EventBasedSwitchState.hydrate_state(v) if type(v) is not (TransitionEventCondition or EndEventCondition) else v for v in p_value]
55+
return [EventBasedSwitchState.hydrate_state(v) if not (
56+
isinstance(v, TransitionEventCondition or EndEventCondition)) else v for v in p_value]
5657

5758
if p_key == 'onErrors':
5859
return HydratableParameter(value=p_value).hydrateAs(ArrayTypeOf(Error))

serverlessworkflow/sdk/function_ref.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class FunctionRef:
1111

1212
def __init__(self,
1313
refName: str = None,
14-
arguments: dict[str, dict] = None,
14+
arguments: dict[str, any] = None,
1515
selectionSet: str = None,
1616
invoke: str = None,
1717
**kwargs):

serverlessworkflow/sdk/hydration.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

33
import dataclasses
4+
from abc import ABC, abstractmethod
45
from typing import Any
56

67

7-
class HydratableType:
8+
class HydratableType(ABC):
9+
@abstractmethod
810
def hydrate(self, value):
911
pass
1012

serverlessworkflow/sdk/inject_state.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def __init__(self,
4444
def f_hydration(p_key, p_value):
4545

4646
if p_key == 'end':
47-
return HydratableParameter(value=p_value).hydrateAs(UnionTypeOf(SimpleTypeOf(bool),
48-
ComplexTypeOf(End)))
47+
return HydratableParameter(value=p_value).hydrateAs(UnionTypeOf([SimpleTypeOf(bool),
48+
ComplexTypeOf(End)]))
4949
if p_key == 'data':
5050
return HydratableParameter(value=p_value).hydrateAs(UnionTypeOf([SimpleTypeOf(str),
5151
ComplexTypeOf(dict)]))

serverlessworkflow/sdk/workflow.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def f_hydration(p_key, p_value):
142142
ArrayTypeOf(AuthDef)]))
143143

144144
if p_key == 'states':
145-
return [Workflow.hydrate_state(v) if type(v) is not State else v for v in p_value]
145+
return [Workflow.hydrate_state(v) if not isinstance(v,State) else v for v in p_value]
146146

147147
if p_key == 'functions':
148148
return HydratableParameter(value=p_value).hydrateAs(UnionTypeOf([SimpleTypeOf(str),

tests/examples/helloworld.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "helloworld",
3+
"version": "1.0",
4+
"specVersion": "0.8",
5+
"name": "Hello World Workflow",
6+
"description": "Inject Hello World",
7+
"start": "Hello State",
8+
"states": [
9+
{
10+
"name": "Hello State",
11+
"type": "inject",
12+
"data": {
13+
"result": "Hello World!"
14+
},
15+
"end": true
16+
}
17+
]
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import unittest
2+
3+
from serverlessworkflow.sdk.event_based_switch_state import EventBasedSwitchState
4+
from serverlessworkflow.sdk.transition_event_condition import TransitionEventCondition
5+
6+
7+
class TestEventBasedSwitchState(unittest.TestCase):
8+
def test_programmatically_create_object(self):
9+
event_based_switch_state = EventBasedSwitchState(eventConditions=[TransitionEventCondition(
10+
name="Hold Book",
11+
eventRef="Hold Book Event",
12+
transition="Request Hold"
13+
14+
)])
15+
16+
self.assertTrue(isinstance(event_based_switch_state.eventConditions[0], TransitionEventCondition))

tests/serverlessworkflow/sdk/test_workflow.py

+64-35
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,49 @@
1+
import json
12
import os
23
import unittest
4+
from os import listdir
35

46
from serverlessworkflow.sdk.action import Action
7+
from serverlessworkflow.sdk.action_data_filter import ActionDataFilter
58
from serverlessworkflow.sdk.function import Function
69
from serverlessworkflow.sdk.function_ref import FunctionRef
10+
from serverlessworkflow.sdk.operation_state import OperationState
711
from serverlessworkflow.sdk.workflow import Workflow
812

913

1014
class TestWorkflow(unittest.TestCase):
11-
12-
workflow = Workflow(id_="greeting",
13-
name="Greeting Workflow",
14-
description="Greet Someone",
15-
version='1.0',
16-
specVersion='0.8',
17-
start="Greet",
18-
states=[
19-
{
20-
"name": "Greet",
21-
"type": "operation",
22-
"actions": [
23-
{
24-
"functionRef": {
25-
"refName": "greetingFunction",
26-
"arguments": {
27-
"name": "${ .person.name }"
28-
}
29-
},
30-
"actionDataFilter": {
31-
"results": "${ .greeting }"
32-
}
33-
}
34-
],
35-
"end": True
36-
}
37-
],
38-
functions=[
39-
{
40-
"name": "greetingFunction",
41-
"operation": "file://myapis/greetingapis.json#greeting"
15+
workflow = Workflow(
16+
id_="greeting",
17+
name="Greeting Workflow",
18+
description="Greet Someone",
19+
version='1.0',
20+
specVersion='0.8',
21+
start="Greet",
22+
states=[
23+
OperationState(
24+
name="Greet",
25+
type="operation",
26+
actions=[
27+
Action(
28+
functionRef=FunctionRef(
29+
refName="greetingFunction",
30+
arguments={
31+
"name": "${ .person.name }"
4232
}
43-
]
33+
),
34+
actionDataFilter=ActionDataFilter(
35+
results="${ .greeting }"
4436
)
37+
)
38+
],
39+
end=True
40+
)
41+
],
42+
functions=[
43+
Function(name="greetingFunction",
44+
operation="file://myapis/greetingapis.json#greeting")
45+
]
46+
)
4547

4648
def test_workflow_to_json(self):
4749
expected = """{
@@ -105,9 +107,37 @@ def test_workflow_to_yaml(self):
105107
"""
106108
self.assertEqual(expected, self.workflow.to_yaml())
107109

110+
def test_programmatically_create_workflow(self):
111+
112+
self.assertEqual("greeting", self.workflow.id)
113+
self.assertEqual("operation", self.workflow.states[0].type)
114+
self.assertTrue(isinstance(self.workflow.states[0], OperationState))
115+
self.assertEqual(True, self.workflow.states[0].end)
116+
self.assertTrue(isinstance(self.workflow.states[0].actions[0], Action))
117+
self.assertTrue(isinstance(self.workflow.states[0].actions[0].functionRef, FunctionRef))
118+
self.assertTrue(isinstance(self.workflow.functions[0], Function))
119+
108120
def test_workflow_from_source_json(self):
109-
wf_file = os.path.join(os.path.dirname(__file__), 'test_workflow.json')
110-
self.assert_test_workflow_file(wf_file)
121+
examples_dir = os.path.join(os.path.dirname(__file__), '../../examples')
122+
examples = listdir(examples_dir)
123+
self.assertEqual(len(examples), 10)
124+
125+
for example in examples:
126+
with self.subTest(f"test_{example}"):
127+
with open(examples_dir + "/" + example, "r") as swf_file:
128+
workflow = Workflow.from_source(swf_file)
129+
self.assertTrue(isinstance(workflow, Workflow))
130+
131+
def test_instance_workflow_class(self):
132+
examples_dir = os.path.join(os.path.dirname(__file__), '../../examples')
133+
examples = listdir(examples_dir)
134+
self.assertEqual(len(examples), 10)
135+
136+
for example in examples:
137+
with self.subTest(f"test_{example}"):
138+
with open(examples_dir + "/" + example, "r") as swf_file:
139+
workflow = Workflow(**json.load(swf_file))
140+
self.assertTrue(isinstance(workflow, Workflow))
111141

112142
def test_workflow_from_source_yaml(self):
113143
wf_file = os.path.join(os.path.dirname(__file__), 'test_workflow.yaml')
@@ -125,4 +155,3 @@ def assert_test_workflow_file(self, wf_file):
125155
self.assertTrue(isinstance(workflow.states[0].actions[0], Action))
126156
self.assertTrue(isinstance(workflow.states[0].actions[0].functionRef, FunctionRef))
127157
self.assertTrue(isinstance(workflow.functions[0], Function))
128-

tests/serverlessworkflow/sdk/test_workflow_validator.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ class TestWorkflowValidator(unittest.TestCase):
1414
def test_validate_examples(self):
1515
examples_dir = os.path.join(os.path.dirname(__file__), '../../examples')
1616
examples = listdir(examples_dir)
17-
self.assertEqual(len(examples), 9)
17+
self.assertEqual(len(examples), 10)
1818

1919
for example in examples:
2020
with self.subTest(f"test_{example}"):
21-
2221
with open(examples_dir + "/" + example, "r") as swf_file:
2322
swf_file_content = json.load(swf_file)
2423
workflow = Workflow(**swf_file_content)

0 commit comments

Comments
 (0)