[WIP] Add a new method Session.run_capture() which allows to capture stdout and stderr separately, even while showing the result - #1124
Conversation
|
Edit: ahh, sorry, too many tabs open, got comments mixed up. I tried something new here, I had Claude Fable plan out what it would do, but base it on this PR, so it has two comments you can look at. Should I push them here or make a PR to your fork so you can let me know what you think? |
|
I guess both is fine for me; whatever you prefer. Pushing here has the advantage of all discussion happening here (and not also in PRs in other repos.) |
| Read a stream chunk by chunk, append it to ``out_buffer``, and write it to ``out_stream``. | ||
| """ | ||
| while True: | ||
| chunk = await stream.readline() |
There was a problem hiding this comment.
stream.readline() will fail if the line is too long. See ansible-community/antsibull-core@d910be2.
There was a problem hiding this comment.
Thanks, I've re-implemented that here in 3c6064f. The main difference to your implementation is that it has a lot of comments that explain what exactly is happening, without having to resort to the docs or asyncio.streams' sources.
e19afd9 to
a909bd2
Compare
|
I tried to debug the failing tests a bit on a Macbook; there the tests pass if the tests are run sequentially (without I don't have access to Windows, so no idea how to work around the |
… and stderr separately, even while showing the result.
Co-authored-by: Maxwell G <maxwell@gtmx.me>
This fixes issues with the tests (no more "unclosed event loops" errors), and probably allows tee_popen() to run in parallel.
Session.run_capture()'s interface is similar toSession.run(), except:tuple[str, str, int](unless the command isn't run, then it returnsNone) with return code, stdout, and stderr;silent=Trueis specified and the program exits abnormally, stderr is shown; ifsilent=False(default), all stdout and stderr is shown in near real-time (similar totee, you catch the output while the user can still see it);success_all: boolparameter which, when set toTrue, does not fail on any return code; that allows the caller to completely handle the return code, without having to provide an exhaustive list of possible return codes forsuccess_codes.The implementation of
silent=Falsewas somewhat tricky. I found https://stackoverflow.com/questions/5045771/python-how-to-prevent-subprocesses-from-receiving-ctrl-c-control-c-sigint and the resulting https://github.com/pycontribs/subprocess-tee/blob/main/src/subprocess_tee/__init__.py implementation, but that didn't work as I expected in particular with respect toKeyboardInterrupt. I had to add a SIGINT signal handler and cancel tasks myself. (By default SIGINT kills the event loop, and not evenfinallyblocks in tasks are handled... You can only catchKeyboardInterruptcompletely outside theloop.run_until_complete()call.)I also considered not using asyncio, but selecting on stdout/stderr, i.e. basically reimplementing
Popen.communicate(). But then I looked closer in howPopen.communicate()is implemented in the standard library, and decided it's way too complex to try to do it this way, especially when it also has to work on Windows.This is a work in progress since it doesn't have tests yet. Also I only did a few tests on Linux (I don't have access to Windows, but can do some manual testing on macOS later). Before I spent even more time I'm also curious on whether this has a chance of being merged :)
Fixes #1053.