Skip to content

Merge file and CLI rendering helpers#13

Merged
leynos merged 3 commits intomainfrom
codex/refactor-diagram-rendering-functions
Aug 7, 2025
Merged

Merge file and CLI rendering helpers#13
leynos merged 3 commits intomainfrom
codex/refactor-diagram-rendering-functions

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Aug 7, 2025

Summary

  • merge temporary file writing and mermaid CLI invocation into unified _render_diagram
  • simplify render_block to delegate rendering logic and report failures
  • replace unit tests to cover _render_diagram success and error paths

Testing

  • make fmt
  • make lint
  • make typecheck
  • make test

https://chatgpt.com/codex/tasks/task_e_6894edfc5bdc8322ba7765033e341b37

Summary by Sourcery

Unify file writing and Mermaid CLI invocation into a single helper, simplify render_block to delegate rendering and handle errors, and update unit tests to cover the new rendering workflow.

Enhancements:

  • Unify diagram file creation and Mermaid CLI invocation into a single _render_diagram helper
  • Simplify render_block to delegate rendering logic and handle errors without a verbose flag
  • Streamline CLI error reporting to display only formatted stderr messages
  • Remove redundant _write_diagram_file and _run_mermaid_cli functions

Tests:

  • Replace tests for file-writing and CLI helpers with new tests targeting _render_diagram for both success and failure scenarios

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Aug 7, 2025

Reviewer's Guide

This PR consolidates the temporary file creation and Mermaid CLI invocation into a single _render_diagram helper, refactors render_block to delegate to it with a simplified interface and unified error handling, and overhauls unit tests to exercise the new helper’s success and failure paths.

Class diagram for refactored rendering helpers

classDiagram
    class render_block {
        +async def render_block(block, tmpdir, cfg_path, path, idx, semaphore, timeout=30.0) bool
    }
    class _render_diagram {
        +async def _render_diagram(block, tmpdir, cfg_path, path, idx, sem, timeout) None
    }
    render_block --> _render_diagram : delegates rendering
Loading

Flow diagram for unified diagram rendering process

flowchart TD
    A[Start render_block] --> B[Call _render_diagram]
    B --> C[Write Mermaid block to .mmd file]
    C --> D[Invoke mermaid-cli to render SVG]
    D --> E{Success?}
    E -- Yes --> F[Return True]
    E -- No --> G[Handle error, print to stderr]
    G --> H[Return False]
Loading

File-Level Changes

Change Details Files
Consolidate file writing and CLI invocation into _render_diagram
  • Combine writing of .mmd file and invoking mermaid-cli into one function
  • Compute the CLI command with get_mmdc_cmd inside the helper
  • Use a semaphore guard and standard subprocess call to execute the command
  • Raise simplified RuntimeError using format_cli_error on failure
nixie/cli.py
Refactor render_block to delegate rendering and streamline error handling
  • Remove the _write_diagram_file and _run_mermaid_cli calls
  • Drop the verbose flag and manual command construction
  • Wrap _render_diagram in try/except to handle FileNotFoundError, RuntimeError, and unexpected errors uniformly
nixie/cli.py
Revise unit tests to target _render_diagram directly
  • Rename and update tests from _run_mermaid_cli to _render_diagram
  • Assert that the .mmd file is written, the correct CLI command is logged, and errors are raised appropriately
  • Remove tests for the now-deleted file-writing helper
nixie/unittests/test_render_diagram.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • Refactor

    • Improved error handling and formatting of error messages for better clarity.
    • Cleaned up code by removing unnecessary imports and exception handling.
  • Style

    • Minor formatting adjustments to improve code readability.

Walkthrough

Update exception handling in nixie/cli.py by switching from asyncio.TimeoutError to TimeoutError in wait_for_proc, reformat an error message in render_block to use an f-string, and remove the generic exception catch block. Remove a redundant blank line in the test file.

Changes

