def exec(self, step: CodeStep, context: Dict, store: FileSystemStorage) -> ExecResult: """Execute a workflow step of type :class:`flowserv.model.workflow.step.CodeStep` in a given context. Captures output to STDOUT and STDERR and includes them in the returned execution result. Note that the code worker expects a file system storage volume. Parameters ---------- step: flowserv.model.workflow.step.CodeStep Code step in a serial workflow. context: dict Context for the executed code. store: flowserv.volume.fs.FileSystemStorage Storage volume that contains the workflow run files. Returns ------- flowserv.controller.serial.workflow.result.ExecResult """ result = ExecResult(step=step) out = sys.stdout err = sys.stderr sys.stdout = OutputStream(stream=result.stdout) sys.stderr = OutputStream(stream=result.stderr) # Change working directory temporarily. cwd = os.getcwd() os.chdir(store.basedir) try: step.exec(context=context) except Exception as ex: logging.error(ex, exc_info=True) strace = '\n'.join(util.stacktrace(ex)) logging.debug(strace) result.stderr.append(strace) result.exception = ex result.returncode = 1 finally: # Make sure to reverse redirection of output streams sys.stdout = out sys.stderr = err # Reset working directory. os.chdir(cwd) return result
def test_exec_func_step(): """Test executing a Python function as a step in a serial workflow.""" args = {'x': 1, 'y': 2} step = CodeStep(identifier='test', func=my_add, arg='z') step.exec(context=args) assert args == {'x': 1, 'y': 2, 'z': 3} # Test renaming arguments. step = CodeStep(identifier='test', func=my_add, varnames={'x': 'z'}, arg='x') step.exec(context=args) assert args == {'x': 5, 'y': 2, 'z': 3} # Execute function but ignore output. step = CodeStep(identifier='test', func=my_add) step.exec(context=args) assert args == {'x': 5, 'y': 2, 'z': 3}