Enhance SafeCmd.run: error handling, sinks, and decoding - #9
Conversation
…ss run - Introduce stdout_sink and stderr_sink in ExecutionContext to allow redirection of echoed subprocess output to custom text streams. - Add encoding and errors options to ExecutionContext for decoding subprocess output with configurable character encoding and error handling. - Refactor internal stream consumption to use _StreamConfig dataclass for unified stream handling. - Update _write_chunk to respect encoding and error parameters. - Ensure run(echo=True) respects and writes to provided sinks instead of default sys.stdout/stderr. - Add tests covering stderr-only capture, echoing to custom sinks, and decoding with configured encoding/errors. - Update user guide to document new ExecutionContext options controlling subprocess output handling. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Summary by CodeRabbitRelease Notes
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughThe cuprum library's subprocess execution context has been refactored to introduce unified stream configuration. ExecutionContext gained encoding, error handling, and custom sink parameters. Subprocess I/O handling now uses a coordinated _StreamConfig mechanism for stdout/stderr, with enhanced process termination and improved decoding consistency across streams. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (3)
🧰 Additional context used📓 Path-based instructions (11)docs/**/*.md📄 CodeRabbit inference engine (AGENTS.md)
Files:
docs/users-guide.md📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.md📄 CodeRabbit inference engine (AGENTS.md)
Files:
⚙️ CodeRabbit configuration file
Files:
docs/**/*.{md,mdx,rst,txt}📄 CodeRabbit inference engine (docs/documentation-style-guide.md)
Files:
docs/**/*.{md,mdx,rst,txt,rs}📄 CodeRabbit inference engine (docs/documentation-style-guide.md)
Files:
docs/**/*.{md,mdx}📄 CodeRabbit inference engine (docs/documentation-style-guide.md)
Files:
docs/{users-guide.md,**/RELEASE*,**/CHANGELOG*}📄 CodeRabbit inference engine (docs/roadmap.md)
Files:
**/*.py📄 CodeRabbit inference engine (AGENTS.md)
Files:
⚙️ CodeRabbit configuration file
Files:
**/unittests/test_*.py📄 CodeRabbit inference engine (.rules/python-00.md)
Files:
**/test_*.py📄 CodeRabbit inference engine (.rules/python-00.md)
Files:
**/*test*.py📄 CodeRabbit inference engine (.rules/python-exception-design-raising-handling-and-logging.md)
Files:
🧬 Code graph analysis (1)cuprum/unittests/test_safe_cmd_run.py (2)
🔍 Remote MCP RefBased on my search results, I now have sufficient context to provide a focused summary relevant to reviewing this PR. Summary of Additional Context for PR ReviewPython Error Handling ContextThe PR's default error handling strategy of "replace" is a standard Python codec error handler that replaces malformed data with a replacement marker (on decoding, uses Process Termination Best PracticesThe PR's hardening of process termination aligns with Python's asyncio subprocess behavior: on POSIX systems, Stream Configuration ArchitectureThe PR's approach of unifying stream handling through a Test Coverage ConsiderationsThe new test functions ( Backwards CompatibilityThe PR maintains backwards compatibility by preserving existing behavior through sensible defaults: UTF-8 encoding with "replace" error handling matches common subprocess expectations, and the new 🔇 Additional comments (10)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Comment |
Reviewer's GuideRefactors SafeCmd.run’s subprocess stream handling to support per-stream configuration (capture/echo, sinks, encoding/errors), hardens termination logic against missing processes, and documents/tests the new behavior while preserving default behavior. Sequence diagram for SafeCmd.run with per-stream config and cancellationsequenceDiagram
actor Caller
participant SafeCmd
participant ExecutionContext
participant Subprocess as asyncio_subprocess_exec
participant StdoutConsumer as _consume_stream_stdout
participant StderrConsumer as _consume_stream_stderr
participant Terminator as _terminate_process
Caller->>SafeCmd: run(capture, echo, context)
SafeCmd->>ExecutionContext: resolve ctx (env, sinks, encoding, errors)
SafeCmd->>Subprocess: create_subprocess_exec(argv_with_program, stdout, stderr, env, cwd)
Subprocess-->>SafeCmd: Process(process)
SafeCmd->>SafeCmd: build _StreamConfig from ctx
SafeCmd->>StdoutConsumer: create_task(_consume_stream(process.stdout, config_stdout))
SafeCmd->>StderrConsumer: create_task(_consume_stream(process.stderr, config_stderr))
par stream_consumers
StdoutConsumer->>StdoutConsumer: read, optionally capture
StdoutConsumer->>StdoutConsumer: echo via _write_chunk(stdout_sink, encoding, errors)
StderrConsumer->>StderrConsumer: read, optionally capture
StderrConsumer->>StderrConsumer: echo via _write_chunk(stderr_sink, encoding, errors)
end
alt normal_completion
SafeCmd->>Subprocess: wait()
Subprocess-->>SafeCmd: exit_code
SafeCmd->>StdoutConsumer: gather result
SafeCmd->>StderrConsumer: gather result
StdoutConsumer-->>SafeCmd: stdout_text
StderrConsumer-->>SafeCmd: stderr_text
SafeCmd-->>Caller: CommandResult(stdout, stderr, exit_code)
else cancellation
Caller--xSafeCmd: cancel task (CancelledError)
SafeCmd->>Terminator: _terminate_process(process, cancel_grace)
Terminator->>Subprocess: terminate()
Terminator->>Subprocess: wait with timeout
alt timeout
Terminator->>Subprocess: kill()
Subprocess-->>Terminator: exit
end
SafeCmd->>StdoutConsumer: gather(return_exceptions=True)
SafeCmd->>StderrConsumer: gather(return_exceptions=True)
SafeCmd-->>Caller: propagate CancelledError
end
Class diagram for updated SafeCmd.run stream handlingclassDiagram
class SafeCmd {
+list argv_with_program
+str program
+run(capture, echo, context) CommandResult
}
class ExecutionContext {
+_EnvMapping env
+_CwdType cwd
+float cancel_grace
+IO~str~ stdout_sink
+IO~str~ stderr_sink
+str encoding
+str errors
}
class _StreamConfig {
+bool capture_output
+bool echo_output
+IO~str~ sink
+str encoding
+str errors
}
class CommandResult {
+str program
+list argv
+int exit_code
+str stdout
+str stderr
}
class _consume_stream {
+_consume_stream(stream, config) str
}
class _write_chunk {
+_write_chunk(sink, chunk, encoding, errors) void
}
class _terminate_process {
+_terminate_process(process, grace_period) void
}
SafeCmd --> ExecutionContext : uses
SafeCmd --> CommandResult : returns
SafeCmd --> _StreamConfig : configures
SafeCmd ..> _consume_stream : calls
SafeCmd ..> _terminate_process : calls
_consume_stream ..> _write_chunk : calls
ExecutionContext --> _StreamConfig : provides defaults
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary
Changes
Core
_DEFAULT_ENCODING = "utf-8",_DEFAULT_ERROR_HANDLING = "replace".stdout_sinkandstderr_sink(text sinks for echoed output), defaulting tosys.stdout/sys.stderr.encodinganderrorsfor decoding subprocess output._StreamConfigdataclass to encapsulate per-stream decoding and echo behavior:capture_output,echo_output,sink,encoding,errors._StreamConfigfor both stdout and stderr consumption._consume_streamaccepts aconfigand respects per-stream capture, echo, and decoding settings.consumerslist andasyncio.gather._write_chunkto support custom encoding and error handling when echoing to sinks._terminate_processto safely handle missing processes on terminate/kill paths.Tests
cuprum/unittests/test_safe_cmd_run.py:test_run_captures_stderr_only– verifies capturing only stderr while stdout remains empty.test_run_captures_and_echoes_stderr– ensures stderr is captured and echoed to the terminal whenecho=True.test_run_echoes_to_custom_sinks– validates that echoed output can be directed to custom sinks provided viaExecutionContext.test_run_decodes_with_configured_encoding– confirms decoding uses configuredencodinganderrors(e.g., CP1252 with strict error handling).Docs
docs/users-guide.md:cancel_gracewording to describe termination vs. kill signals.stdout_sinkandstderr_sinkoptions for redirecting echoed output.encodinganderrorsoptions with defaults ("utf-8"and"replace").Why
Testing plan
echo=Truefor both stdout and stderr.Compatibility
ExecutionContextandSafeCmd.runparameters.🌿 Generated by Terry
ℹ️ Tag @terragon-labs to ask questions and address PR feedback
📎 Task: https://www.terragonlabs.com/task/39077fba-50c2-4c7a-911b-a04a8a1747d4
Summary by Sourcery
Enhance SafeCmd.run stream handling with configurable sinks, decoding, and more robust process termination behavior.
New Features:
Enhancements:
Documentation:
Tests: