Skip to content

Commit

Permalink
simplify incoming wfb and correct outgoing object syntax (#261)
Browse files Browse the repository at this point in the history
* cleanup/convert incoming wfb object

* simplify incoming wfb json and compile

---------

Co-authored-by: Vasu Jaganath <vasu.jaganath@axleinfo.com>
  • Loading branch information
vjaganat90 and Vasu Jaganath authored Aug 19, 2024
1 parent beefe05 commit d9c6af7
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 2 deletions.
33 changes: 31 additions & 2 deletions src/sophios/api/http/restapi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pathlib import Path
import argparse
import copy
import yaml


import uvicorn
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
Expand All @@ -12,8 +14,8 @@
from sophios.utils_yaml import wic_loader
from sophios import utils_cwl
from sophios.cli import get_args
from sophios.wic_types import CompilerInfo, Json, Tool, Tools, StepId, YamlTree, Cwl
# from sophios.api.utils import converter
from sophios.wic_types import CompilerInfo, Json, Tool, Tools, StepId, YamlTree, Cwl, NodeData
from sophios.api.utils import converter
# from .auth.auth import authenticate


Expand Down Expand Up @@ -105,6 +107,8 @@ async def compile_wf(request: Request) -> Json:
print('---------- Compile Workflow! ---------')
# ========= PROCESS REQUEST OBJECT ==========
req: Json = await request.json()
# clean up and convert the incoming object
req = converter.raw_wfb_to_lean_wfb(req)
wkflw_name = "generic_workflow"
args = get_args(wkflw_name)

Expand Down Expand Up @@ -137,7 +141,32 @@ async def compile_wf(request: Request) -> Json:
print('---------- Run Workflow locally! ---------')
retval = run_workflow(compiler_info, args)

# ======== OUTPUT PROCESSING ================
# ========= PROCESS COMPILED OBJECT =========
sub_node_data: NodeData = compiler_info.rose.data
yaml_stem = sub_node_data.name
cwl_tree = sub_node_data.compiled_cwl
yaml_inputs = sub_node_data.workflow_inputs_file
cwl_tree_no_dd = remove_dot_dollar(cwl_tree)
yaml_inputs_no_dd = remove_dot_dollar(yaml_inputs)

# Convert the compiled yaml file to json for labshare Compute.
cwl_tree_run = copy.deepcopy(cwl_tree_no_dd)
# for step_key in cwl_tree['steps']:
# step_name_i = step_key
# step_name_i = step_name_i.replace('.yml', '_yml') # Due to calling remove_dot_dollar above
# # step_name = '__'.join(step_key.split('__')[3:]) # Remove prefix
# # Get step CWL from templates
# # run_val = next((tval['cwlScript']
# # for _, tval in ict_plugins.items() if step_name == tval['name']), None)
# # cwl_tree_run['steps'][step_name_i]['run'] = run_val

compute_workflow: Json = {}
compute_workflow = {
"name": yaml_stem,
"cwlJobInputs": yaml_inputs_no_dd,
**cwl_tree_run
}
compute_workflow["retval"] = str(retval)
return compute_workflow

Expand Down
Empty file.
232 changes: 232 additions & 0 deletions src/sophios/api/utils/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import copy
from typing import Any, Dict, List

from jsonschema import Draft202012Validator

# from sophios import cli
from sophios.wic_types import Json

SCHEMA: Json = {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Link": {
"properties": {
"id": {
"type": "number"
},
"inletIndex": {
"type": "number"
},
"outletIndex": {
"type": "number"
},
"sourceId": {
"type": "number"
},
"targetId": {
"type": "number"
},
"x1": {
"type": "number"
},
"x2": {
"type": "number"
},
"y1": {
"type": "number"
},
"y2": {
"type": "number"
}
},
"type": "object",
"required": ["id", "sourceId", "targetId"]
},
"NodeSettings": {
"properties": {
"inputs": {
"additionalProperties": {
"$ref": "#/definitions/T"
},
"type": "object"
},
"outputs": {
"additionalProperties": {
"$ref": "#/definitions/T"
},
"type": "object"
}
},
"type": "object"
},
"NodeX": {
"properties": {
"expanded": {
"type": "boolean"
},
"height": {
"type": "number"
},
"id": {
"type": "number"
},
"internal": {
"type": "boolean"
},
"name": {
"type": "string"
},
"pluginId": {
"type": "string"
},
"settings": {
"$ref": "#/definitions/NodeSettings"
},
"width": {
"type": "number"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"z": {
"type": "number"
},
},
"type": "object",
"required": ["id", "name", "pluginId", "settings", "internal"]
},
"T": {
"type": "object"
}
},
"properties": {
"links": {
"items": {
"$ref": "#/definitions/Link"
},
"type": "array"
},
"nodes": {
"items": {
"$ref": "#/definitions/NodeX"
},
"type": "array"
},
"selection": {
"items": {
"type": "number"
},
"type": "array"
}
},
"type": "object",
"required": ["links", "nodes"]
}


