Skip to content

Commit 1e4224c

Browse files
committed
Robustify wf_info
1 parent 055c520 commit 1e4224c

File tree

2 files changed

+103
-18
lines changed

2 files changed

+103
-18
lines changed

test/test_wesclient_utils.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import unittest
2+
import os
3+
from wes_client.util import wf_info
4+
5+
class WorkflowInfoTest(unittest.TestCase):
6+
7+
local = {'cwl': 'file://' + os.path.join(os.getcwd() + '/workflow-service/testdata/md5sum.cwl'),
8+
'wdl': 'file://' + os.path.join(os.getcwd() + '/workflow-service/testdata/md5sum.wdl'),
9+
'py': 'file://' + os.path.join(os.getcwd() + '/workflow-service/test/test_integration.py'),
10+
'unsupported':'fake.txt'}
11+
12+
remote = {'cwl':'https://raw.githubusercontent.com/common-workflow-language/workflow-service/master/testdata/md5sum.cwl',
13+
'wdl':'https://raw.githubusercontent.com/common-workflow-language/workflow-service/master/testdata/md5sum.wdl',
14+
'py': 'https://raw.githubusercontent.com/common-workflow-language/workflow-service/master/test/test_integration.py',
15+
'unsupported': 'gs://topmed_workflow_testing/topmed_aligner/small_test_files_sbg/example_human_known_snp.py', # TODO: find real external file of .py, .cwl, .wdl
16+
'unreachable':'https://fake.py'}
17+
18+
expected = {'cwl':('v1.0', 'CWL'),
19+
'wdl':('draft-2','WDL'),
20+
'py': ('2.7','PY'),
21+
'pyWithPrefix' : ('2.7','PY')}
22+
23+
def testSupportedFormatChecking(self):
24+
"""Check that non-wdl, -python, -cwl files are rejected."""
25+
26+
# The choice to run this on local files prevents the irrelevant steps of creating and removing a new file.
27+
for format, location in self.local.items():
28+
if format != 'unsupported':
29+
# Tests the behavior after receiving supported file types with and without the 'file://' prefix
30+
self.assertEquals(wf_info(location), self.expected[format])
31+
self.assertEquals(wf_info(location[7:]), self.expected[format])
32+
33+
else:
34+
# Tests behavior after recieveing a non supported file type.
35+
with self.assertRaises(TypeError):
36+
wf_info(location)
37+
38+
39+
def testFileLocationChecking(self):
40+
"""Check that the function rejects unsupported file locations."""
41+
# This needs to be run on remote files to get to the location checking step.
42+
for format, location in self.remote.items():
43+
if format == 'unsupported':
44+
# Tests behavior after receiving a non-existant file.
45+
with self.assertRaises(NotImplementedError):
46+
wf_info(location)
47+
elif format == 'unreachable':
48+
# Tests behavior after receiving a non-existant file.
49+
with self.assertRaises(IOError):
50+
wf_info(location)
51+
else:
52+
self.assertEquals(wf_info(location), self.expected[format])
53+
self.assertFalse(os.path.isfile(os.path.join(os.getcwd(), 'fetchedFromRemote.' + format)))

wes_client/util.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
11
import os
22
import json
3+
import subprocess
4+
import yaml
5+
from urllib import urlopen
36

4-
5-
def wf_type(workflow_file):
6-
if workflow_file.lower().endswith('wdl'):
7-
return 'WDL'
8-
elif workflow_file.lower().endswith('cwl'):
9-
return 'CWL'
10-
elif workflow_file.lower().endswith('py'):
11-
return 'PY'
12-
else:
13-
raise ValueError('Unrecognized/unsupported workflow file extension: %s' % workflow_file.lower().split('.')[-1])
7+
def _twoSevenCompatible(filePath):
8+
"""Determines if a python file is 2.7 compatible by seeing if it compiles in a subprocess"""
9+
try:
10+
passes = not subprocess.call(['python2', '-m', 'py_compile', filePath])
11+
except:
12+
raise RuntimeError('Python files must be 2.7 compatible')
13+
return passes
1414

1515

16-
def wf_version(workflow_file):
17-
# TODO: Check inside of the file, handling local/http/etc.
18-
if wf_type(workflow_file) == 'PY':
16+
def _getVersion(extension, workflow_file):
17+
'''Determines the version of a .py, .wdl, or .cwl file.'''
18+
if extension == 'py' and _twoSevenCompatible(workflow_file):
1919
return '2.7'
20-
# elif wf_type(workflow_file) == 'CWL':
21-
# # only works locally
22-
# return yaml.load(open(workflow_file))['cwlVersion']
20+
elif extension == 'cwl':
21+
return yaml.load(open(workflow_file))['cwlVersion']
22+
else: # Must be a wdl file.
23+
# Borrowed from https://github.com/Sage-Bionetworks/synapse-orchestrator/blob/develop/synorchestrator/util.py#L142
24+
try:
25+
return [l.lstrip('version') for l in workflow_file.splitlines() if 'version' in l.split(' ')][0]
26+
except IndexError:
27+
return 'draft-2'
28+
29+
30+
def wf_info(workflow_file):
31+
"""
32+
Returns the version of the file and the file extension.
33+
34+
Assumes that the file path is to the file directly ie, ends with a valid file extension.Supports checking local
35+
files as well as files at http:// and https:// locations. Files at these remote locations are recreated locally to
36+
enable our approach to version checking, then removed after version is extracted.
37+
"""
38+
39+
supportedFormats = ['py', 'wdl', 'cwl']
40+
fileType = workflow_file.lower().split('.')[-1] # Grab the file extension
41+
workflow_file = workflow_file if ':' in workflow_file else 'file://' + workflow_file
42+
43+
if fileType in supportedFormats:
44+
if workflow_file.startswith('file://'):
45+
version = _getVersion(fileType, workflow_file[7:])
46+
elif workflow_file.startswith('https://') or workflow_file.startswith('http://'): # If file not local go fetch it.
47+
html = urlopen(workflow_file).read()
48+
localLoc = os.path.join(os.getcwd(), 'fetchedFromRemote.' + fileType)
49+
with open(localLoc, 'w') as f:
50+
f.write(html)
51+
version = wf_info('file://' + localLoc)[0] # Dont take the filetype here.
52+
os.remove(localLoc) # TODO: Find a way to avoid recreating file before version determination.
53+
else:
54+
raise NotImplementedError('Unsupported workflow file location: {}. Must be local or HTTP(S).'.format(workflow_file))
2355
else:
24-
# TODO: actually check the wdl file
25-
return "v1.0"
56+
raise TypeError('Unsupported workflow type: .{}. Must be {}.'.format(fileType, '.py, .cwl, or .wdl'))
57+
return version, fileType.upper()
2658

2759

2860
def build_wes_request(workflow_file, json_path, attachments=None):

0 commit comments

Comments
 (0)