Skip to content

Commit

Permalink
add required files for rest api (core)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjaganat90 authored and Vasu Jaganath committed Jun 26, 2024
1 parent 0ef6e46 commit 398dc66
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ dependencies = [
# See https://github.com/common-workflow-language/cwl-utils/releases/
"typeguard",
"pydantic>=2.6",
"pydantic-settings",
"docker",
# FYI also need uidmap to run podman rootless
"podman",
# We are using the official release for these packages for now
"toil[cwl]",
"fastapi",
"python-jose",
"uvicorn"
]

[project.readme]
Expand Down
Empty file.
175 changes: 175 additions & 0 deletions src/sophios/api/http/restapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from pathlib import Path
import argparse
import yaml

import uvicorn
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware

from sophios import __version__, compiler
from sophios import run_local, input_output
from sophios.utils_graphs import get_graph_reps
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 wic.api.utils import converter
# from .auth.auth import authenticate
# from utils import converter


# helper functions


def remove_dot_dollar(tree: Cwl) -> Cwl:
"""Removes . and $ from dictionary keys, e.g. $namespaces and $schemas. Otherwise, you will get
{'error': {'statusCode': 500, 'message': 'Internal Server Error'}}
This is due to MongoDB:
See https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names
Args:
tree (Cwl): A Cwl document
Returns:
Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
"""
tree_str = str(yaml.dump(tree, sort_keys=False, line_break='\n', indent=2))
tree_str_no_dd = tree_str.replace('$namespaces', 'namespaces').replace(
'$schemas', 'schemas').replace('.wic', '_wic')
tree_no_dd: Cwl = yaml.load(tree_str_no_dd, Loader=wic_loader()) # This effectively copies tree
return tree_no_dd


def get_yaml_tree(req: Json) -> Json:
"""
Get the Sophios yaml tree from incoming JSON
Args:
req (JSON): A raw JSON content of incoming JSON object
Returns:
Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
"""
wkflw_name = "generic_workflow"
# args = converter.get_args(wkflw_name)
# yaml_tree_json: Json = converter.wfb_to_wic(req)
yaml_tree_json: Json = {}
return yaml_tree_json


def run_workflow(compiler_info: CompilerInfo, args: argparse.Namespace) -> int:
"""
Get the Sophios yaml tree from incoming JSON
Args:
req (JSON): A raw JSON content of incoming JSON object
Returns:
Cwl: A Cwl document with . and $ removed from $namespaces and $schemas
"""
# ========= WRITE OUT =======================
input_output.write_to_disk(compiler_info.rose, Path('autogenerated/'), relative_run_path=True)
# ======== TEST RUN =========================
retval = run_local.run_local(args, compiler_info.rose, args.cachedir, 'cwltool', False)
return retval


app = FastAPI()

origins = ["*"]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.get("/", status_code=status.HTTP_200_OK)
# @authenticate
async def root(request: Request) -> Json:
"""The api has 1 route: compile
Returns:
Dict[str, str]: {"message": "The api has 1 route: compile"}
"""
return {"message": "The api has 1 route: compile"}


@app.post("/compile")
# @authenticate
async def compile_wf(request: Request) -> Json:
"""The compile route compiles the json object from http request object built elsewhere
Args:
request (Request): request object built elsewhere
Returns:
compute_workflow (JSON): workflow json object ready to submit to compute
"""
print('---------- Compile Workflow! ---------')
# ========= PROCESS REQUEST OBJECT ==========
req: Json = await request.json()
wkflw_name = "generic_workflow"
args = get_args(wkflw_name)
# yaml_tree_json: Json = converter.wfb_to_wic(req)
# yaml_tree_json = get_yaml_tree(req)

workflow_temp = req["templates"]["ict"]["cwlScript"] # Assume dict form
workflow_can = utils_cwl.desugar_into_canonical_normal_form(workflow_temp)

# ========= BUILD WIC COMPILE INPUT =========
# ict_plugins = req["templates"]
tools_cwl: Tools = {StepId(content["id"], "global"):
Tool(".", content["run"]) for content in workflow_can["steps"]}
# run tag will have the actual CommandLineTool
wic_obj = {'wic': workflow_can.get('wic', {})}
plugin_ns = wic_obj['wic'].get('namespace', 'global')

graph = get_graph_reps(wkflw_name)
yaml_tree: YamlTree = YamlTree(StepId(wkflw_name, plugin_ns), workflow_can)

# ========= COMPILE WORKFLOW ================
compiler_info: CompilerInfo = compiler.compile_workflow(yaml_tree, args, [], [graph], {}, {}, {}, {},
tools_cwl, True, relative_run_path=True, testing=False)

# =========== OPTIONAL RUN ==============
print('---------- Run Workflow locally! ---------')
retval = run_workflow(compiler_info, args)

compute_workflow: Json = {}
compute_workflow.get('retval', str(retval))
return compute_workflow


if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=3000)


# # ========= 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

# # ======== OUTPUT PROCESSING ================
# 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

# TODO: set name and driver in workflow builder ui
# compute_workflow: Json = {}
# compute_workflow = {
# "name": yaml_stem,
# "driver": "argo",
# # "driver": "cwltool",
# "cwlJobInputs": yaml_inputs_no_dd,
# **cwl_tree_run
# }

0 comments on commit 398dc66

Please sign in to comment.