Cohort / File(s) Change Summary
Exception Handling & Formatting Updates
nixie/cli.py
Change caught exception from asyncio.TimeoutError to TimeoutError; reformat FileNotFoundError message to use f-string; remove generic Exception catch block and traceback printing.
Test Formatting Cleanup
nixie/unittests/test_render_diagram.py
Remove a trailing blank line after the future import; no functional changes.

Sequence Diagram(s)

No sequence diagram generated as the changes do not introduce or alter control flow or feature interactions.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Possibly related PRs

Poem

A tweak to the errors, a message made neat,
With f-strings and comments, the code is complete.
No logic was shifted, no features were bent,
Just polish and tidying—review time well spent!
🧹✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/refactor-diagram-rendering-functions

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `nixie/cli.py:140` </location>
<code_context>
-            f"{format_cli_error(stderr.decode('utf-8', errors='replace'))}"
-        )
-        raise RuntimeError(error_message)
+        raise RuntimeError(format_cli_error(stderr.decode("utf-8", errors="replace")))


</code_context>

<issue_to_address>
Loss of context in error message compared to previous implementation.

Including the command, file, and diagram index in the error message would improve debugging and troubleshooting.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        raise RuntimeError(format_cli_error(stderr.decode("utf-8", errors="replace")))
=======
        error_message = (
            f"Command '{cmd}' failed for file '{path}' (diagram index {idx}):\n"
            f"{format_cli_error(stderr.decode('utf-8', errors='replace'))}"
        )
        raise RuntimeError(error_message)
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `nixie/cli.py:110` </location>
<code_context>
-async def _run_mermaid_cli(
-    cmd: list[str],
-    semaphore: asyncio.Semaphore,
+async def _render_diagram(
+    block: str,
+    tmpdir: Path,
</code_context>

<issue_to_address>
Consider splitting file writing and CLI invocation into separate helpers to improve single-responsibility and testability.

Consider restoring single-responsibility and testability by pulling file-I/O and CLI invocation back out of `_render_diagram`. For example:

```python
def _prepare_diagram_paths(block: str, tmpdir: Path, path: Path, idx: int) -> tuple[Path, Path]:
    mmd = tmpdir / f"{path.stem}_{idx}.mmd"
    svg = mmd.with_suffix(".svg")
    mmd.write_text(block)
    return mmd, svg

async def _invoke_mermaid(
    cmd: list[str],
    sem: asyncio.Semaphore,
    path: Path,
    idx: int,
    timeout: float,
) -> None:
    logging.getLogger(__name__).info(shlex.join(cmd))
    async with sem:
        proc = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        success, stderr = await wait_for_proc(proc, path, idx, timeout)
        if not success:
            raise RuntimeError(format_cli_error(stderr.decode("utf-8", errors="replace")))
```

Then simplify `_render_diagram` to just:

```python
async def _render_diagram(
    block: str,
    tmpdir: Path,
    cfg_path: Path,
    path: Path,
    idx: int,
    sem: asyncio.Semaphore,
    timeout: float,
) -> None:
    mmd, svg = _prepare_diagram_paths(block, tmpdir, path, idx)
    cmd = get_mmdc_cmd(mmd, svg, cfg_path)
    await _invoke_mermaid(cmd, sem, path, idx, timeout)
```

This keeps each helper tiny (and easy to test), reduces the overall signature surface in each function, and preserves exactly the same behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread nixie/cli.py Outdated
Comment thread nixie/cli.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
nixie/cli.py (1)

152-174: Simplified error handling improves maintainability.

The updated render_block function properly delegates to _render_diagram and handles exceptions appropriately. The error reporting is clear and user-friendly.

However, address the broad exception handling as flagged by static analysis.

The broad Exception catch on line 169 violates best practices. Replace with specific exception types or document why broad catching is necessary:

-    except Exception as exc:  # pragma: no cover - unexpected
+    except Exception as exc:  # pragma: no cover - FIXME: narrow exception types
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 906f11d and ca0214c.

📒 Files selected for processing (3)
  • nixie/cli.py (4 hunks)
  • nixie/unittests/test_render_diagram.py (3 hunks)
  • nixie/unittests/test_write_diagram_file.py (0 hunks)
💤 Files with no reviewable changes (1)
  • nixie/unittests/test_write_diagram_file.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit Inference Engine (AGENTS.md)

For Python files: Pass all relevant unit and behavioral tests, pass lint checks (ruff check), adhere to formatting standards (ruff format), and pass type checking (pyright).

**/*.py: Use snake_case.py for file names, naming files for their contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names in Python files
Use snake_case for variable and function names in Python files
Use UPPER_SNAKE_CASE for module-level constants in Python files
Prefix private/internal helpers or APIs with a single underscore (_) in Python files
Use typing everywhere and maintain full static type coverage in Python files; use Pyright for type-checking
Use TypedDict or Dataclass for structured data where appropriate; for internal-only usage, prefer @DataClass(slots=True)
Avoid Any in type annotations; use Unknown, generics, or cast() when necessary, and always document why Any is acceptable if used
Be explicit with return types in public functions and class methods (e.g., use -> None, -> str, etc.)
Favor immutability in Python files; prefer tuples over lists, and frozendict or types.MappingProxyType where appropriate
Enable Ruff for linting and formatting; use Ruff to lint for performance, security, consistency, and style issues, and let Ruff handle whitespace and formatting entirely
Enforce strict mode in Pyright and treat all Pyright warnings as CI errors; use # pyright: ignore sparingly and with explanation
Avoid side effects at import time in Python modules; modules should not modify global state or perform actions on import
Use docstrings to document public functions, classes, and modules using NumPy format
Explain tricky code with inline comments for non-obvious logic or decisions in Python files

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, especially for resource management (files, locks, connections, etc.), instead of manual try/finally blocks.
Use @contextmanager from contextlib for straightforward proce...

Files:

  • nixie/unittests/test_render_diagram.py
  • nixie/cli.py

⚙️ CodeRabbit Configuration File

**/*.py: How about the following?

- Keep cyclomatic complexity ≤ 12
- Follow single responsibility and CQRS (command/query segregation)
- Docstrings must follow the `numpy` style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
- Move conditionals with >2 branches to predicate/helper functions
- Avoid `eval`, `exec`, `pickle`, monkey-patching, `ctypes`, unsafe shell
- Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
- Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar
- Lint suppressions:
  - Blanket `# noqa`, file-level skips, and categories are **forbidden**
  - Only narrow in-line disables (`# noqa: XYZ`) are permitted, and must be accompanied by `FIXME:` or a ticket link, and used only as a last resort.
- Use `pytest` fixtures for shared setup (`conftest.py` or `fixtures/`)
- Replace duplicate tests with `@pytest.mark.parametrize`
- Prefer `pytest-mock` or `unittest.mock` for stubs/mocks
- Use `assert …, "message"` over bare asserts
- Reflect all API/behaviour changes in `docs/` and update roadmap on completion
- Files must not exceed 400 logical lines:
  - Decompose large modules into subpackages
  - Split large `match/case` or dispatch tables by domain and collocate with targets if appropriate
  - Move bulky data (fixtures, templates) to external files for parsing at runtime
- Mutable defaults and shadowed built-ins are forbidden
- All code must have clear type hints using modern style (`A | B`, `list[str]`, `class Foo[A]:`, `type Bar = int`, etc.), with ABC imports drawn from the correct stdlib module.

Files:

  • nixie/unittests/test_render_diagram.py
  • nixie/cli.py
**/unittests/test_*.py

📄 CodeRabbit Inference Engine (.rules/python-00.mdc)

Colocate unit tests with code using a unittests subdirectory and a test_ prefix for test files

Files:

  • nixie/unittests/test_render_diagram.py
**/test_*.py

📄 CodeRabbit Inference Engine (.rules/python-00.mdc)

**/test_*.py: Use pytest idioms in test files: prefer fixtures over setup/teardown methods, parametrize broadly, and avoid unnecessary mocks
Group related tests using class with method names prefixed by test_ in test files

Files:

  • nixie/unittests/test_render_diagram.py
🧬 Code Graph Analysis (1)
nixie/unittests/test_render_diagram.py (1)
nixie/cli.py (3)
  • cli (262-269)
  • _render_diagram (110-140)
  • get_mmdc_cmd (62-79)
🪛 Ruff (0.12.2)
nixie/unittests/test_render_diagram.py

49-49: Trailing comma missing

Add trailing comma

(COM812)

nixie/cli.py

117-117: Async function definition with a timeout parameter

(ASYNC109)


163-164: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


169-169: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (5)
nixie/unittests/test_render_diagram.py (3)

10-10: LGTM on import update.

The import correctly reflects the refactored API by importing _render_diagram instead of the previous separate functions.


14-44: Test correctly validates the consolidated rendering workflow.

The test properly validates that _render_diagram writes the Mermaid block to a file and logs the CLI command. The dynamic construction of expected file paths and command verification is well-implemented.


48-74: Test correctly validates error handling in consolidated workflow.

The failure test properly verifies that _render_diagram raises RuntimeError with formatted CLI error output when the subprocess fails. The simplified error message assertions align with the refactored error handling.

nixie/cli.py (2)

6-6: LGTM on subprocess import addition.

The explicit import of asyncio.subprocess is appropriate for the subprocess management in _render_diagram.


110-141: Excellent consolidation of rendering workflow.

The _render_diagram function effectively consolidates file writing and CLI invocation, reducing parameter surface area and centralising the subprocess management. The function handles file creation, command construction, logging, and error formatting in a cohesive manner.

Note: The static analysis warning about async timeout parameters (ASYNC109) can be safely ignored here as the timeout is properly used with asyncio.wait_for() in the wait_for_proc function.

Comment thread nixie/cli.py Outdated
Comment thread nixie/unittests/test_render_diagram.py
leynos and others added 2 commits August 8, 2025 00:09
Consolidate temporary file creation and mermaid CLI invocation into _render_diagram, simplifying render_block. Update tests accordingly.
@leynos leynos force-pushed the codex/refactor-diagram-rendering-functions branch from ca0214c to 5656e78 Compare August 7, 2025 23:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (3)
nixie/cli.py (3)

109-117: Catch asyncio.TimeoutError (or both) to ensure timeout handling and process cleanup.

asyncio.wait_for raises asyncio.TimeoutError. Catching only built-in TimeoutError risks missing the timeout on some Python versions, leaving the spawned process alive and skipping the intended kill()/wait() path.

Apply this diff:

-    except TimeoutError:
+    except (asyncio.TimeoutError, TimeoutError):

166-174: Hold the semaphore for the entire CLI execution to actually cap concurrency.

Releasing the semaphore before wait_for_proc allows unlimited concurrent CLI processes; it only gates process creation, not execution. Gate the full lifecycle.

Apply this diff:

-    async with sem:
-        proc = await asyncio.create_subprocess_exec(  # nosemgrep: python.lang.security.audit.dangerous-asyncio-create-exec-audit
-            *cmd,
-            stdout=asyncio.subprocess.PIPE,
-            stderr=asyncio.subprocess.PIPE,
-        )
-
-    success, stderr = await wait_for_proc(proc, path, idx, timeout)
+    async with sem:
+        proc = await asyncio.create_subprocess_exec(  # nosemgrep: python.lang.security.audit.dangerous-asyncio-create-exec-audit
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
+        )
+        success, stderr = await wait_for_proc(proc, path, idx, timeout)

268-271: Apply the same BLE001 resolution to the top-level catch in main.

This is another intentional boundary; either suppress narrowly with rationale or narrow the exception set. Keep behaviour consistent with render_block.

Example (keep and justify):

-            except Exception as exc:  # pragma: no cover - unexpected
+            except Exception as exc:  # pragma: no cover - unexpected  # noqa: BLE001 - CLI boundary: fail file, keep process running
                 print(f"Validation task raised an exception: {exc}")
                 success = False
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca0214c and 5656e78.

📒 Files selected for processing (2)
  • nixie/cli.py (2 hunks)
  • nixie/unittests/test_render_diagram.py (0 hunks)
💤 Files with no reviewable changes (1)
  • nixie/unittests/test_render_diagram.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit Inference Engine (AGENTS.md)

For Python files: Pass all relevant unit and behavioral tests, pass lint checks (ruff check), adhere to formatting standards (ruff format), and pass type checking (pyright).

**/*.py: Use snake_case.py for file names, naming files for their contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names in Python files
Use snake_case for variable and function names in Python files
Use UPPER_SNAKE_CASE for module-level constants in Python files
Prefix private/internal helpers or APIs with a single underscore (_) in Python files
Use typing everywhere and maintain full static type coverage in Python files; use Pyright for type-checking
Use TypedDict or Dataclass for structured data where appropriate; for internal-only usage, prefer @DataClass(slots=True)
Avoid Any in type annotations; use Unknown, generics, or cast() when necessary, and always document why Any is acceptable if used
Be explicit with return types in public functions and class methods (e.g., use -> None, -> str, etc.)
Favor immutability in Python files; prefer tuples over lists, and frozendict or types.MappingProxyType where appropriate
Enable Ruff for linting and formatting; use Ruff to lint for performance, security, consistency, and style issues, and let Ruff handle whitespace and formatting entirely
Enforce strict mode in Pyright and treat all Pyright warnings as CI errors; use # pyright: ignore sparingly and with explanation
Avoid side effects at import time in Python modules; modules should not modify global state or perform actions on import
Use docstrings to document public functions, classes, and modules using NumPy format
Explain tricky code with inline comments for non-obvious logic or decisions in Python files

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, especially for resource management (files, locks, connections, etc.), instead of manual try/finally blocks.
Use @contextmanager from contextlib for straightforward proce...

Files:

  • nixie/cli.py

⚙️ CodeRabbit Configuration File

**/*.py: How about the following?

- Keep cyclomatic complexity ≤ 12
- Follow single responsibility and CQRS (command/query segregation)
- Docstrings must follow the `numpy` style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
- Move conditionals with >2 branches to predicate/helper functions
- Avoid `eval`, `exec`, `pickle`, monkey-patching, `ctypes`, unsafe shell
- Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
- Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar
- Lint suppressions:
  - Blanket `# noqa`, file-level skips, and categories are **forbidden**
  - Only narrow in-line disables (`# noqa: XYZ`) are permitted, and must be accompanied by `FIXME:` or a ticket link, and used only as a last resort.
- Use `pytest` fixtures for shared setup (`conftest.py` or `fixtures/`)
- Replace duplicate tests with `@pytest.mark.parametrize`
- Prefer `pytest-mock` or `unittest.mock` for stubs/mocks
- Use `assert …, "message"` over bare asserts
- Reflect all API/behaviour changes in `docs/` and update roadmap on completion
- Files must not exceed 400 logical lines:
  - Decompose large modules into subpackages
  - Split large `match/case` or dispatch tables by domain and collocate with targets if appropriate
  - Move bulky data (fixtures, templates) to external files for parsing at runtime
- Mutable defaults and shadowed built-ins are forbidden
- All code must have clear type hints using modern style (`A | B`, `list[str]`, `class Foo[A]:`, `type Bar = int`, etc.), with ABC imports drawn from the correct stdlib module.

Files:

  • nixie/cli.py
🪛 Ruff (0.12.2)
nixie/cli.py

220-220: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (1)
nixie/cli.py (1)

213-217: Use f-string for error message — LGTM.

Maintain this style for consistency with modern Python and Ruff’s UP031 rule.

Comment thread nixie/cli.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
nixie/cli.py (1)

165-173: Hold the semaphore for the full subprocess lifecycle

Enforce the concurrency limit across the entire CLI execution. As written, the semaphore is released immediately after spawn, so the number of concurrently running mmdc processes is effectively unbounded.

-    async with sem:
-        proc = await asyncio.create_subprocess_exec(  # nosemgrep: python.lang.security.audit.dangerous-asyncio-create-exec-audit
-            *cmd,
-            stdout=asyncio.subprocess.PIPE,
-            stderr=asyncio.subprocess.PIPE,
-        )
-
-    success, stderr = await wait_for_proc(proc, path, idx, timeout)
+    async with sem:
+        proc = await asyncio.create_subprocess_exec(  # nosemgrep: python.lang.security.audit.dangerous-asyncio-create-exec-audit
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
+        )
+        success, stderr = await wait_for_proc(proc, path, idx, timeout)
♻️ Duplicate comments (1)
nixie/cli.py (1)

265-267: Either justify or remove blanket except Exception in main (Ruff BLE001)

Treat this as a deliberate CLI boundary or remove it. If keeping it to ensure robust CLI UX, add a narrow suppression with rationale to satisfy linting.

-            except Exception as exc:  # pragma: no cover - unexpected
+            except Exception as exc:  # pragma: no cover - unexpected  # noqa: BLE001 - CLI boundary: log-and-continue by design
                 print(f"Validation task raised an exception: {exc}")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5656e78 and bb13e4e.

📒 Files selected for processing (1)
  • nixie/cli.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit Inference Engine (AGENTS.md)

For Python files: Pass all relevant unit and behavioral tests, pass lint checks (ruff check), adhere to formatting standards (ruff format), and pass type checking (pyright).

**/*.py: Use snake_case.py for file names, naming files for their contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names in Python files
Use snake_case for variable and function names in Python files
Use UPPER_SNAKE_CASE for module-level constants in Python files
Prefix private/internal helpers or APIs with a single underscore (_) in Python files
Use typing everywhere and maintain full static type coverage in Python files; use Pyright for type-checking
Use TypedDict or Dataclass for structured data where appropriate; for internal-only usage, prefer @DataClass(slots=True)
Avoid Any in type annotations; use Unknown, generics, or cast() when necessary, and always document why Any is acceptable if used
Be explicit with return types in public functions and class methods (e.g., use -> None, -> str, etc.)
Favor immutability in Python files; prefer tuples over lists, and frozendict or types.MappingProxyType where appropriate
Enable Ruff for linting and formatting; use Ruff to lint for performance, security, consistency, and style issues, and let Ruff handle whitespace and formatting entirely
Enforce strict mode in Pyright and treat all Pyright warnings as CI errors; use # pyright: ignore sparingly and with explanation
Avoid side effects at import time in Python modules; modules should not modify global state or perform actions on import
Use docstrings to document public functions, classes, and modules using NumPy format
Explain tricky code with inline comments for non-obvious logic or decisions in Python files

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, especially for resource management (files, locks, connections, etc.), instead of manual try/finally blocks.
Use @contextmanager from contextlib for straightforward proce...

Files:

  • nixie/cli.py

⚙️ CodeRabbit Configuration File

**/*.py: How about the following?

- Keep cyclomatic complexity ≤ 12
- Follow single responsibility and CQRS (command/query segregation)
- Docstrings must follow the `numpy` style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
- Move conditionals with >2 branches to predicate/helper functions
- Avoid `eval`, `exec`, `pickle`, monkey-patching, `ctypes`, unsafe shell
- Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
- Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar
- Lint suppressions:
  - Blanket `# noqa`, file-level skips, and categories are **forbidden**
  - Only narrow in-line disables (`# noqa: XYZ`) are permitted, and must be accompanied by `FIXME:` or a ticket link, and used only as a last resort.
- Use `pytest` fixtures for shared setup (`conftest.py` or `fixtures/`)
- Replace duplicate tests with `@pytest.mark.parametrize`
- Prefer `pytest-mock` or `unittest.mock` for stubs/mocks
- Use `assert …, "message"` over bare asserts
- Reflect all API/behaviour changes in `docs/` and update roadmap on completion
- Files must not exceed 400 logical lines:
  - Decompose large modules into subpackages
  - Split large `match/case` or dispatch tables by domain and collocate with targets if appropriate
  - Move bulky data (fixtures, templates) to external files for parsing at runtime
- Mutable defaults and shadowed built-ins are forbidden
- All code must have clear type hints using modern style (`A | B`, `list[str]`, `class Foo[A]:`, `type Bar = int`, etc.), with ABC imports drawn from the correct stdlib module.

Files:

  • nixie/cli.py
🔇 Additional comments (1)
nixie/cli.py (1)

214-214: Good: modernise message formatting with an f-string

Adopt f-strings for the CLI-not-found error. This aligns with Ruff UP031 and improves readability.

Comment thread nixie/cli.py
try:
_, stderr = await asyncio.wait_for(proc.communicate(), timeout)
except asyncio.TimeoutError:
except TimeoutError:
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix timeout exception class to catch asyncio timeouts

Catch asyncio.TimeoutError (raised by asyncio.wait_for), not the built-in TimeoutError. The current clause will fail to intercept timeouts, changing behaviour and leaking processes until the outer scope handles the exception.

-    except TimeoutError:
+    except asyncio.TimeoutError:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except TimeoutError:
except asyncio.TimeoutError:
🤖 Prompt for AI Agents
In nixie/cli.py at line 110, replace the built-in TimeoutError exception with
asyncio.TimeoutError to correctly catch timeouts raised by asyncio.wait_for.
Import asyncio if not already imported, and update the except clause to except
asyncio.TimeoutError to prevent unhandled timeout exceptions and potential
process leaks.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai new GitHub issue, please

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos I'll create a GitHub issue for the timeout exception fix.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --title "Fix timeout exception handling in wait_for_proc function" \
  --body "## Description

The \`wait_for_proc\` function in \`nixie/cli.py\` currently catches the built-in \`TimeoutError\` instead of \`asyncio.TimeoutError\`. This prevents proper handling of timeouts raised by \`asyncio.wait_for\`, potentially leading to unhandled exceptions and process leaks.

## Expected Behaviour
The function should catch \`asyncio.TimeoutError\` to properly handle timeouts from \`asyncio.wait_for\`.

## Current Behaviour
The function catches the built-in \`TimeoutError\`, which will not intercept timeouts from \`asyncio.wait_for\`.

## Fix
Change line 110 from:
\`\`\`python
except TimeoutError:
\`\`\`

to:
\`\`\`python
except asyncio.TimeoutError:
\`\`\`

## References
- PR: https://github.com/leynos/nixie/pull/13
- Comment: https://github.com/leynos/nixie/pull/13#discussion_r2261636969

Requested by: @leynos" \
  --assignee leynos

Length of output: 959


@leynos I've successfully created GitHub issue #15 for the timeout exception handling fix. The issue includes:

  • Clear description of the problem with the incorrect exception type
  • Expected vs current behaviour
  • The specific code change needed
  • References to this PR and comment thread
  • Assignment to you as requested

You can view the issue at: #15


🧬 Code Graph Analysis Results

Relevant snippet from nixie/unittests/test_get_mmdc_cmd.py (lines 22-25)

def which(cmd: str) -> str | None:
        if cmd == "bun":
            return "/usr/bin/bun"
        return None

This snippet defines a mock which function that returns a path for the "bun" command and None otherwise. It is relevant because the nixie/cli.py file uses shutil.which to detect the availability of CLI executables (mmdc, bun, npx) in the get_mmdc_cmd function. This mock could be part of tests that simulate the presence or absence of these executables.

@leynos leynos merged commit bd6c5d4 into main Aug 7, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant