Skip to content

Commit bca3011

Browse files
committed
Redirecting stdout and stderr in runner when executing the callables
1 parent 0f3e2c9 commit bca3011

File tree

7 files changed

+58
-11
lines changed

7 files changed

+58
-11
lines changed

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ To call the function using Arca, the following example would do so:
100100
print(result.output)
101101
102102
The code would print ``Hello World!``.
103-
``result`` would be a ``arca.Result`` instance which currently only has one attribute,
104-
``output``, with the output of the function call.
103+
``result`` would be a ``arca.Result`` instance. ``arca.Result`` has three attributes,
104+
``output`` with the return value of the function call, ``stdout`` and ``stderr`` contain things printed to the standard outputs.
105105
If the task fails, ``arca.exceptions.BuildError`` would be raised.
106106

107107
By default, the `Current Environment Backend <https://arca.readthedocs.io/en/latest/backends.html#current-environment>`_ is used to run tasks,

arca/_runner.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
""" This file is the code which actually launches the tasks, the serialized JSONs.
22
"""
3+
import contextlib
4+
import io
35
import json
4-
import traceback
5-
import sys
66
import os
7+
import sys
8+
import traceback
79
from importlib import import_module
810
from pathlib import Path
911

@@ -53,12 +55,19 @@ def run(filename):
5355
except (ImportError, AttributeError):
5456
return {"success": False, "reason": "import", "error": traceback.format_exc()}
5557

58+
stdout = io.StringIO()
59+
stderr = io.StringIO()
60+
5661
try:
57-
res = entry_point(*args, **kwargs)
62+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
63+
res = entry_point(*args, **kwargs)
5864
except BaseException:
5965
return {"success": False, "error": traceback.format_exc()}
6066

61-
return {"success": True, "result": res}
67+
return {"success": True,
68+
"result": res,
69+
"stdout": stdout.getvalue(),
70+
"stderr": stderr.getvalue()}
6271

6372

6473
if __name__ == "__main__":

arca/result.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,9 @@ def __init__(self, result: Union[str, Dict[str, Any]]) -> None:
3636

3737
#: The output of the task
3838
self.output = result.get("result")
39+
40+
#: What the function wrote to stdout
41+
self.stdout = result.get("stdout")
42+
43+
#: What the function wrote to stderr
44+
self.stderr = result.get("stderr")

docs/quickstart.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ To call the function using Arca, the following example would do so:
4040
print(result.output)
4141
4242
The code would print ``Hello World!``.
43-
``result`` would be a :class:`Result <arca.Result>` instance which currently only has one attribute,
44-
``output``, with the output of the function call.
43+
44+
``result`` would be a :class:`Result <arca.Result>` instance. :class:`Result <arca.Result>` has three attributes,
45+
``output`` with the return value of the function call, ``stdout`` and ``stderr`` contain things printed to the standard outputs.
4546
If the task fails, :class:`arca.exceptions.BuildError` would be raised.
4647

4748
By default, the :ref:`Current Environment Backend <backends_cur>` is used to run tasks,

docs/tasks.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ When a task exceeds a timeout, :class:`arca.exceptions.BuildTimeoutError` is rai
7272
Result
7373
------
7474

75+
The output of a task is stored and returned in a :class:`arca.Result` instance.
7576
Anything that's json-serializable can be returned from the entrypoints.
76-
Unfortunately, due to the way tasks are being launched by various backends, the entrypoints **must not** print anything,
77-
they can only return values.
77+
The :class:`arca.Result` instances contain three attributes.
78+
``output`` contains the value returned from the entrypoint.
79+
``stdout`` and ``stderr`` contain things written to the standard outputs.

tests/common.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,16 @@ def return_str_function():
6464
time.sleep(2)
6565
return "Some string"
6666
"""
67+
68+
PRINTING_FUNCTION = """
69+
import sys
70+
71+
def func():
72+
print("Printed to stdout")
73+
sys.stdout.write("Written to stdout")
74+
75+
print("Printed to stderr", file=sys.stderr)
76+
sys.stderr.write("Written to stderr")
77+
78+
return 1
79+
"""

tests/test_runner.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import pytest
66

77
import arca._runner as runner
8-
from arca import Task, Result
8+
from arca import Task, Result, Arca, CurrentEnvironmentBackend
9+
from common import PRINTING_FUNCTION
910

1011

1112
@pytest.mark.parametrize("definition", [
@@ -130,3 +131,18 @@ def func(カ):
130131
assert Result(output).output == result
131132

132133
file.unlink()
134+
135+
136+
def test_output(temp_repo_func):
137+
arca = Arca(backend=CurrentEnvironmentBackend(current_environment_requirements=None,
138+
requirements_strategy="ignore"))
139+
140+
temp_repo_func.file_path.write_text(PRINTING_FUNCTION)
141+
temp_repo_func.repo.index.add([str(temp_repo_func.file_path)])
142+
temp_repo_func.repo.index.commit("Initial")
143+
144+
result = arca.run(temp_repo_func.url, temp_repo_func.branch, Task("test_file:func"))
145+
146+
assert result.output == 1
147+
assert result.stdout == "Printed to stdout\nWritten to stdout"
148+
assert result.stderr == "Printed to stderr\nWritten to stderr"

0 commit comments

Comments
 (0)