def del_irrelevant_keys(ldict: List[Dict[Any, Any]], relevant_keys: List[Any]) -> None:
"""deletes irrelevant keys from every dict in the list of dicts"""
for elem in ldict:
ekeys = list(elem.keys())
for ek in ekeys:
if ek not in relevant_keys:
# delete the key if it exists
elem.pop(ek, None)


def validate_schema_and_object(schema: Json, jobj: Json) -> None:
"""Validate schema object"""
Draft202012Validator.check_schema(schema)
df2012 = Draft202012Validator(schema)
df2012.is_valid(jobj)


def raw_wfb_to_lean_wfb(inp: Json) -> Json:
"""drop all the unnecessary info from incoming wfb object"""
inp_restrict = copy.deepcopy(inp)
keys = list(inp.keys())
# To avoid deserialization
# required attributes from schema
prop_req = SCHEMA['required']
nodes_req = SCHEMA['definitions']['NodeX']['required']
links_req = SCHEMA['definitions']['Link']['required']
do_not_rem_nodes_prop = ['cwlScript']
do_not_rem_links_prop: list = []

for k in keys:
if k not in prop_req:
del inp_restrict[k]
elif k == 'links':
lems = inp_restrict[k]
rel_links_keys = links_req + do_not_rem_links_prop
del_irrelevant_keys(lems, rel_links_keys)
elif k == 'nodes':
nems = inp_restrict[k]
rel_nodes_keys = nodes_req + do_not_rem_nodes_prop
del_irrelevant_keys(nems, rel_nodes_keys)
else:
pass

validate_schema_and_object(SCHEMA, inp_restrict)
return inp_restrict


# def wfb_to_wic(request: Json) -> Json:
# """Convert the json object from http request object to a json object that can be used as input to wic compiler.

# Args:
# request (Json): json object from http request

# Returns:
# converted_json (Json): json object that can be used as input to wic compiler"""

# converted_steps: list[Any] = []

# for step in request['steps']:
# step_template = step['template']
# arguments = step['arguments']
# # Get the template name from the step template
# template_name = next((tval['name']
# for tname, tval in request['templates'].items() if step_template == tname), None)
# # template_name = None
# # for tname, tval in request['templates'].items():
# # if tname == step_template and tval['name']:
# # template_name = tval['name']
# # break
# # elif tname == step_template and not tval['name']:
# # break
# # else:
# # pass

# converted_step: Json = {}
# if template_name:
# converted_step[template_name] = {
# "in": {}
# }

# for key, value in arguments.items():
# # be aware of usage of periods in the values as delimiters, this may cause an issue when storing in MongoDB
# if value.startswith("steps."):
# parts = value.split('.')
# src_step_idx = int(parts[1][len("step"):])
# src_output_key = parts[3]

# # Get the src template name from the src step name
# src_template = next((step.get("template")
# for step in request['steps'] if step.get("name") == parts[1]), None)
# src_template_name = next((stval['name'] for stname, stval in request['templates'].items()
# if src_template == stname), None)
# src_converted_step = next((step.get(src_template_name)
# for step in converted_steps if step.get(src_template_name)), None)
# if src_converted_step:
# src_converted_step["in"][src_output_key] = f"&{src_template_name}.{src_output_key}.{src_step_idx}"
# converted_step[template_name]["in"][key] = f"*{src_template_name}.{src_output_key}.{src_step_idx}"
# else:
# converted_step[template_name]["in"][key] = value
# converted_steps.append(converted_step)

# converted_json: Json = {"steps": converted_steps}
# return converted_json

0 comments on commit d9c6af7

Please sign in to comment.