Fix Command-Query Separation violations (#118) - #144
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PR separates allowlist enforcement from hook collection, preserves asynchronous observe-hook tasks, improves stream shutdown after downstream closure, and adds locked maturin build diagnostics plus CI dependency preparation. ChangesCQRS execution and stream refactor
Native wheel build and coverage preparation
Sequence Diagram(s)sequenceDiagram
participant SafeCmd
participant StageObservation
participant ObserveHooks
participant PendingTasks
participant PipelineFinalisation
SafeCmd->>StageObservation: emit execution event
StageObservation->>ObserveHooks: invoke observe hooks
ObserveHooks-->>StageObservation: return scheduled tasks or emission error
StageObservation->>PendingTasks: retain scheduled tasks
PipelineFinalisation->>PendingTasks: await and clear tasks
sequenceDiagram
participant UpstreamReader
participant PumpStream
participant DownstreamWriter
UpstreamReader->>PumpStream: provide chunks
PumpStream->>DownstreamWriter: write and drain chunks
DownstreamWriter-->>PumpStream: report CLOSED on pipe failure
PumpStream->>UpstreamReader: bounded drain to EOF or timeout
PumpStream->>DownstreamWriter: close writer once
Possibly related PRs
Suggested reviewers: Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (19 passed)
📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Reviewer's GuideRefactors three helper functions to enforce clear Command-Query Separation, introduces a semantic write outcome enum for stream pumping, reshapes observability task scheduling to avoid out-parameters, and adds focused unit tests and minor documentation/test comment updates. Sequence diagram for allowlist enforcement and hook collectionsequenceDiagram
participant Run as run
participant Enforce as _enforce_allowlist
participant Collect as _collect_hooks
participant Ctx as current_context
Run->>Enforce: _enforce_allowlist(self)
Enforce->>Ctx: current_context().check_allowed(program)
Ctx-->>Enforce: None
Enforce-->>Run: None
Run->>Ctx: current_context()
Ctx-->>Run: CuprumContext
Run->>Collect: _collect_hooks(CuprumContext)
Collect-->>Run: _ExecutionHooks
Run->>Run: _ExecutionTracking(execution_hooks, pending_tasks)
Sequence diagram for exec event emission without out-parameterssequenceDiagram
participant Emit as emit
participant EmitExec as _emit_exec_event
participant Hook as ExecHook
participant Wait as _wait_for_exec_hook_tasks
Emit->>EmitExec: _emit_exec_event(hooks, event)
loop for each ExecHook
EmitExec->>Hook: hook(event)
Hook-->>EmitExec: result
alt [result is awaitable]
EmitExec->>EmitExec: asyncio.create_task(_await_awaitable(result))
end
end
EmitExec-->>Emit: list[asyncio.Task]
Emit->>Emit: pending_tasks.extend(list[asyncio.Task])
Emit->>Wait: _wait_for_exec_hook_tasks(pending_tasks)
Wait-->>Emit: None
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
b4a8705 to
cb3355b
Compare
cb3355b to
778661d
Compare
778661d to
d43d8ee
Compare
d43d8ee to
f8cc2fc
Compare
577964d to
36edd6d
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cuprum/_pipeline_internals.py`:
- Around line 256-276: Update _drain_tasks_during_cleanup to use a single-line
docstring summarizing its cleanup behavior. Expand the existing # noqa: BLE001
on the BaseException handler with an inline rationale explaining that all task
failures, including non-Exception BaseException values, must be aggregated
without masking active_error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 346046b2-f048-42e3-99ff-45aff6de2c1a
📒 Files selected for processing (2)
cuprum/_pipeline_internals.pycuprum/unittests/test_cqrs_hook_behaviour.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/shared-actions(auto-detected)leynos/pylint-pypy-shim(auto-detected)leynos/whitaker(auto-detected)
Reduce the `_drain_tasks_during_cleanup` docstring to a single-line summary and move the aggregation rationale next to the `# noqa: BLE001` handler, documenting that every task failure — including non-Exception BaseException values — must be aggregated with `active_error` so cleanup never masks the error that triggered it. No behaviour changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`test_maturin_wheel_build_snapshot` fails every scheduled mutmut run: `sys.executable -m maturin build` dies with "Unable to find `maturin` script" before any mutants are generated, aborting the whole baseline. The `maturin` PyPI package resolves its own compiled binary by walking each `sysconfig` scheme's `scripts` directory keyed off the running interpreter's `sys.prefix`, not `sys.path` or `PATH`. Under mutmut's `uv run --with mutmut==3.6.0` overlay, `sys.prefix` points at a temporary environment layered on top of the project's own virtualenv: the `maturin` module imports fine (via `sys.path`, so `cargo`/`rustc` and `toolchain_available()` all report success), but the overlay never received maturin's script, so the lookup comes up empty. Add `maturin_script_locatable()` to `tests/helpers/maturin.py`, mirroring maturin's own lookup, and skip the wheel-build test with a precise reason when it reports the script unreachable. In a normal virtualenv (CI, `build-wheels.yml`, local `uv run pytest`) `sys.prefix` matches the install location, the script is found, and the real build still runs — only the layered mutmut overlay skips. Add unit tests pinning the new detector's true/false behaviour by faking the `sysconfig` scheme lookup. Rebasing onto main brought in `MaturinBuildError` (#144), which — combined with the new `maturin_script_locatable()` detector — pushed `tests/helpers/maturin.py` past pylint's 400-line module limit. Extract the wheel-artifact snapshot parsers (`wheel_build_snapshot` and its private helpers) into a new sibling module `tests/helpers/maturin_wheel.py`, and re-export `wheel_build_snapshot` from `tests/helpers/maturin.py` so existing import sites are unchanged.
`test_maturin_wheel_build_snapshot` fails every scheduled mutmut run: `sys.executable -m maturin build` dies with "Unable to find `maturin` script" before any mutants are generated, aborting the whole baseline. The `maturin` PyPI package resolves its own compiled binary by walking each `sysconfig` scheme's `scripts` directory keyed off the running interpreter's `sys.prefix`, not `sys.path` or `PATH`. Under mutmut's `uv run --with mutmut==3.6.0` overlay, `sys.prefix` points at a temporary environment layered on top of the project's own virtualenv: the `maturin` module imports fine (via `sys.path`, so `cargo`/`rustc` and `toolchain_available()` all report success), but the overlay never received maturin's script, so the lookup comes up empty. Add `maturin_script_locatable()` to `tests/helpers/maturin.py`, mirroring maturin's own lookup, and skip the wheel-build test with a precise reason when it reports the script unreachable. In a normal virtualenv (CI, `build-wheels.yml`, local `uv run pytest`) `sys.prefix` matches the install location, the script is found, and the real build still runs — only the layered mutmut overlay skips. Add unit tests pinning the new detector's true/false behaviour by faking the `sysconfig` scheme lookup. Rebasing onto main brought in `MaturinBuildError` (#144), which — combined with the new `maturin_script_locatable()` detector — pushed `tests/helpers/maturin.py` past pylint's 400-line module limit. Extract the wheel-artifact snapshot parsers (`wheel_build_snapshot` and its private helpers) into a new sibling module `tests/helpers/maturin_wheel.py`, and re-export `wheel_build_snapshot` from `tests/helpers/maturin.py` so existing import sites are unchanged.
`test_maturin_wheel_build_snapshot` fails every scheduled mutmut run: `sys.executable -m maturin build` dies with "Unable to find `maturin` script" before any mutants are generated, aborting the whole baseline. The `maturin` PyPI package resolves its own compiled binary by walking each `sysconfig` scheme's `scripts` directory keyed off the running interpreter's `sys.prefix`, not `sys.path` or `PATH`. Under mutmut's `uv run --with mutmut==3.6.0` overlay, `sys.prefix` points at a temporary environment layered on top of the project's own virtualenv: the `maturin` module imports fine (via `sys.path`, so `cargo`/`rustc` and `toolchain_available()` all report success), but the overlay never received maturin's script, so the lookup comes up empty. Add `maturin_script_locatable()` to `tests/helpers/maturin.py`, mirroring maturin's own lookup, and skip the wheel-build test with a precise reason when it reports the script unreachable. In a normal virtualenv (CI, `build-wheels.yml`, local `uv run pytest`) `sys.prefix` matches the install location, the script is found, and the real build still runs — only the layered mutmut overlay skips. Add unit tests pinning the new detector's true/false behaviour by faking the `sysconfig` scheme lookup. Rebasing onto main brought in `MaturinBuildError` (#144), which — combined with the new `maturin_script_locatable()` detector — pushed `tests/helpers/maturin.py` past pylint's 400-line module limit. Extract the wheel-artifact snapshot parsers (`wheel_build_snapshot` and its private helpers) into a new sibling module `tests/helpers/maturin_wheel.py`, and re-export `wheel_build_snapshot` from `tests/helpers/maturin.py` so existing import sites are unchanged.
) * Skip the maturin wheel build when its script is unreachable (#211) `test_maturin_wheel_build_snapshot` fails every scheduled mutmut run: `sys.executable -m maturin build` dies with "Unable to find `maturin` script" before any mutants are generated, aborting the whole baseline. The `maturin` PyPI package resolves its own compiled binary by walking each `sysconfig` scheme's `scripts` directory keyed off the running interpreter's `sys.prefix`, not `sys.path` or `PATH`. Under mutmut's `uv run --with mutmut==3.6.0` overlay, `sys.prefix` points at a temporary environment layered on top of the project's own virtualenv: the `maturin` module imports fine (via `sys.path`, so `cargo`/`rustc` and `toolchain_available()` all report success), but the overlay never received maturin's script, so the lookup comes up empty. Add `maturin_script_locatable()` to `tests/helpers/maturin.py`, mirroring maturin's own lookup, and skip the wheel-build test with a precise reason when it reports the script unreachable. In a normal virtualenv (CI, `build-wheels.yml`, local `uv run pytest`) `sys.prefix` matches the install location, the script is found, and the real build still runs — only the layered mutmut overlay skips. Add unit tests pinning the new detector's true/false behaviour by faking the `sysconfig` scheme lookup. Rebasing onto main brought in `MaturinBuildError` (#144), which — combined with the new `maturin_script_locatable()` detector — pushed `tests/helpers/maturin.py` past pylint's 400-line module limit. Extract the wheel-artifact snapshot parsers (`wheel_build_snapshot` and its private helpers) into a new sibling module `tests/helpers/maturin_wheel.py`, and re-export `wheel_build_snapshot` from `tests/helpers/maturin.py` so existing import sites are unchanged. * Document the maturin_script_locatable native-wheel skip boundary (#211) Record the scope and reuse policy for the shared `maturin_script_locatable()` detector in the developers' guide, as the abstraction/helper policy in AGENTS.md requires. The helper now decides when the native-wheel build contract is skipped, so document: - how it mirrors maturin's own `sysconfig`-scheme script lookup keyed off `sys.prefix`, and why that is deliberately narrower than `toolchain_available()`'s importability probe; - the layered/ephemeral interpreter case (the `uv run --with mutmut` overlay) where the two checks disagree and the skip is genuine, versus normal virtualenvs where the real build still runs; - the reuse policy: tests that shell out to `python -m maturin build` should gate on both `toolchain_available()` and `maturin_script_locatable()`; import-only or pin/metadata tests should not; extend the helper in place rather than re-deriving the scan. Also note that the wheel-artefact snapshot parsers now live in `tests/helpers/maturin_wheel.py` (re-exported from `tests/helpers/maturin.py`), matching the module split. * Type the wheel snapshot payload and pin stem-matching (#211) Replace the `dict[str, typ.Any]` returns in `tests/helpers/maturin_wheel.py` with `TypedDict`s describing the fixed snapshot shape: `WheelBuildSnapshot` (`generator`, `metadata`, `wheel`, `entries`), plus `WheelMetadata` and `WheelHeaders` for the nested payloads. Indexing `snapshot_payload["generator"]` at the `test_maturin_wheel_build_snapshot` call site is now statically typed without `Any`; `TypedDict` is a plain dict at runtime, so the syrupy snapshot comparison is unchanged. Also pin the deliberate stem-based, recursive matching in `_script_named_maturin_exists` with a regression test. maturin's own `get_maturin_path` walks the scripts directory with `os.walk` and compares `os.path.splitext(f)[0]` against `"maturin"`, so it accepts any extension at any depth; `maturin.exe` is the real launcher on the `windows-2022` wheel target. Narrowing the match to an exact top-level `maturin` filename would diverge from maturin and make the probe report unavailable on Windows, silently skipping the native-wheel contract there. * Add launcher-discovery assertion messages and Oxford spelling (#211) Give both remaining bare assertions in the maturin launcher-discovery tests descriptive failure messages naming the expected outcome, matching the style already used by the Windows-launcher test and the rest of the module. The assertion conditions are unchanged. Switch the `-ised` docstring prose in `tests/helpers/maturin_wheel.py` to the `-ized` Oxford endings the documentation style guide requires (`docs/documentation-style-guide.md`, en-GB-oxendict), matching the `_normalise_wheel_entry` docstring that already read "Normalize". Identifiers keep their existing spelling, so `_normalise_wheel_entry` and the `normalised` loop variable are untouched. * Assert on missing wheel METADATA and align docstrings (#211) `wheel_build_snapshot` derived the `.dist-info/METADATA` entry name from the WHEEL entry by string substitution and passed it straight to `ZipFile.read`, which raises `KeyError` for an absent member. That contradicted the documented `Raises` contract, which advertises only `AssertionError`, `OSError`, and `zipfile.BadZipFile`. Check archive membership first and raise `AssertionError`, matching the existing `_locate_dist_info_wheel` precedent for a missing WHEEL entry, and cover it with `test_wheel_build_snapshot_rejects_wheel_without_metadata`. Bring the module's docstrings into line with the docstring-structure policy in `docs/developers-guide.md`, which requires comprehensive NumPy-style docstrings for public symbols and prefers single-line summaries for private helpers: - add Parameters and Returns sections to `wheel_build_snapshot`, and widen its AssertionError entry to cover the new METADATA check; - document every field of the public `WheelMetadata`, `WheelHeaders`, and `WheelBuildSnapshot` TypedDicts with Attributes sections; - reduce `_locate_dist_info_wheel` and `_parse_wheel_header` to single-line summaries, dropping sections that only restated their signatures; - add a Returns section to `maturin_script_locatable`, keeping its explanatory prose, which records genuinely non-obvious behaviour. --------- Co-authored-by: leynos <leynos@rohga>
Summary
This branch fixes three Command-Query Separation violations flagged by the refactoring audit — helpers that read as a command or a query but did the opposite, or both.
Closes #118.
_run_before_hooks(cmd)was documented as "collect hooks" (a query) but its real job was thectx.check_allowed(...)side effect. It is split into_enforce_allowlist(cmd)(a command) and_collect_hooks(ctx)(a pure query)._emit_exec_eventappended scheduled tasks to a caller-suppliedpending_tasksout-parameter; it now returns the scheduled tasks and the caller extends its own collection._write_to_stream_writerwas a "write" command whoseStreamWriter | Nonereturn was load-bearing control flow; it now returns a semantic_WriteOutcome(OPEN/CLOSED) and leaves writer ownership and closure to the caller.Review walkthrough
_enforce_allowlist/_collect_hookssplit, then cuprum/sh.py and cuprum/_process_lifecycle.py for the updated call sites (enforce, then collect)._WriteOutcomeenum and the revised_pump_streamloop that keeps draining after downstream close.Validation
make check-fmt: passmake lint: passmake typecheck: passmake test: pass (626 passed, 45 skipped; Rust suite 4 passed)coderabbit review --agent: 0 findingsNotes
The wheel-build snapshot gains the new test module.
_collect_hooksreadscurrent_context()once per call site rather than once per command; behaviour is unchanged because the context is constant within each synchronous build step.Summary by Sourcery
Enforce clearer command-query separation in pipeline execution helpers and stream handling, and update observability, tests, and docs accordingly.
Enhancements:
Documentation:
Tests:
References