-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #90 from matthew-t-smith/refactor/cli
Refactors CLI to reduce need for special execute functions
- Loading branch information
Showing
4 changed files
with
98 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,123 @@ | ||
import sys | ||
|
||
import click | ||
import os | ||
import uuid | ||
import json | ||
|
||
from pyworkflow import Workflow | ||
from pyworkflow import Workflow, WorkflowException | ||
from pyworkflow import NodeException | ||
from pyworkflow.nodes import ReadCsvNode, WriteCsvNode | ||
|
||
|
||
class Config(object): | ||
def __init__(self): | ||
self.verbose = False | ||
|
||
|
||
pass_config = click.make_pass_decorator(Config, ensure=True) | ||
|
||
|
||
@click.group() | ||
def cli(): | ||
pass | ||
|
||
|
||
@cli.command() | ||
@click.argument('filename', type=click.Path(exists=True), nargs=-1) | ||
@click.argument('filenames', type=click.Path(exists=True), nargs=-1) | ||
@click.option('--verbose', is_flag=True, help='Enables verbose mode.') | ||
def execute(filename, verbose): | ||
def execute(filenames, verbose): | ||
"""Execute Workflow file(s).""" | ||
# Check whether to log to terminal, or redirect output | ||
log = click.get_text_stream('stdout').isatty() | ||
|
||
write_to_stdout = not click.get_text_stream('stdout').isatty() | ||
# Execute each workflow in the args | ||
for workflow_file in filenames: | ||
|
||
#execute each one of the workflows in the ar | ||
for workflow_file in filename: | ||
if workflow_file is None: | ||
click.echo('Please specify a workflow to run', err=True) | ||
return | ||
|
||
stdin_files = [] | ||
if log: | ||
click.echo('Loading workflow file from %s' % workflow_file) | ||
|
||
if not click.get_text_stream('stdin').isatty(): | ||
stdin_text = click.get_text_stream('stdin') | ||
try: | ||
workflow = open_workflow(workflow_file) | ||
execute_workflow(workflow, log, verbose) | ||
except OSError as e: | ||
click.echo(f"Issues loading workflow file: {e}", err=True) | ||
except WorkflowException as e: | ||
click.echo(f"Issues during workflow execution\n{e}", err=True) | ||
|
||
|
||
def execute_workflow(workflow, log, verbose): | ||
"""Execute a workflow file, node-by-node. | ||
Retrieves the execution order from the Workflow and iterates through nodes. | ||
If any I/O nodes are present AND stdin/stdout redirection is provided in the | ||
command-line, overwrite the stored options and then replace before saving. | ||
Args: | ||
workflow - Workflow object loaded from file | ||
log - True, for outputting to terminal; False for stdout redirection | ||
verbose - True, for outputting debug information; False otherwise | ||
""" | ||
execution_order = workflow.execution_order() | ||
|
||
# Execute each node in the order returned by the Workflow | ||
for node in execution_order: | ||
try: | ||
node_to_execute = workflow.get_node(node) | ||
original_file_option = pre_execute(workflow, node_to_execute, log) | ||
|
||
# write standard in to a new file in local filesystem | ||
file_name = str(uuid.uuid4()) | ||
if verbose: | ||
print('Executing node of type ' + str(type(node_to_execute))) | ||
|
||
# TODO small issue here, might be better to upload this file to the workflow directory instead of cwd | ||
new_file_path = os.path.join(os.getcwd(), file_name) | ||
# perform execution | ||
executed_node = workflow.execute(node) | ||
|
||
# read from std in and upload a new file in project directory | ||
with open(new_file_path, 'w') as f: | ||
f.write(stdin_text.read()) | ||
# If file was replaced with stdin/stdout, restore original option | ||
if original_file_option is not None: | ||
executed_node.option_values["file"] = original_file_option | ||
|
||
stdin_files.append(file_name) | ||
# Update Node in Workflow with changes (saved data file) | ||
workflow.update_or_add_node(executed_node) | ||
except NodeException as e: | ||
click.echo(f"Issues during node execution\n{e}", err=True) | ||
|
||
if workflow_file is None: | ||
click.echo('Please specify a workflow to run') | ||
return | ||
try: | ||
if not write_to_stdout: | ||
click.echo('Loading workflow file from %s' % workflow_file) | ||
if verbose: | ||
click.echo('Completed workflow execution!') | ||
|
||
Workflow.execute_workflow(workflow_file, stdin_files, write_to_stdout, verbose) | ||
|
||
if verbose: | ||
click.echo('Completed workflow execution!') | ||
def pre_execute(workflow, node_to_execute, log): | ||
"""Pre-execution steps, to overwrite file options with stdin/stdout. | ||
If stdin is not a tty, and the Node is ReadCsv, replace file with buffer. | ||
If stdout is not a tty, and the Node is WriteCsv, replace file with buffer. | ||
Args: | ||
workflow - Workflow object loaded from file | ||
node_to_execute - The Node to execute | ||
log - True, for outputting to terminal; False for stdout redirection | ||
""" | ||
stdin = click.get_text_stream('stdin') | ||
|
||
if type(node_to_execute) is ReadCsvNode and not stdin.isatty(): | ||
new_file_location = stdin | ||
elif type(node_to_execute) is WriteCsvNode and not log: | ||
new_file_location = click.get_text_stream('stdout') | ||
else: | ||
# No file redirection needed | ||
return None | ||
|
||
# save original file info | ||
original_file_option = node_to_execute.option_values["file"] | ||
|
||
# replace with value from stdin and save | ||
node_to_execute.option_values["file"] = new_file_location | ||
workflow.update_or_add_node(node_to_execute) | ||
|
||
return original_file_option | ||
|
||
|
||
def open_workflow(workflow_file): | ||
with open(workflow_file) as f: | ||
json_content = json.load(f) | ||
|
||
except NodeException as ne: | ||
click.echo("Issues during node execution") | ||
click.echo(ne) | ||
return Workflow.from_json(json_content['pyworkflow']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters