|
1 |
| -import sys |
2 |
| - |
3 | 1 | import click
|
4 |
| -import os |
5 |
| -import uuid |
| 2 | +import json |
6 | 3 |
|
7 |
| -from pyworkflow import Workflow |
| 4 | +from pyworkflow import Workflow, WorkflowException |
8 | 5 | from pyworkflow import NodeException
|
| 6 | +from pyworkflow.nodes import ReadCsvNode, WriteCsvNode |
9 | 7 |
|
10 | 8 |
|
11 | 9 | class Config(object):
|
12 | 10 | def __init__(self):
|
13 | 11 | self.verbose = False
|
14 | 12 |
|
| 13 | + |
15 | 14 | pass_config = click.make_pass_decorator(Config, ensure=True)
|
16 | 15 |
|
| 16 | + |
17 | 17 | @click.group()
|
18 | 18 | def cli():
|
19 | 19 | pass
|
20 | 20 |
|
21 | 21 |
|
22 | 22 | @cli.command()
|
23 |
| -@click.argument('filename', type=click.Path(exists=True), nargs=-1) |
| 23 | +@click.argument('filenames', type=click.Path(exists=True), nargs=-1) |
24 | 24 | @click.option('--verbose', is_flag=True, help='Enables verbose mode.')
|
25 |
| -def execute(filename, verbose): |
| 25 | +def execute(filenames, verbose): |
| 26 | + """Execute Workflow file(s).""" |
| 27 | + # Check whether to log to terminal, or redirect output |
| 28 | + log = click.get_text_stream('stdout').isatty() |
26 | 29 |
|
27 |
| - write_to_stdout = not click.get_text_stream('stdout').isatty() |
| 30 | + # Execute each workflow in the args |
| 31 | + for workflow_file in filenames: |
28 | 32 |
|
29 |
| - #execute each one of the workflows in the ar |
30 |
| - for workflow_file in filename: |
| 33 | + if workflow_file is None: |
| 34 | + click.echo('Please specify a workflow to run', err=True) |
| 35 | + return |
31 | 36 |
|
32 |
| - stdin_files = [] |
| 37 | + if log: |
| 38 | + click.echo('Loading workflow file from %s' % workflow_file) |
33 | 39 |
|
34 |
| - if not click.get_text_stream('stdin').isatty(): |
35 |
| - stdin_text = click.get_text_stream('stdin') |
| 40 | + try: |
| 41 | + workflow = open_workflow(workflow_file) |
| 42 | + execute_workflow(workflow, log, verbose) |
| 43 | + except OSError as e: |
| 44 | + click.echo(f"Issues loading workflow file: {e}", err=True) |
| 45 | + except WorkflowException as e: |
| 46 | + click.echo(f"Issues during workflow execution\n{e}", err=True) |
| 47 | + |
| 48 | + |
| 49 | +def execute_workflow(workflow, log, verbose): |
| 50 | + """Execute a workflow file, node-by-node. |
| 51 | +
|
| 52 | + Retrieves the execution order from the Workflow and iterates through nodes. |
| 53 | + If any I/O nodes are present AND stdin/stdout redirection is provided in the |
| 54 | + command-line, overwrite the stored options and then replace before saving. |
| 55 | +
|
| 56 | + Args: |
| 57 | + workflow - Workflow object loaded from file |
| 58 | + log - True, for outputting to terminal; False for stdout redirection |
| 59 | + verbose - True, for outputting debug information; False otherwise |
| 60 | + """ |
| 61 | + execution_order = workflow.execution_order() |
| 62 | + |
| 63 | + # Execute each node in the order returned by the Workflow |
| 64 | + for node in execution_order: |
| 65 | + try: |
| 66 | + node_to_execute = workflow.get_node(node) |
| 67 | + original_file_option = pre_execute(workflow, node_to_execute, log) |
36 | 68 |
|
37 |
| - # write standard in to a new file in local filesystem |
38 |
| - file_name = str(uuid.uuid4()) |
| 69 | + if verbose: |
| 70 | + print('Executing node of type ' + str(type(node_to_execute))) |
39 | 71 |
|
40 |
| - # TODO small issue here, might be better to upload this file to the workflow directory instead of cwd |
41 |
| - new_file_path = os.path.join(os.getcwd(), file_name) |
| 72 | + # perform execution |
| 73 | + executed_node = workflow.execute(node) |
42 | 74 |
|
43 |
| - # read from std in and upload a new file in project directory |
44 |
| - with open(new_file_path, 'w') as f: |
45 |
| - f.write(stdin_text.read()) |
| 75 | + # If file was replaced with stdin/stdout, restore original option |
| 76 | + if original_file_option is not None: |
| 77 | + executed_node.option_values["file"] = original_file_option |
46 | 78 |
|
47 |
| - stdin_files.append(file_name) |
| 79 | + # Update Node in Workflow with changes (saved data file) |
| 80 | + workflow.update_or_add_node(executed_node) |
| 81 | + except NodeException as e: |
| 82 | + click.echo(f"Issues during node execution\n{e}", err=True) |
48 | 83 |
|
49 |
| - if workflow_file is None: |
50 |
| - click.echo('Please specify a workflow to run') |
51 |
| - return |
52 |
| - try: |
53 |
| - if not write_to_stdout: |
54 |
| - click.echo('Loading workflow file from %s' % workflow_file) |
| 84 | + if verbose: |
| 85 | + click.echo('Completed workflow execution!') |
55 | 86 |
|
56 |
| - Workflow.execute_workflow(workflow_file, stdin_files, write_to_stdout, verbose) |
57 | 87 |
|
58 |
| - if verbose: |
59 |
| - click.echo('Completed workflow execution!') |
| 88 | +def pre_execute(workflow, node_to_execute, log): |
| 89 | + """Pre-execution steps, to overwrite file options with stdin/stdout. |
| 90 | +
|
| 91 | + If stdin is not a tty, and the Node is ReadCsv, replace file with buffer. |
| 92 | + If stdout is not a tty, and the Node is WriteCsv, replace file with buffer. |
| 93 | +
|
| 94 | + Args: |
| 95 | + workflow - Workflow object loaded from file |
| 96 | + node_to_execute - The Node to execute |
| 97 | + log - True, for outputting to terminal; False for stdout redirection |
| 98 | + """ |
| 99 | + stdin = click.get_text_stream('stdin') |
| 100 | + |
| 101 | + if type(node_to_execute) is ReadCsvNode and not stdin.isatty(): |
| 102 | + new_file_location = stdin |
| 103 | + elif type(node_to_execute) is WriteCsvNode and not log: |
| 104 | + new_file_location = click.get_text_stream('stdout') |
| 105 | + else: |
| 106 | + # No file redirection needed |
| 107 | + return None |
| 108 | + |
| 109 | + # save original file info |
| 110 | + original_file_option = node_to_execute.option_values["file"] |
| 111 | + |
| 112 | + # replace with value from stdin and save |
| 113 | + node_to_execute.option_values["file"] = new_file_location |
| 114 | + workflow.update_or_add_node(node_to_execute) |
| 115 | + |
| 116 | + return original_file_option |
| 117 | + |
60 | 118 |
|
| 119 | +def open_workflow(workflow_file): |
| 120 | + with open(workflow_file) as f: |
| 121 | + json_content = json.load(f) |
61 | 122 |
|
62 |
| - except NodeException as ne: |
63 |
| - click.echo("Issues during node execution") |
64 |
| - click.echo(ne) |
| 123 | + return Workflow.from_json(json_content['pyworkflow']) |
0 commit comments