Skip to content

Commit

Permalink
Implement the metadata in both API and CLI. (#76)
Browse files Browse the repository at this point in the history
* Roughly implement the /metadata in both API and CLI.

* Fix the arguments for metadata commands.
  • Loading branch information
rexwangcc authored Jun 3, 2019
1 parent e7c1003 commit 9833c34
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 1 deletion.
42 changes: 41 additions & 1 deletion cromwell_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def parser(arguments=None):
help='release_hold help',
description='Request Cromwell to release the hold on a workflow.',
)
metadata = subparsers.add_parser(
'metadata',
help='metadata help',
description='Retrieve the workflow and call-level metadata for a specified workflow by UUID.',
)
query = subparsers.add_parser(
'query',
help='query help',
Expand All @@ -53,7 +58,16 @@ def parser(arguments=None):
)

# cromwell url and authentication arguments apply to all sub-commands
cromwell_sub_commands = (submit, wait, status, abort, release_hold, query, health)
cromwell_sub_commands = (
submit,
wait,
status,
abort,
release_hold,
metadata,
query,
health,
)
auth_args = {
'url': 'The URL to the Cromwell server. e.g. "https://cromwell.server.org/"',
'username': 'Cromwell username for HTTPBasicAuth.',
Expand Down Expand Up @@ -186,6 +200,31 @@ def add_auth_args(subcommand_parser):
help='A Cromwell workflow UUID, which is the workflow identifier.',
)

# metadata arguments
metadata.add_argument(
'--uuid',
required=True,
help='A Cromwell workflow UUID, which is the workflow identifier.',
)
# TODO: add a mutually exclusive group to make it fail early
metadata.add_argument(
'--includeKey',
nargs='+',
default=None,
help='When specified key(s) to include from the metadata. Matches any key starting with the value. May not be used with excludeKey.',
)
metadata.add_argument(
'--excludeKey',
nargs='+',
default=None,
help='When specified key(s) to exclude from the metadata. Matches any key starting with the value. May not be used with includeKey.',
)
metadata.add_argument(
'--expandSubWorkflows',
default=False,
help='When true, metadata for sub workflows will be fetched and inserted automatically in the metadata response.',
)

# query arguments
# TODO: implement CLI entry for query API.

Expand All @@ -200,6 +239,7 @@ def add_auth_args(subcommand_parser):
'abort',
'release_hold',
'health',
'metadata',
):
auth_arg_dict = {k: args.get(k) for k in auth_args.keys()}
auth = CromwellAuth.harmonize_credentials(**auth_arg_dict)
Expand Down
61 changes: 61 additions & 0 deletions cromwell_tools/cromwell_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,67 @@ def abort(cls, uuid, auth, raise_for_status=False):
cls._check_and_raise_status(response)
return response

@classmethod
def metadata(
cls,
uuid,
auth,
includeKey=None,
excludeKey=None,
expandSubWorkflows=False,
raise_for_status=False,
):
"""Retrieve the workflow and call-level metadata for a specified workflow by UUID.
Args:
uuid (str): A Cromwell workflow UUID, which is the workflow identifier.
auth (cromwell_tools.cromwell_auth.CromwellAuth): The authentication class holding headers or auth
information to a Cromwell server.
includeKey (Optional[List]): When specified key(s) to include from the metadata. Matches any key
starting with the value. May not be used with excludeKey. (default None)
excludeKey (Optional[List]): When specified key(s) to exclude from the metadata. Matches any key
starting with the value. May not be used with includeKey. (default None)
expandSubWorkflows (Optional[bool]): When true, metadata for sub workflows will be fetched
and inserted automatically in the metadata response. (default False)
raise_for_status (Optional[bool]): Whether to check and raise for status based on the response. (default
False)
Raises:
requests.exceptions.HTTPError: This will be raised when raise_for_status is True and Cromwell returns
a response that satisfies 400 <= response.status_code < 600.
Returns:
requests.Response: HTTP response from Cromwell.
"""

if excludeKey and includeKey:
raise ValueError('includeKey and excludeKey may not be specified together!')

params = {'expandSubWorkflows': json.dumps(expandSubWorkflows)}

if isinstance(excludeKey, str):
logger.info(f'Adding {excludeKey} to the request parameter list.')
params['excludeKey'] = [excludeKey]
elif isinstance(excludeKey, list) and len(excludeKey) >= 1:
params['excludeKey'] = excludeKey

if isinstance(includeKey, str):
logger.info(f'Adding {includeKey} to the request parameter list.')
params['includeKey'] = [includeKey]
elif isinstance(includeKey, list) and len(includeKey) >= 1:
params['includeKey'] = includeKey

response = requests.get(
url=auth.url + cls._metadata_endpoint.format(uuid=uuid),
auth=auth.auth,
headers=auth.header,
params=params,
)

if raise_for_status:
cls._check_and_raise_status(response)
return response

@classmethod
def status(cls, uuid, auth, raise_for_status=False):
"""Retrieves the current state for a workflow by UUID.
Expand Down
23 changes: 23 additions & 0 deletions cromwell_tools/tests/test_cromwell_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,29 @@ def _request_callback(request, context):
with self.assertRaises(requests.exceptions.HTTPError):
CromwellAPI.release_hold(workflow_id, cromwell_auth).raise_for_status()

@requests_mock.mock()
def test_metadata_returns_200(self, mock_request):
workflow_id = '12345abcde'
test_include_key = 'workflow'

def _request_callback(request, context):
context.status_code = 200
context.headers['test'] = 'header'
return {'id': '12345abcde', 'actualWorkflowLanguageVersion': 'draft-2'}

for cromwell_auth in self.auth_options:
mock_request.get(
'{0}/api/workflows/v1/{1}/metadata?expandSubWorkflows=false&includeKey={2}'.format(
cromwell_auth.url, workflow_id, test_include_key
),
json=_request_callback,
)
result = CromwellAPI.metadata(
workflow_id, cromwell_auth, includeKey=test_include_key
)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json()['id'], workflow_id)

@requests_mock.mock()
def test_health_returns_200(self, mock_request):
expected = {
Expand Down

0 comments on commit 9833c34

Please sign in to comment.