Skip to content

Commit eaf6193

Browse files
author
bshifaw
authored
Add metadata function (#157)
* added metadata and slim metadata command * added checker line for requests response to toggle on whether to raise an exception * added flag to toggle expanding subworkflow * added unit tests for metadata function * added 'pygments' required packages to setup.py * Add ability to exclude input keys, moved processes_keys_and_flags function call to module level, removed string concatenation in favor for requests params
1 parent 22c0252 commit eaf6193

16 files changed

+392
-62
lines changed

dev-requirements.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ cython
1010
sphinx
1111
sphinx-rtd-theme
1212
mypy==0.770
13-
click
14-
termcolor
13+
click>=8.0.0
14+
termcolor
15+
requests
16+
pygments

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
termcolor
2020
click
2121
requests
22+
pygments
2223
""".split(
2324
"\n"
2425
),

src/cromshell/__main__.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from cromshell.utilities import cromshellconfig
77

88
from .abort import command as abort
9+
from .metadata import command as metadata
10+
from .slim_metadata import command as slim_metadata
911
from .status import command as status
1012
from .submit import command as submit
1113

@@ -43,11 +45,6 @@
4345
flag_value=True,
4446
help="Hide turtle logo",
4547
)
46-
@click.option(
47-
"--slim_metadata_parameters",
48-
type=str,
49-
help="Get a subset of the metadata for a workflow",
50-
)
5148
@click.option(
5249
"--cromwell_url",
5350
type=str,
@@ -71,7 +68,6 @@
7168
def main_entry(
7269
cromshell_config,
7370
verbosity,
74-
slim_metadata_parameters,
7571
hide_logo,
7672
cromwell_url,
7773
requests_timeout,
@@ -89,7 +85,6 @@ def main_entry(
8985

9086
# Create an object to hold all cromwell configurations
9187
cromshell_config.obj = cromshellconfig
92-
cromshellconfig.override_slim_metadata_parameters(slim_metadata_parameters)
9388
cromshellconfig.resolve_cromwell_config_server_address(server_user=cromwell_url)
9489
cromshellconfig.override_requests_cert_parameters(skip_certs=requests_skip_certs)
9590
cromshellconfig.resolve_requests_connect_timeout(timeout_cli=requests_timeout)
@@ -105,6 +100,8 @@ def version():
105100
main_entry.add_command(abort.main)
106101
main_entry.add_command(status.main)
107102
main_entry.add_command(submit.main)
103+
main_entry.add_command(slim_metadata.main)
104+
main_entry.add_command(metadata.main)
108105

109106

110107
if __name__ == "__main__":

src/cromshell/abort/command.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def main(config, workflow_ids):
3535

3636
if requests_out.ok:
3737
# Todo: Replace input with requests_out.json() once rebased with submit PR
38-
io_utils.pretty_print_json(requests_out.text)
38+
io_utils.pretty_print_json(requests_out.json())
3939
else:
4040
return_code = 1
4141

src/cromshell/metadata/__init__.py

Whitespace-only changes.

src/cromshell/metadata/command.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import logging
2+
3+
import click
4+
import requests
5+
6+
from cromshell.utilities import cromshellconfig, http_utils, io_utils
7+
8+
LOGGER = logging.getLogger(__name__)
9+
10+
11+
@click.command(name="metadata")
12+
@click.argument("workflow_id")
13+
@click.option(
14+
"-des",
15+
"--dont-expand-subworkflows",
16+
is_flag=True,
17+
default=False,
18+
help="Do not expand subworkflow info in metadata",
19+
)
20+
@click.pass_obj
21+
def main(config, workflow_id: str, dont_expand_subworkflows: bool):
22+
"""Get the full metadata of a workflow."""
23+
24+
LOGGER.info("metadata")
25+
26+
check_cromwell_server(config=config, workflow_id=workflow_id)
27+
28+
obtain_and_print_metadata(
29+
config=config,
30+
metadata_param=config.METADATA_KEYS_TO_OMIT,
31+
exclude_keys=True,
32+
dont_expand_subworkflows=dont_expand_subworkflows,
33+
)
34+
35+
return 0
36+
37+
38+
def check_cromwell_server(config, workflow_id):
39+
"""Checks for an associated cromwell server for the workflow_id
40+
and checks connection with the cromwell server"""
41+
42+
# Overrides the default cromwell url set in the cromshell config file or
43+
# command line argument if the workflow id is found in the submission file.
44+
cromshellconfig.resolve_cromwell_config_server_address(workflow_id=workflow_id)
45+
46+
config.cromwell_api_workflow_id = f"{config.get_cromwell_api()}/{workflow_id}"
47+
48+
# Check if Cromwell Server Backend works
49+
http_utils.assert_can_communicate_with_server(config)
50+
51+
52+
def format_metadata_params(
53+
list_of_keys: list, exclude_keys: bool, expand_subworkflows: bool
54+
) -> dict:
55+
"""This functions organises a list of cromwell metadata keys and flags into a
56+
dictionary that can be passed to requests library"""
57+
58+
if not list_of_keys:
59+
LOGGER.error("No keys provided when querying metadata parameter.")
60+
raise ValueError("No keys provided when querying metadata parameter.")
61+
elif "" in list_of_keys:
62+
LOGGER.error("One of the provided metadata keys is empty.")
63+
raise ValueError("One of the provided metadata keys is empty.")
64+
else:
65+
# Determines whether the list of keys will be used to exclude or
66+
# include fields in the metadata.
67+
key_action = "excludeKey" if exclude_keys else "includeKey"
68+
69+
final_key = {key_action: list_of_keys}
70+
71+
if expand_subworkflows:
72+
final_key["expandSubWorkflows"] = "true"
73+
74+
return final_key
75+
76+
77+
def get_workflow_metadata(
78+
meta_params: dict,
79+
api_workflow_id: str,
80+
timeout: int,
81+
verify_certs: bool,
82+
) -> str:
83+
"""Uses requests to get the metadata or sub-metadata of a workflow
84+
from the cromwell server and returns a JSON formatted string."""
85+
86+
requests_out = requests.get(
87+
f"{api_workflow_id}/metadata",
88+
params=meta_params,
89+
timeout=timeout,
90+
verify=verify_certs,
91+
)
92+
93+
http_utils.check_http_request_status_code(
94+
short_error_message="Failed to get metadata", response=requests_out
95+
)
96+
97+
return requests_out.json()
98+
99+
100+
def obtain_and_print_metadata(
101+
config, metadata_param: list, exclude_keys: bool, dont_expand_subworkflows: bool
102+
):
103+
"""Format metadata parameters and obtains metadata from cromwell server"""
104+
105+
# Combine keys and flags into a dictionary
106+
formatted_metadata_parameter = format_metadata_params(
107+
list_of_keys=metadata_param,
108+
exclude_keys=exclude_keys,
109+
expand_subworkflows=not dont_expand_subworkflows, # Invert variable
110+
)
111+
112+
# Request workflow metadata
113+
workflow_metadata_json = get_workflow_metadata(
114+
meta_params=formatted_metadata_parameter,
115+
api_workflow_id=config.cromwell_api_workflow_id,
116+
timeout=config.requests_connect_timeout,
117+
verify_certs=config.requests_verify_certs,
118+
)
119+
120+
io_utils.pretty_print_json(workflow_metadata_json, add_color=True)

src/cromshell/slim_metadata/__init__.py

Whitespace-only changes.
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import logging
2+
3+
import click
4+
5+
from cromshell.metadata import command as metadata_command
6+
7+
LOGGER = logging.getLogger(__name__)
8+
9+
10+
@click.command(name="slim-metadata")
11+
@click.argument("workflow_id")
12+
@click.option(
13+
"-k",
14+
"--keys",
15+
help="Use keys to get a subset of the metadata for a workflow. "
16+
"Separate multiple keys by comma (e.g. '-k id[,status,...]').",
17+
)
18+
@click.option(
19+
"-des",
20+
"--dont-expand-subworkflows",
21+
is_flag=True,
22+
default=False,
23+
help="Do not expand subworkflow info in metadata",
24+
)
25+
@click.option(
26+
"-x",
27+
"--exclude_keys",
28+
is_flag=True,
29+
show_default=True,
30+
default=False,
31+
help="Toggle to either include or exclude keys that are specified "
32+
"by the --keys option or in the cromshell config JSON.",
33+
)
34+
@click.pass_obj
35+
def main(
36+
config,
37+
workflow_id: str,
38+
keys: list,
39+
dont_expand_subworkflows: bool,
40+
exclude_keys: bool,
41+
):
42+
"""Get a subset of the workflow metadata."""
43+
44+
LOGGER.info("slim-metadata")
45+
46+
# If no keys were provided then set key_param to empty else
47+
# strip trailing comma from keys and split keys by comma
48+
key_param = [] if not keys else str(keys).strip(",").split(",")
49+
50+
metadata_command.check_cromwell_server(config=config, workflow_id=workflow_id)
51+
52+
# Resolve and get metadata keys from cli, config file, or config default
53+
metadata_parameter = resolve_and_return_metadata_keys(
54+
cli_key=key_param,
55+
cromshell_config_options=config.cromshell_config_options,
56+
config_slim_metadata_default_param=config.SLIM_METADATA_DEFAULT_KEYS,
57+
)
58+
59+
key_action = "include" if not exclude_keys else "exclude"
60+
LOGGER.info("Metadata keys set to %s: %s", key_action, metadata_parameter)
61+
62+
metadata_command.obtain_and_print_metadata(
63+
config=config,
64+
metadata_param=metadata_parameter,
65+
exclude_keys=exclude_keys,
66+
dont_expand_subworkflows=dont_expand_subworkflows,
67+
)
68+
69+
return 0
70+
71+
72+
def resolve_and_return_metadata_keys(
73+
cli_key: list,
74+
cromshell_config_options: dict,
75+
config_slim_metadata_default_param: list,
76+
) -> list:
77+
"""Determines which metadata keys to use from cli, config file, and default
78+
parameters, then returns a string of the processed keys ready to be used
79+
in an api call. Also returns true if default parameter is being used else false."""
80+
81+
# If keys is specified in cli then use this first
82+
if cli_key:
83+
LOGGER.info("Using metadata key(s) from command line options.")
84+
return cli_key
85+
86+
# If metadata_keys is specified in cromshell config file then use it for keys
87+
elif "slim_metadata_keys" in cromshell_config_options:
88+
LOGGER.info("Setting metadata key(s) from value in config file.")
89+
# TODO: Turn to magic string in the config script once rebased with PR 156
90+
return cromshell_config_options["slim_metadata_keys"]
91+
92+
# Return the default keys from config module constant
93+
else:
94+
LOGGER.info("No custom metadata keys were found in cli or config file.")
95+
# The default metadata key needs to
96+
return config_slim_metadata_default_param

src/cromshell/submit/command.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def main(config, wdl, wdl_json, options_json, dependencies_zip):
100100

101101
# Everything checks out, display success to terminal
102102
log.display_logo(io_utils.turtle)
103-
io_utils.pretty_print_json(request_out.text)
103+
io_utils.pretty_print_json(request_out.json())
104104

105105
# TODO: Refactor these file manipulations into its own "cleanup" function?
106106
# If we get here, we successfully submitted the job and should track it locally:

src/cromshell/utilities/cromshellconfig.py

+10-18
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
"""Setup Cromshell config details. Intended to be used as a singleton"""
1212

1313
# Set Cromshell Configuration Default Values
14-
METADATA_PARAMETERS = "excludeKey=submittedFiles&expandSubWorkflows=true"
15-
slim_metadata_parameters = (
16-
"=includeKey=id&includeKey=executionStatus&includeKey=backendStatus&includeKey"
17-
"=status&includeKey=callRoot&expandSubWorkflows=true&includeKey"
18-
"=subWorkflowMetadata&includeKey=subWorkflowId"
19-
)
14+
METADATA_KEYS_TO_OMIT = ["submittedFiles"]
15+
SLIM_METADATA_DEFAULT_KEYS = [
16+
"id",
17+
"executionStatus",
18+
"backendStatus",
19+
"status",
20+
"callRoot",
21+
"subWorkflowMetadata",
22+
"subWorkflowId",
23+
]
2024
API_STRING = "/api/workflows/v1"
2125
# Concatenate the cromwell url, api string, and workflow ID. Set in subcommand.
2226
cromwell_api_workflow_id = None
@@ -65,18 +69,6 @@ class WorkflowStatuses(Enum):
6569
DOOMED = ["DOOMED"]
6670

6771

68-
def override_slim_metadata_parameters(slim_metadata_param):
69-
"""Override Cromwell Slim Metadata Key From Command Line"""
70-
71-
global slim_metadata_parameters
72-
73-
if slim_metadata_param:
74-
slim_metadata_parameters = (
75-
slim_metadata_param
76-
+ "&includeKey=subWorkflowMetadata&includeKey=subWorkflowId"
77-
)
78-
79-
8072
def resolve_cromwell_config_server_address(server_user=None, workflow_id=None):
8173
"""
8274
Override Cromwell Server From Command Line or Environment or Submission file

src/cromshell/utilities/http_utils.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ def check_http_request_status_code(
3939
response: requests.models.Response,
4040
raise_exception: bool = True,
4141
):
42-
"""Check request response "ok" key. Response.ok returns
43-
False if status_code is equal to or greater than 400.
42+
"""Check request response "ok" key value. If status_code is
43+
equal to or greater than 400 Response.ok returns
44+
False and checker function will fail with error.
4445
4546
- short_error_message: simple version of error message
4647
- response: output from request
@@ -52,7 +53,7 @@ def check_http_request_status_code(
5253
LOGGER.error("Status_code: %s", response.status_code)
5354
LOGGER.error("Message: %s", response.text)
5455
if raise_exception:
55-
raise Exception(
56+
raise requests.exceptions.RequestException(
5657
f"{short_error_message}\n"
5758
f"Reason: {response.reason}\n"
5859
f"Status_code: {response.status_code}\n"

0 commit comments

Comments
 (0)