Skip to content

Commit e4fa3de

Browse files
committed
add pydantic model for workflow
1 parent 3bbda19 commit e4fa3de

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

example_workflows/nested/jobflow.ipynb

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,64 @@
22
"cells": [
33
{
44
"cell_type": "code",
5-
"execution_count": null,
65
"id": "initial_id",
76
"metadata": {
8-
"collapsed": true
7+
"collapsed": true,
8+
"ExecuteTime": {
9+
"end_time": "2025-10-23T13:08:02.617292Z",
10+
"start_time": "2025-10-23T13:08:02.205177Z"
11+
}
12+
},
13+
"source": "from python_workflow_definition.jobflow import load_workflow_json",
14+
"outputs": [
15+
{
16+
"name": "stderr",
17+
"output_type": "stream",
18+
"text": [
19+
"/home/jgeorge/miniconda3/envs/2025_PWD_Extension_nested_flows/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
20+
" from .autonotebook import tqdm as notebook_tqdm\n"
21+
]
22+
}
23+
],
24+
"execution_count": 1
25+
},
26+
{
27+
"metadata": {
28+
"ExecuteTime": {
29+
"end_time": "2025-10-23T13:08:03.884311Z",
30+
"start_time": "2025-10-23T13:08:03.635773Z"
31+
}
932
},
33+
"cell_type": "code",
34+
"source": "load_workflow_json(\"main.pwd.json\")",
35+
"id": "7e1707c47e14fbcc",
36+
"outputs": [
37+
{
38+
"ename": "ModuleNotFoundError",
39+
"evalue": "No module named 'prod_div'",
40+
"output_type": "error",
41+
"traceback": [
42+
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
43+
"\u001B[31mModuleNotFoundError\u001B[39m Traceback (most recent call last)",
44+
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[2]\u001B[39m\u001B[32m, line 1\u001B[39m\n\u001B[32m----> \u001B[39m\u001B[32m1\u001B[39m \u001B[43mload_workflow_json\u001B[49m\u001B[43m(\u001B[49m\u001B[33;43m\"\u001B[39;49m\u001B[33;43mmain.pwd.json\u001B[39;49m\u001B[33;43m\"\u001B[39;49m\u001B[43m)\u001B[49m\n",
45+
"\u001B[36mFile \u001B[39m\u001B[32m/smb/jgeorge/hpc-user/PycharmProjects/2025_PWD_Extension_nested_flows/python-workflow-definition/src/python_workflow_definition/jobflow.py:301\u001B[39m, in \u001B[36mload_workflow_json\u001B[39m\u001B[34m(file_name)\u001B[39m\n\u001B[32m 299\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(v, \u001B[38;5;28mstr\u001B[39m) \u001B[38;5;129;01mand\u001B[39;00m \u001B[33m\"\u001B[39m\u001B[33m.\u001B[39m\u001B[33m\"\u001B[39m \u001B[38;5;129;01min\u001B[39;00m v:\n\u001B[32m 300\u001B[39m p, m = v.rsplit(\u001B[33m\"\u001B[39m\u001B[33m.\u001B[39m\u001B[33m\"\u001B[39m, \u001B[32m1\u001B[39m)\n\u001B[32m--> \u001B[39m\u001B[32m301\u001B[39m mod = \u001B[43mimport_module\u001B[49m\u001B[43m(\u001B[49m\u001B[43mp\u001B[49m\u001B[43m)\u001B[49m\n\u001B[32m 302\u001B[39m nodes_new_dict[\u001B[38;5;28mint\u001B[39m(k)] = \u001B[38;5;28mgetattr\u001B[39m(mod, m)\n\u001B[32m 303\u001B[39m \u001B[38;5;28;01melse\u001B[39;00m:\n",
46+
"\u001B[36mFile \u001B[39m\u001B[32m~/miniconda3/envs/2025_PWD_Extension_nested_flows/lib/python3.11/importlib/__init__.py:126\u001B[39m, in \u001B[36mimport_module\u001B[39m\u001B[34m(name, package)\u001B[39m\n\u001B[32m 124\u001B[39m \u001B[38;5;28;01mbreak\u001B[39;00m\n\u001B[32m 125\u001B[39m level += \u001B[32m1\u001B[39m\n\u001B[32m--> \u001B[39m\u001B[32m126\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_bootstrap\u001B[49m\u001B[43m.\u001B[49m\u001B[43m_gcd_import\u001B[49m\u001B[43m(\u001B[49m\u001B[43mname\u001B[49m\u001B[43m[\u001B[49m\u001B[43mlevel\u001B[49m\u001B[43m:\u001B[49m\u001B[43m]\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mpackage\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mlevel\u001B[49m\u001B[43m)\u001B[49m\n",
47+
"\u001B[36mFile \u001B[39m\u001B[32m<frozen importlib._bootstrap>:1204\u001B[39m, in \u001B[36m_gcd_import\u001B[39m\u001B[34m(name, package, level)\u001B[39m\n",
48+
"\u001B[36mFile \u001B[39m\u001B[32m<frozen importlib._bootstrap>:1176\u001B[39m, in \u001B[36m_find_and_load\u001B[39m\u001B[34m(name, import_)\u001B[39m\n",
49+
"\u001B[36mFile \u001B[39m\u001B[32m<frozen importlib._bootstrap>:1140\u001B[39m, in \u001B[36m_find_and_load_unlocked\u001B[39m\u001B[34m(name, import_)\u001B[39m\n",
50+
"\u001B[31mModuleNotFoundError\u001B[39m: No module named 'prod_div'"
51+
]
52+
}
53+
],
54+
"execution_count": 2
55+
},
56+
{
57+
"metadata": {},
58+
"cell_type": "code",
1059
"outputs": [],
11-
"source": "from python_workflow_definition.aiida import load_workflow_json"
60+
"execution_count": null,
61+
"source": "",
62+
"id": "ae57f67080d3a26c"
1263
}
1364
],
1465
"metadata": {

src/python_workflow_definition/models.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,34 @@ def check_value_format(cls, v: str):
6363
raise ValueError(msg)
6464
return v
6565

66+
class PythonWorkflowDefinitionWorklowNode(PythonWorkflowDefinitionBaseNode):
67+
"""
68+
Model for function execution nodes.
69+
The 'name' attribute is computed automatically from 'value'.
70+
"""
71+
72+
type: Literal["workflow"]
73+
value: str # Expected format: 'module.function'
74+
75+
@field_validator("value")
76+
@classmethod
77+
def check_value_format(cls, v: str):
78+
if not v or "." not in v or v.startswith(".") or v.endswith("."):
79+
msg = (
80+
"FunctionNode 'value' must be a non-empty string ",
81+
"in 'module.function' format with at least one period.",
82+
)
83+
raise ValueError(msg)
84+
return v
85+
6686

6787
# Discriminated Union for Nodes
6888
PythonWorkflowDefinitionNode = Annotated[
6989
Union[
7090
PythonWorkflowDefinitionInputNode,
7191
PythonWorkflowDefinitionOutputNode,
7292
PythonWorkflowDefinitionFunctionNode,
93+
PythonWorkflowDefinitionWorklowNode
7394
],
7495
Field(discriminator="type"),
7596
]

0 commit comments

Comments
 (0)