diff --git a/.travis.yml b/.travis.yml index ed6ad35..48cbfc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,11 @@ python: os: - linux before_install: + - sudo apt-get update + - sudo apt-get install graphviz - python --version install: + - pip install -r requirements.txt - pip install -r test-requirements.txt - python setup.py install # command to run tests @@ -23,6 +26,7 @@ matrix: osx_image: xcode9.4 # Python 3.7 running on macOS 10.13 language: shell # 'language: python' is an error on Travis CI macOS before_install: + - brew install graphviz - python3 --version - pip3 install virtualenv - virtualenv -p python3 venv diff --git a/cwlkernel/kernel_magics.py b/cwlkernel/kernel_magics.py index c3cd68e..88eaf40 100644 --- a/cwlkernel/kernel_magics.py +++ b/cwlkernel/kernel_magics.py @@ -6,6 +6,9 @@ from pathlib import Path from typing import List +import pydot +from cwltool.cwlviewer import CWLViewer +from cwltool.main import main as cwltool_main from ruamel.yaml import YAML from .CWLKernel import CONF as CWLKernel_CONF @@ -342,6 +345,33 @@ def magics(kernel: CWLKernel, arg: str): ) +@CWLKernel.register_magic('view') +def visualize_graph(kernel: CWLKernel, tool_id: str): + """Visualize a Workflow""" + tool_id = tool_id.strip() + path = kernel.workflow_repository.get_tools_path_by_id(tool_id) + rdf_stream = StringIO() + import logging + handler = logging.StreamHandler() + cwltool_main(['--print-rdf', os.path.abspath(path)], stdout=rdf_stream, logger_handler=handler) + cwl_viewer = CWLViewer(rdf_stream.getvalue()) + (dot_object,) = pydot.graph_from_dot_data(cwl_viewer.dot()) + image = dot_object.create('dot', 'svg') + + kernel.send_response( + kernel.iopub_socket, + 'display_data', + { + 'data': { + "image/svg+xml": image.decode(), + "text/plain": image.decode() + }, + 'metadata': {}, + }, + + ) + + # import user's magic commands if CWLKernel_CONF.CWLKERNEL_MAGIC_COMMANDS_DIRECTORY is not None: diff --git a/examples/Compose.ipynb b/examples/Compose.ipynb index c030814..19560b5 100644 --- a/examples/Compose.ipynb +++ b/examples/Compose.ipynb @@ -96,7 +96,13 @@ "type": "File" } ], - "outputs": [], + "outputs": [ + { + "id": "tailoutput", + "outputSource": "tailstepid/tailoutput", + "type": "File" + } + ], "requirements": {}, "steps": { "headstepid": { @@ -112,13 +118,15 @@ "in": { "tailinput": "headstepid/headoutput" }, - "out": [], + "out": [ + "tailoutput" + ], "run": "tail.cwl" } } }, "text/plain": [ - "{\"cwlVersion\": \"v1.0\", \"class\": \"Workflow\", \"id\": \"main\", \"inputs\": [{\"id\": \"inputfile\", \"type\": \"File\"}], \"outputs\": [], \"steps\": {\"tailstepid\": {\"run\": \"tail.cwl\", \"in\": {\"tailinput\": \"headstepid/headoutput\"}, \"out\": []}, \"headstepid\": {\"run\": \"head.cwl\", \"in\": {\"headinput\": \"inputfile\"}, \"out\": [\"headoutput\"]}}, \"requirements\": {}}" + "{\"cwlVersion\": \"v1.0\", \"class\": \"Workflow\", \"id\": \"main\", \"inputs\": [{\"id\": \"inputfile\", \"type\": \"File\"}], \"outputs\": [{\"id\": \"tailoutput\", \"type\": \"File\", \"outputSource\": \"tailstepid/tailoutput\"}], \"steps\": {\"tailstepid\": {\"run\": \"tail.cwl\", \"in\": {\"tailinput\": \"headstepid/headoutput\"}, \"out\": [\"tailoutput\"]}, \"headstepid\": {\"run\": \"head.cwl\", \"in\": {\"headinput\": \"inputfile\"}, \"out\": [\"headoutput\"]}}, \"requirements\": {}}" ] }, "metadata": { @@ -139,6 +147,7 @@ "type: File\n", "% newWorkflowAddStepIn tailstepid headstepid headoutput\n", "tailinput: headstepid/headoutput\n", + "% newWorkflowAddOutputSource tailstepid/tailoutput File\n", "% newWorkflowBuild" ] }, @@ -156,9 +165,22 @@ }, { "data": { - "application/json": {}, + "application/json": { + "tailoutput": { + "basename": "tail.out", + "checksum": "sha1$e186f07099395040cf9d83ff1eb0a5dad4801937", + "class": "File", + "http://commonwl.org/cwltool#generation": 0, + "id": "tailoutput", + "location": "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/runtime_data/tail.out", + "nameext": ".out", + "nameroot": "tail", + "result_counter": 0, + "size": 688 + } + }, "text/plain": [ - "{}" + "{\"tailoutput\": {\"location\": \"file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/runtime_data/tail.out\", \"basename\": \"tail.out\", \"nameroot\": \"tail\", \"nameext\": \".out\", \"class\": \"File\", \"checksum\": \"sha1$e186f07099395040cf9d83ff1eb0a5dad4801937\", \"size\": 688, \"http://commonwl.org/cwltool#generation\": 0, \"id\": \"tailoutput\", \"result_counter\": 0}}" ] }, "metadata": { @@ -174,7 +196,157 @@ "% execute main\n", "inputfile: \n", " class: File\n", - " location: /Users/dks/Desktop/data.csv" + " location: /Users/dks/Workspaces/CWLKernel/tests/input_data/data.csv" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "G\n", + "\n", + "\n", + "cluster_inputs\n", + "\n", + "Workflow Inputs\n", + "\n", + "\n", + "cluster_outputs\n", + "\n", + "Workflow Outputs\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid\n", + "\n", + "head\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid\n", + "\n", + "tail\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailoutput\n", + "\n", + "main/tailoutput\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailoutput\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/inputfile\n", + "\n", + "main/inputfile\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/inputfile->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "G\n", + "\n", + "\n", + "cluster_inputs\n", + "\n", + "Workflow Inputs\n", + "\n", + "\n", + "cluster_outputs\n", + "\n", + "Workflow Outputs\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid\n", + "\n", + "head\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid\n", + "\n", + "tail\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailoutput\n", + "\n", + "main/tailoutput\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailstepid->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/tailoutput\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/inputfile\n", + "\n", + "main/inputfile\n", + "\n", + "\n", + "\n", + "file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/inputfile->file:///private/tmp/CWLKERNEL_DATA/d8f78c63-0b6a-413b-b60e-03d8f96165c8/repo/main.cwl#main/headstepid\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "% view main" ] } ], diff --git a/examples/multipleSteps.ipynb b/examples/multipleSteps.ipynb index ac5457b..5c948ae 100644 --- a/examples/multipleSteps.ipynb +++ b/examples/multipleSteps.ipynb @@ -276,6 +276,15 @@ "source": [ "% displayData tailoutput" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "% view main" + ] } ], "metadata": { diff --git a/requirements.txt b/requirements.txt index 17ed56b..50908a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cwltool>=3.0.20200706173533 +git+https://github.com/giannisdoukas/cwltool.git@cwl-view#egg=cwltool jsonschema>=3.2.0 jupyter-client>=5.3.4 jupyter-core>=4.6.3 @@ -8,4 +8,5 @@ PyYAML>=5.3.1 pandas>=1.0.4 notebook>=6.0.3 requests>=2.23.0 -pygtrie>=2.3.3 \ No newline at end of file +pygtrie>=2.3.3 +pydot>=1.4.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 7398c7d..df21f28 100644 --- a/setup.py +++ b/setup.py @@ -59,16 +59,6 @@ def get_version(rel_path): with open(os.sep.join([os.path.abspath(os.path.dirname(__file__)), "README.md"]), "r") as fh: long_description = fh.read() -with open('requirements.txt') as f: - req = f.readlines() - -for i, r in enumerate(req): - r = r.rstrip() - if r.startswith('git+https://'): - egg_position = r.rfind("#egg=") - dependency_name = r[egg_position + 5:] - req[i] = f"{dependency_name} @ {r[:egg_position]}" - setup( name=name, version=get_version(f"{name}/CWLKernel.py"), @@ -91,9 +81,4 @@ def get_version(rel_path): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], - # cmdclass={ - # 'develop': PostDevelopCommand, - # 'install': PostInstallCommand, - # }, - install_requires=req ) diff --git a/tests/test_CWLKernel.py b/tests/test_CWLKernel.py index 9bb6193..9af0677 100644 --- a/tests/test_CWLKernel.py +++ b/tests/test_CWLKernel.py @@ -844,7 +844,6 @@ def test_magics_magic_command(self): commands = [f'\t- {cmd}' for cmd in kernel._magic_commands.keys()] commands.sort() - self.assertDictEqual( {'status': 'ok', 'execution_count': 0, 'payload': [], 'user_expressions': {}}, kernel.do_execute(f"""% magics""") @@ -867,6 +866,73 @@ def test_magics_magic_command(self): self.assertIn(' '.join('Display all the data which are registered in the kernel session.'.split()), ' '.join(responses[-1][0][2]['text'].split())) + def test_view_magic_command(self): + kernel = CWLKernel() + # cancel send_response + responses = [] + kernel.send_response = lambda *args, **kwargs: responses.append((args, kwargs)) + with open(os.sep.join([self.cwl_directory, 'head.cwl'])) as f: + head = f.read() + with open(os.sep.join([self.cwl_directory, 'tail.cwl'])) as f: + tail = f.read() + self.assertDictEqual( + {'status': 'ok', 'execution_count': 0, 'payload': [], 'user_expressions': {}}, + kernel.do_execute(head) + ) + self.assertDictEqual( + {'status': 'ok', 'execution_count': 0, 'payload': [], 'user_expressions': {}}, + kernel.do_execute(tail) + ) + + self.assertDictEqual( + {'status': 'ok', 'execution_count': 0, 'payload': [], 'user_expressions': {}}, + kernel.do_execute("""% newWorkflow main +% newWorkflowAddStep tail tailstepid +% newWorkflowAddStep head headstepid +% newWorkflowAddInput headstepid headinput +id: inputfile +type: File +% newWorkflowAddStepIn tailstepid headstepid headoutput +tailinput: headstepid/headoutput +% newWorkflowAddOutputSource tailstepid/tailoutput File +% newWorkflowBuild""") + ) + + self.assertDictEqual( + { + "cwlVersion": "v1.0", + "class": "Workflow", + "id": "main", + "inputs": [{'id': 'inputfile', 'type': 'File'}], + "outputs": [{'id': 'tailoutput', 'type': 'File', 'outputSource': "tailstepid/tailoutput"}], + "steps": { + "headstepid": + { + "run": "head.cwl", + "in": {"headinput": "inputfile"}, + "out": ['headoutput'] + }, + "tailstepid": + { + "run": "tail.cwl", + "in": {"tailinput": "headstepid/headoutput"}, + "out": ['tailoutput'] + }, + }, + 'requirements': {} + }, + yaml.load(kernel._workflow_repository.get_by_id("main").to_yaml(), yaml.Loader), + ) + + self.assertDictEqual( + {'status': 'ok', 'execution_count': 0, 'payload': [], 'user_expressions': {}}, + kernel.do_execute('% view main') + ) + + self.assertIn( + 'image/svg+xml', + responses[-1][0][2]['data']) + if __name__ == '__main__': unittest.main()