diff --git a/pyproject.toml b/pyproject.toml index 4990bb38..d0aa2e77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,7 +141,7 @@ namespaces = true [tool.setuptools.package-data] "*" = ["*.txt"] -"sophios" = ["*.json"] +"sophios" = ["**/*.json"] [tool.aliases] test = "pytest --workers 8" diff --git a/src/sophios/api/utils/converter.py b/src/sophios/api/utils/converter.py index 8a1e5ba6..8cbccc8c 100644 --- a/src/sophios/api/utils/converter.py +++ b/src/sophios/api/utils/converter.py @@ -1,130 +1,17 @@ import copy +from pathlib import Path from typing import Any, Dict, List +import json import yaml from jsonschema import Draft202012Validator from sophios.utils_yaml import wic_loader from sophios.wic_types import Json, Cwl -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"] -} +SCHEMA_FILE = Path(__file__).parent / "input_object_schema.json" +SCHEMA: Json = {} +with open(SCHEMA_FILE, 'r', encoding='utf-8') as f: + SCHEMA = json.load(f) def del_irrelevant_keys(ldict: List[Dict[Any, Any]], relevant_keys: List[Any]) -> None: @@ -137,20 +24,36 @@ def del_irrelevant_keys(ldict: List[Dict[Any, Any]], relevant_keys: List[Any]) - elem.pop(ek, None) -def validate_schema_and_object(schema: Json, jobj: Json) -> None: +def validate_schema_and_object(schema: Json, jobj: Json) -> bool: """Validate schema object""" Draft202012Validator.check_schema(schema) df2012 = Draft202012Validator(schema) - df2012.is_valid(jobj) + return df2012.is_valid(jobj) + + +def extract_state(inp: Json) -> Json: + """Extract only the state information from the incoming wfb object. + It includes converting "ICT" nodes to "CLT" using "plugins" tag of the object. + """ + inp_restrict: Json = {} + if not inp.get('plugins'): + inp_restrict = copy.deepcopy(inp['state']) + else: + inp_inter = copy.deepcopy(inp) + # Here goes the ICT to CLT extraction logic + inp_restrict = inp_inter['state'] + return inp_restrict 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()) + """Drop all the unnecessary info from incoming wfb object""" + if validate_schema_and_object(SCHEMA, inp): + print('incoming object is valid against input object schema') + inp_restrict = extract_state(inp) + keys = list(inp_restrict.keys()) # To avoid deserialization # required attributes from schema - prop_req = SCHEMA['required'] + prop_req = SCHEMA['definitions']['State']['required'] nodes_req = SCHEMA['definitions']['NodeX']['required'] links_req = SCHEMA['definitions']['Link']['required'] do_not_rem_nodes_prop = ['cwlScript', 'run'] @@ -170,12 +73,11 @@ def raw_wfb_to_lean_wfb(inp: Json) -> Json: else: pass - validate_schema_and_object(SCHEMA, inp_restrict) return inp_restrict def wfb_to_wic(inp: Json) -> Cwl: - """convert lean wfb json to compliant wic""" + """Convert lean wfb json to compliant wic""" # non-schema preserving changes inp_restrict = copy.deepcopy(inp) diff --git a/src/sophios/api/utils/input_object_schema.json b/src/sophios/api/utils/input_object_schema.json new file mode 100644 index 00000000..bd3b0fc8 --- /dev/null +++ b/src/sophios/api/utils/input_object_schema.json @@ -0,0 +1,377 @@ +{ + "$ref": "#/definitions/WicPayload", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Dictionary": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Dictionary": { + "additionalProperties": {}, + "type": "object" + }, + "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" + } + }, + "required": [ + "id", + "sourceId", + "targetId" + ], + "type": "object" + }, + "NodeInput": { + "properties": { + "description": { + "type": "string" + }, + "format": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "options": { + "$ref": "#/definitions/NodeInputOptions" + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "NodeInputOptions": { + "properties": { + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "values" + ], + "type": "object" + }, + "NodeInputUI": { + "properties": { + "condition": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fields": { + "items": { + "type": "string" + }, + "type": "array" + }, + "format": { + "items": { + "type": "string" + }, + "type": "array" + }, + "key": { + "type": "string" + }, + "placeholder": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "key", + "title", + "description", + "type" + ], + "type": "object" + }, + "NodeOutput": { + "properties": { + "description": { + "type": "string" + }, + "format": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "NodeSettings": { + "properties": { + "inputs": { + "$ref": "#/definitions/Dictionary%3Cunknown%3E" + }, + "outputs": { + "$ref": "#/definitions/Dictionary%3Cstring%3E" + } + }, + "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" + } + }, + "required": [ + "id", + "name", + "pluginId", + "settings", + "internal" + ], + "type": "object" + }, + "PluginX": { + "properties": { + "author": { + "type": "string" + }, + "baseCommand": { + "items": { + "type": "string" + }, + "type": "array" + }, + "contact": { + "type": "string" + }, + "container": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "description": { + "type": "string" + }, + "documentation": { + "type": "string" + }, + "entrypoint": { + "type": "string" + }, + "hardware": { + "type": "object" + }, + "id": { + "type": "string" + }, + "inputs": { + "items": { + "$ref": "#/definitions/NodeInput" + }, + "type": "array" + }, + "institution": { + "type": "string" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "outputs": { + "items": { + "$ref": "#/definitions/NodeOutput" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "pid": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "specVersion": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "type": "string" + }, + "ui": { + "items": { + "$ref": "#/definitions/NodeInputUI" + }, + "type": "array" + }, + "updatedAt": { + "type": "string" + }, + "updatedBy": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "version", + "title", + "description", + "inputs", + "outputs", + "ui" + ], + "type": "object" + }, + "State": { + "properties": { + "links": { + "items": { + "$ref": "#/definitions/Link" + }, + "type": "array" + }, + "nodes": { + "items": { + "$ref": "#/definitions/NodeX" + }, + "type": "array" + }, + "selection": { + "items": { + "type": "number" + }, + "type": "array" + } + }, + "required": [ + "links", + "nodes" + ], + "type": "object" + }, + "WicPayload": { + "properties": { + "plugins": { + "items": { + "$ref": "#/definitions/PluginX" + }, + "type": "array" + }, + "state": { + "$ref": "#/definitions/State" + } + }, + "required": [ + "state", + "plugins" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/tests/rest_wfb_objects/multi_node.json b/tests/rest_wfb_objects/multi_node.json index ffe42014..27b9894a 100644 --- a/tests/rest_wfb_objects/multi_node.json +++ b/tests/rest_wfb_objects/multi_node.json @@ -1,76 +1,79 @@ { - "nodes": [ - { - "id": 7, - "x": 462, - "y": 206, - "z": 2, - "name": "touch", - "pluginId": "touch", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "filename": "empty.txt" + "state": { + "nodes": [ + { + "id": 7, + "x": 462, + "y": 206, + "z": 2, + "name": "touch", + "pluginId": "touch", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "filename": "empty.txt" + }, + "outputs": { + "file": "file_touch" + } }, - "outputs": { - "file": "file_touch" - } + "internal": false }, - "internal": false - }, - { - "id": 18, - "x": 155, - "y": 195, - "z": 1, - "name": "append", - "pluginId": "append", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "str": "Hello", - "file": "file_touch" + { + "id": 18, + "x": 155, + "y": 195, + "z": 1, + "name": "append", + "pluginId": "append", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "str": "Hello", + "file": "file_touch" + }, + "outputs": { + "file": "file_append1" + } }, - "outputs": { - "file": "file_append1" - } + "internal": false }, - "internal": false - }, - { - "id": 9, - "x": 790.3254637299812, - "y": 449.8103498684344, - "z": 5, - "name": "append", - "pluginId": "append", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "str": "World!", - "file": "file_append1" + { + "id": 9, + "x": 790.3254637299812, + "y": 449.8103498684344, + "z": 5, + "name": "append", + "pluginId": "append", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "str": "World!", + "file": "file_append1" + }, + "outputs": { + "file": "file_append2" + } }, - "outputs": { - "file": "file_append2" - } + "internal": false + } + ], + "links": [ + { + "sourceId": 7, + "targetId": 18, + "id": 1 }, - "internal": false - } - ], - "links": [ - { - "sourceId": 7, - "targetId": 18, - "id": 1 - }, - { - "sourceId": 18, - "targetId": 9, - "id": 5 - } - ], - "selection": [] + { + "sourceId": 18, + "targetId": 9, + "id": 5 + } + ], + "selection": [] + }, + "plugins": [] } \ No newline at end of file diff --git a/tests/rest_wfb_objects/multi_node_inline_cwl.json b/tests/rest_wfb_objects/multi_node_inline_cwl.json index c972ee36..410a1d48 100644 --- a/tests/rest_wfb_objects/multi_node_inline_cwl.json +++ b/tests/rest_wfb_objects/multi_node_inline_cwl.json @@ -1,181 +1,184 @@ { - "nodes": [ - { - "id": 7, - "x": 462, - "y": 206, - "z": 2, - "name": "touch", - "pluginId": "touch", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "filename": "empty.txt" - }, - "outputs": { - "file": "file_touch" - } - }, - "run": { - "cwlVersion": "v1.0", - "class": "CommandLineTool", - "requirements": { - "DockerRequirement": { - "dockerPull": "docker.io/bash:4.4" + "state": { + "nodes": [ + { + "id": 7, + "x": 462, + "y": 206, + "z": 2, + "name": "touch", + "pluginId": "touch", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "filename": "empty.txt" }, - "InlineJavascriptRequirement": {} - }, - "baseCommand": "touch", - "inputs": { - "filename": { - "type": "string", - "inputBinding": { - "position": 1 - } + "outputs": { + "file": "file_touch" } }, - "outputs": { - "file": { - "type": "File", - "outputBinding": { - "glob": "$(inputs.filename)" + "run": { + "cwlVersion": "v1.0", + "class": "CommandLineTool", + "requirements": { + "DockerRequirement": { + "dockerPull": "docker.io/bash:4.4" + }, + "InlineJavascriptRequirement": {} + }, + "baseCommand": "touch", + "inputs": { + "filename": { + "type": "string", + "inputBinding": { + "position": 1 + } + } + }, + "outputs": { + "file": { + "type": "File", + "outputBinding": { + "glob": "$(inputs.filename)" + } } } - } - }, - "internal": false - }, - { - "id": 18, - "x": 155, - "y": 195, - "z": 1, - "name": "append", - "pluginId": "append", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "str": "Hello", - "file": "file_touch" }, - "outputs": { - "file": "file_append1" - } + "internal": false }, - "run": { - "class": "CommandLineTool", - "cwlVersion": "v1.0", - "requirements": { - "ShellCommandRequirement": {}, - "InlineJavascriptRequirement": {}, - "InitialWorkDirRequirement": { - "listing": [ - "$(inputs.file)" - ] + { + "id": 18, + "x": 155, + "y": 195, + "z": 1, + "name": "append", + "pluginId": "append", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "str": "Hello", + "file": "file_touch" + }, + "outputs": { + "file": "file_append1" } }, - "inputs": { - "str": { - "type": "string", - "inputBinding": { - "shellQuote": false, - "position": 1, - "prefix": "echo" + "run": { + "class": "CommandLineTool", + "cwlVersion": "v1.0", + "requirements": { + "ShellCommandRequirement": {}, + "InlineJavascriptRequirement": {}, + "InitialWorkDirRequirement": { + "listing": [ + "$(inputs.file)" + ] } }, - "file": { - "type": "File", - "inputBinding": { - "shellQuote": false, - "position": 2, - "prefix": ">>" + "inputs": { + "str": { + "type": "string", + "inputBinding": { + "shellQuote": false, + "position": 1, + "prefix": "echo" + } + }, + "file": { + "type": "File", + "inputBinding": { + "shellQuote": false, + "position": 2, + "prefix": ">>" + } } - } - }, - "outputs": { - "file": { - "type": "File", - "outputBinding": { - "glob": "$(inputs.file.basename)" + }, + "outputs": { + "file": { + "type": "File", + "outputBinding": { + "glob": "$(inputs.file.basename)" + } } } - } - }, - "internal": false - }, - { - "id": 9, - "x": 790.3254637299812, - "y": 449.8103498684344, - "z": 5, - "name": "append", - "pluginId": "append", - "height": 50, - "width": 250, - "settings": { - "inputs": { - "str": "World!", - "file": "file_append1" }, - "outputs": { - "file": "file_append2" - } + "internal": false }, - "run": { - "class": "CommandLineTool", - "cwlVersion": "v1.0", - "requirements": { - "ShellCommandRequirement": {}, - "InlineJavascriptRequirement": {}, - "InitialWorkDirRequirement": { - "listing": [ - "$(inputs.file)" - ] + { + "id": 9, + "x": 790.3254637299812, + "y": 449.8103498684344, + "z": 5, + "name": "append", + "pluginId": "append", + "height": 50, + "width": 250, + "settings": { + "inputs": { + "str": "World!", + "file": "file_append1" + }, + "outputs": { + "file": "file_append2" } }, - "inputs": { - "str": { - "type": "string", - "inputBinding": { - "shellQuote": false, - "position": 1, - "prefix": "echo" + "run": { + "class": "CommandLineTool", + "cwlVersion": "v1.0", + "requirements": { + "ShellCommandRequirement": {}, + "InlineJavascriptRequirement": {}, + "InitialWorkDirRequirement": { + "listing": [ + "$(inputs.file)" + ] } }, - "file": { - "type": "File", - "inputBinding": { - "shellQuote": false, - "position": 2, - "prefix": ">>" + "inputs": { + "str": { + "type": "string", + "inputBinding": { + "shellQuote": false, + "position": 1, + "prefix": "echo" + } + }, + "file": { + "type": "File", + "inputBinding": { + "shellQuote": false, + "position": 2, + "prefix": ">>" + } } - } - }, - "outputs": { - "file": { - "type": "File", - "outputBinding": { - "glob": "$(inputs.file.basename)" + }, + "outputs": { + "file": { + "type": "File", + "outputBinding": { + "glob": "$(inputs.file.basename)" + } } } - } + }, + "internal": false + } + ], + "links": [ + { + "sourceId": 7, + "targetId": 18, + "id": 1 }, - "internal": false - } - ], - "links": [ - { - "sourceId": 7, - "targetId": 18, - "id": 1 - }, - { - "sourceId": 18, - "targetId": 9, - "id": 5 - } - ], - "selection": [] + { + "sourceId": 18, + "targetId": 9, + "id": 5 + } + ], + "selection": [] + }, + "plugins": [] } \ No newline at end of file diff --git a/tests/rest_wfb_objects/single_node.json b/tests/rest_wfb_objects/single_node.json index 099bf9ce..196fa80c 100644 --- a/tests/rest_wfb_objects/single_node.json +++ b/tests/rest_wfb_objects/single_node.json @@ -1,40 +1,43 @@ { - "nodes": [ - { - "id": 1, - "name": "PythonHelloWorld", - "pluginId": "", - "cwlScript": { - "steps": { - "one": { - "run": { - "baseCommand": [ - "python", - "-c", - "print('hello world'); print('sqr of 7 : %.2f' % 7**2)" - ], - "class": "CommandLineTool", - "cwlVersion": "v1.2", - "inputs": {}, - "outputs": { - "pyout": { - "outputBinding": { - "glob": "output" - }, - "type": "File" + "state": { + "nodes": [ + { + "id": 1, + "name": "PythonHelloWorld", + "pluginId": "", + "cwlScript": { + "steps": { + "one": { + "run": { + "baseCommand": [ + "python", + "-c", + "print('hello world'); print('sqr of 7 : %.2f' % 7**2)" + ], + "class": "CommandLineTool", + "cwlVersion": "v1.2", + "inputs": {}, + "outputs": { + "pyout": { + "outputBinding": { + "glob": "output" + }, + "type": "File" + } + }, + "stdout": "output", + "requirements": { + "InlineJavascriptRequirement": {} } - }, - "stdout": "output", - "requirements": { - "InlineJavascriptRequirement": {} } } } - } - }, - "settings": {}, - "internal": false - } - ], - "links": [] + }, + "settings": {}, + "internal": false + } + ], + "links": [] + }, + "plugins": [] } \ No newline at end of file