Skip to content

Fix lint issues in version tools - #4

Merged
leynos merged 8 commits into
mainfrom
codex/fix-lint-errors-in-bump_version.py
Oct 3, 2025
Merged

Fix lint issues in version tools#4
leynos merged 8 commits into
mainfrom
codex/fix-lint-errors-in-bump_version.py

Conversation

@leynos

@leynos leynos commented Oct 2, 2025

Copy link
Copy Markdown
Owner

Summary

  • adjust type imports, raw docstrings, and markdown error handling in the version bump script to satisfy linting
  • document pytest tests, update parametrization, and split long literals to align with docstring and pytest lint rules
  • clean up publish helpers by removing blank lines and adopting modern isinstance union syntax

Testing

  • make lint
  • make test

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

Summary by Sourcery

Refactor version bump and publish scripts and associated tests to resolve lint errors by standardizing imports and type hints, adopting modern syntax, normalizing docstrings, and cleaning up test definitions.

Enhancements:

  • Update type annotations and imports to use typing.TYPE_CHECKING with a collections.abc alias and PEP 604 union syntax
  • Convert docstrings to raw-string (r"""...") format
  • Extract inline markdown update error handling into a dedicated helper function and replace os.replace with Path.replace
  • Remove extraneous blank lines across scripts

Tests:

  • Add descriptive docstrings to pytest tests, rename parameters, employ pytest.param, and split long literals to satisfy lint rules

Summary by CodeRabbit

  • Refactor
    • Improved type safety, safer file updates, and centralized markdown-update warnings for more reliable version and content updates.
  • Tests
    • Expanded coverage for dependency/version updates and markdown handling, including quote/comment/indentation preservation and failure logging.
  • Chores
    • Type-checking updated to Python 3.13, tooling scripts included in checks, test dirs excluded from type checks, and packaging/import resolution simplified.

@sourcery-ai

sourcery-ai Bot commented Oct 2, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR addresses lint errors across the version bump and publish helper scripts by adjusting type and abc imports, converting docstrings to raw strings, extracting Markdown update error handling, enhancing pytest tests, and adopting modern union syntax.

Sequence diagram for extracted Markdown update error handling in bump_version.py

sequenceDiagram
    participant main
    participant _warn_on_markdown_update_failure
    participant _update_markdown_versions
    participant sys.stderr
    main->>_warn_on_markdown_update_failure: Call with md_path, version
    _warn_on_markdown_update_failure->>_update_markdown_versions: Try to update Markdown
    _update_markdown_versions-->>_warn_on_markdown_update_failure: Exception (TOMLKitError, OSError, TypeError, ValueError)
    _warn_on_markdown_update_failure->>sys.stderr: Print warning message
Loading

Class diagram for updated type usage in bump_version.py

classDiagram
    class bump_version {
        +_is_matching_fence_token(tok: Token, lang: str) bool
        +_process_fence_token(tok: Token, lines: list[str], lang: str, replace_fn: cabc.Callable[[str], str]) str
        +replace_fences(md_text: str, lang: str, replace_fn: cabc.Callable[[str], str]) str
        +_update_package_version(doc: cabc.MutableMapping[str, object], version: str) None
        +_extract_version_prefix(entry: tomlkit.items.String | cabc.Mapping[str, object] | str | None) str
        +_update_dict_dependency(entry: cabc.MutableMapping[str, object], version: str) None
        +_update_string_dependency(deps: cabc.MutableMapping[str, object], dependency: str, entry: tomlkit.items.String | str, version: str) None
        +_update_dependency_in_table(deps: cabc.MutableMapping[str, object], dependency: str, version: str) None
        +_update_dependency_version(doc: cabc.MutableMapping[str, object], dependency: str, version: str) None
        +_set_version(toml_path: Path, version: str, dependency: str | None = None, doc: cabc.MutableMapping[str, object] | None = None) None
        +_warn_on_markdown_update_failure(md_path: Path, version: str) None
        +main(argv: list[str]) int
    }
    bump_version ..> "cabc.Callable" : uses
    bump_version ..> "cabc.MutableMapping" : uses
    bump_version ..> "cabc.Mapping" : uses
    bump_version ..> "Token" : uses
    bump_version ..> "Path" : uses
    bump_version ..> "tomlkit.items.String" : uses
Loading

Class diagram for updated union syntax in publish_patch.py

classDiagram
    class publish_patch {
        +extract_existing_items(value: object) tuple[tuple[str, object], ...]
    }
    publish_patch ..> "Table" : uses
    publish_patch ..> "InlineTable" : uses
Loading

File-Level Changes

Change Details Files
Standardize type imports and abc usage in bump_version
  • Guard typing-only imports with TYPE_CHECKING
  • Replace direct os.replace with Path.replace
  • Switch Mapping, MutableMapping, Callable to cabc equivalents
  • Annotate tok parameter with Token
crate_tools/bump_version.py
Convert docstrings to raw string literals
  • Prefix multiline docstrings with r"""
  • Maintain examples and formatting in raw docstrings
crate_tools/bump_version.py
Extract Markdown update error handling to helper
  • Introduce _warn_on_markdown_update_failure helper
  • Replace inline try/except in main with helper call
crate_tools/bump_version.py
Document and refactor pytest tests
  • Add docstrings to test functions
  • Rename parametrization variables for clarity
  • Split long literals across lines to satisfy lint rules
crate_tools/unittests/test_bump_version.py
Modernize isinstance union syntax in publish helpers
  • Remove extraneous blank lines
  • Replace isinstance checks on tuples with A
B syntax

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

coderabbitai Bot commented Oct 2, 2025

Copy link
Copy Markdown

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.

Walkthrough

Makefile's typecheck target now runs ty check with Python 3.13, adds an --extra-search-path crate_tools, and passes a CRATE_TOOLS_SCRIPTS list. Multiple crate_tools modules were refactored for stronger typing, package-relative imports, safer TOML/Markdown updates, atomic file replacement, and accompanying test and pyright exclude updates.

Changes

Cohort / File(s) Summary
Build & Type-checking
Makefile, pyproject.toml
Added CRATE_TOOLS_SCRIPTS = $(sort $(wildcard crate_tools/*.py)); typecheck now runs ty check --python-version 3.13 --extra-search-path crate_tools $(CRATE_TOOLS_SCRIPTS). pyproject.toml adds tool.pyright excludes for crate_tools/unittests and **/unittests.
Version bump tool
crate_tools/bump_version.py
Introduced TYPE_CHECKING imports, TomlMapping/TomlMutableMapping aliases, extensive type annotations, logging with _warn_on_markdown_update_failure, stricter guards (raise/skip when fence tokens lack mapping), TOML string cloning helpers (_infer_string_type, _clone_string_with_value), and atomic file replacement via Path(...).replace(...).
Publish tooling (typing & imports)
crate_tools/publish_patch.py, crate_tools/publish_workspace_dependencies.py, crate_tools/publish_workspace_members.py, crate_tools/run_publish_check.py
publish_patch.py: switched to type-only cabc import with runtime fallback, added runtime type checks/casts for Table/InlineTable. publish_workspace_dependencies.py: conditional/package-relative import of publish_patch. publish_workspace_members.py: removed explicit cast when returning rebuilt members. run_publish_check.py: added TYPE_CHECKING and package-relative imports, adjusted App init (assign config after init), and renamed parameter usages from workspace_root to workspace.
Tests
crate_tools/unittests/test_bump_version.py, crate_tools/unittests/test_publish_workspace_dependencies.py
test_bump_version.py: added TOMLKitError import and tests for markdown warning behavior, quote/trailing-comment/indentation preservation, and workspace-managed dependency behavior; exposes _warn_on_markdown_update_failure. test_publish_workspace_dependencies.py: updated imports to from crate_tools import publish_workspace_dependencies as dependencies.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Make as Makefile (typecheck)
  participant Ty as ty
  participant Scripts as crate_tools/*.py

  Dev->>Make: make typecheck
  Make->>Ty: ty check --python-version 3.13 --extra-search-path crate_tools $(CRATE_TOOLS_SCRIPTS)
  Ty-->>Make: Type-check results
  Make-->>Dev: Report
Loading
sequenceDiagram
  autonumber
  actor User
  participant BV as bump_version.py
  participant TK as tomlkit
  participant FS as Filesystem
  participant Warn as _warn_on_markdown_update_failure
  participant Log as logger

  User->>BV: run update
  BV->>TK: parse TOML (typed/cast)
  BV->>BV: process fence tokens (raise/skip if mapping missing)
  BV->>FS: write temp file
  BV->>FS: Path(temp_name).replace(...) for atomic swap
  alt Markdown update raises TOMLKitError
    BV->>Warn: call helper
    Warn->>Log: emit warning
  else success
    BV-->>User: files updated
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • codescene-delta-analysis

Poem

I hop through code with careful paws,
Guarding tokens, types, and clause.
Atomic swaps and warnings light,
Imports tidy, tests polite.
A rabbit hums — the repo’s bright 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit's high-level summary is enabled.
Title Check ✅ Passed The title succinctly states the primary purpose of the changeset, which is addressing lint issues across the version bump and publishing helper scripts, and it avoids generic or off-topic language while remaining clear and concise.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.
✨ 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/fix-lint-errors-in-bump_version.py

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cfde917 and f8ab631.

📒 Files selected for processing (2)
  • crate_tools/bump_version.py (17 hunks)
  • crate_tools/unittests/test_bump_version.py (9 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*.py: Python files must use snake_case filenames (e.g., http_client.py, task_queue.py)
Classes must use PascalCase
Variables and functions must use snake_case
Module-level constants must use UPPER_SNAKE_CASE
Prefix non-exported helpers or internal APIs with a single leading underscore
Use typing everywhere and maintain full static type coverage
Use TypedDict or @DataClass for structured data; prefer @DataClass(slots=True) for internal-only usage
Avoid Any; prefer precise types (TypeVar, Protocol, Literal, Union); use typing.cast only when necessary with justification; use object for unknown opaque values
Be explicit with return types (e.g., -> None, -> str) for all public functions and methods
Favor immutability: prefer tuples to lists and MappingProxyType for read-only mappings; document third-party frozendict if used
Use # pyright: ignore sparingly and include an explanation comment when used
Avoid side effects at import time (no global state mutation or actions on import)
Use docstrings (NumPy format) for public functions, classes, and modules
Explain tricky code with inline comments

**/*.py: Prefer context managers to encapsulate setup/teardown for resources (files, locks, connections) instead of manual try/finally blocks
Use contextlib.contextmanager (@contextmanager) to implement simple, linear setup/teardown context managers
Implement a class with enter and exit for context managers that require internal state or more complex lifecycle handling
For file I/O, prefer with open(...) as ... over open()/try/finally/close patterns
Choose @contextmanager when control flow is linear and no persistent state is needed
Choose a class-based context manager when there is internal state, lifecycle methods, re-entry, or advanced context needs

**/*.py: Exception classes must end with the suffix 'Error' (N818)
Prefer specific built-ins (e.g., TypeError, ValueError) or domain exceptions over raising Exception directly (TRY003/TRY004)
Preserve causal chains...

Files:

  • crate_tools/unittests/test_bump_version.py
  • crate_tools/bump_version.py
**/unittests/test_*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

Colocate unit tests with code in an unittests/ subdirectory using test_ prefix

Files:

  • crate_tools/unittests/test_bump_version.py
{**/unittests/test_*.py,tests/**/*.py}

📄 CodeRabbit inference engine (.rules/python-00.md)

{**/unittests/test_*.py,tests/**/*.py}: Use pytest idioms: prefer fixtures over setup/teardown, parametrize broadly, avoid unnecessary mocks
Group related tests using classes with method names prefixed by test_
Write tests from a user's perspective: test public behaviour, not internals

Files:

  • crate_tools/unittests/test_bump_version.py
🧬 Code graph analysis (1)
crate_tools/unittests/test_bump_version.py (1)
crate_tools/bump_version.py (4)
  • _update_dependency_version (297-333)
  • _update_markdown_versions (589-614)
  • _warn_on_markdown_update_failure (617-627)
  • replace_version_in_toml (553-583)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (26)
crate_tools/unittests/test_bump_version.py (10)

1-14: LGTM! Clean imports and module setup.

The module docstring, imports, and setup are well-structured. The addition of TOMLKitError and _warn_on_markdown_update_failure imports properly supports the expanded test coverage.


17-41: LGTM! Improved parametrization format.

The explicit tuple syntax in the parametrization decorator follows pytest best practices and improves clarity.


44-78: LGTM! Clear and descriptive test docstrings.

The updated docstrings effectively communicate the purpose of each test case and follow the project's documentation standards.


80-130: Improved parametrization, but PR objective partially unresolved.

The use of pytest.param with descriptive IDs and clearer parameter names (md_text, expected_text) is an improvement. However, the PR objectives indicated that this test should be simplified by passing the expected change directly rather than computing should_change from the outcome parameter. While the current implementation is clearer than before, the original suggestion to further simplify the parameterization remains unaddressed.

Based on PR objectives summary.


132-152: LGTM! Addresses past review feedback.

This test effectively verifies that _warn_on_markdown_update_failure logs exceptions with tracebacks when Markdown updates fail. The use of monkeypatch and caplog follows pytest best practices.

This resolves the past review comment requesting a test for markdown update failure warnings.


154-170: LGTM! Complementary success case test.

This test ensures that successful Markdown updates don't emit spurious logs, providing good coverage alongside the failure case.


173-197: LGTM! Enhanced test coverage for comment preservation.

The addition of inline comments in the test TOML and the more specific line-by-line assertions improve the test's ability to catch regressions in trivia preservation.


200-218: LGTM! Consistent parametrization format.

The explicit tuple syntax matches the pattern used in other tests, maintaining consistency across the test suite.


221-233: LGTM! Clear docstring update.

The docstring accurately describes the test's purpose and the expected behavior.


236-253: LGTM! Important indentation preservation test.

The test ensures that nested TOML fences maintain their indentation, which is critical for Markdown readability. The docstring clearly communicates this intent.

crate_tools/bump_version.py (16)

1-41: LGTM! Strong typing foundation and proper imports.

The module setup demonstrates excellent adherence to coding guidelines:

  • Standard aliases (cabc, typ) improve consistency
  • Type aliases (TomlMapping, TomlMutableMapping) reduce verbosity
  • TYPE_CHECKING guard prevents runtime import costs
  • Module-level logger follows best practices

As per coding guidelines.


43-59: LGTM! Proper type annotation.

The addition of the Token type annotation improves type safety while maintaining the function's clarity.


75-110: LGTM! Defensive validation and improved typing.

The addition of explicit validation for tok.map prevents potential None access errors downstream. The use of ValueError with a clear message follows exception handling guidelines. Type annotations with Token and cabc.Callable strengthen type safety.

As per coding guidelines.


113-152: LGTM! Consistent type annotations and defensive checks.

The type annotations align with the updated _process_fence_token signature, and the additional tok.map is None check ensures robustness when skipping tokens without positional data.


155-173: Excellent use of structural pattern matching!

The dict destructuring pattern elegantly handles both workspace-nested and direct package versions without nested isinstance checks. This implementation is declarative, type-safe, and leverages Python 3.10+ features effectively.

Based on PR objectives and coding guidelines.


176-195: LGTM! Comprehensive type handling.

The function correctly handles all tomlkit value types while preserving prefix detection logic. The type cast is appropriate given the runtime check.


198-220: Excellent trivia preservation helpers!

These helpers are critical for maintaining TOML formatting during updates:

  • _infer_string_type systematically determines quoting style
  • _clone_string_with_value preserves all trivia attributes (comments, whitespace, indentation)

The comment explaining the tomlkit 0.13+ stability rationale is helpful. This approach aligns with tomlkit best practices for style-preserving TOML updates.

Based on learnings.


223-246: LGTM! Safe string updates with trivia preservation.

The use of _clone_string_with_value ensures that version updates maintain the original formatting, quoting, and inline comments.


249-270: LGTM! Consistent trivia preservation.

The function correctly handles both tomlkit String and plain str types while preserving formatting when possible.


273-294: LGTM! Clean delegation with proper type checking.

The function correctly uses cabc.Mapping for type checking and delegates to the appropriate helper function.

As per coding guidelines.


297-333: LGTM! Proper type annotation.

The addition of TomlMutableMapping type annotation improves type safety while maintaining the function's correctness.


336-372: LGTM! Modern Path API usage.

The switch from os.replace to Path.replace is more idiomatic and maintains atomic file replacement guarantees.

As per coding guidelines.


440-489: LGTM! Appropriate raw docstring.

The raw docstring format is correct for examples containing escape sequences.


553-583: LGTM! Consistent documentation style.

The raw docstring format maintains consistency with other functions in the module.


617-627: Excellent error handling centralization!

The new _warn_on_markdown_update_failure helper properly:

  • Catches only expected exceptions (TOMLKitError, OSError)
  • Uses structured logging with logger.exception for full tracebacks
  • Follows parameterized logging guidelines (LOG004/LOG007)
  • Avoids masking unexpected errors like TypeError or ValueError

This resolves the past review comment about broad exception catching.

As per coding guidelines and past review feedback.


630-679: LGTM! Clean main flow with centralized error handling.

The main function now delegates Markdown update error handling to the dedicated helper, improving readability and maintainability.


Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

  • Consider introducing a type alias for repeated annotations like cabc.Mapping[str, object] to reduce verbosity and improve readability.
  • Might be better to use the logging module instead of print in _warn_on_markdown_update_failure so warnings integrate with existing logging configuration.
  • In test_update_markdown_versions_behavior you could parameterize the expected change directly instead of computing should_change from outcome to simplify the test logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider introducing a type alias for repeated annotations like cabc.Mapping[str, object] to reduce verbosity and improve readability.
- Might be better to use the logging module instead of print in _warn_on_markdown_update_failure so warnings integrate with existing logging configuration.
- In test_update_markdown_versions_behavior you could parameterize the expected change directly instead of computing should_change from outcome to simplify the test logic.

## Individual Comments

### Comment 1
<location> `crate_tools/bump_version.py:588-594` </location>
<code_context>
     md_path.write_text(updated, encoding="utf-8")


+def _warn_on_markdown_update_failure(md_path: Path, version: str) -> None:
+    """Emit a warning if a markdown update fails."""
+    try:
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Catching broad exceptions may mask unexpected errors.

Review whether TypeError and ValueError should be caught here, as handling them may obscure programming mistakes or unexpected input. Allowing some exceptions to propagate could improve error visibility during development.

```suggestion
    try:
        _update_markdown_versions(md_path, version)
    except (TOMLKitError, OSError) as exc:
        print(
            f"Warning: Failed to update {md_path}: {exc}",
            file=sys.stderr,
        )
```
</issue_to_address>

### Comment 2
<location> `crate_tools/unittests/test_bump_version.py:71` </location>
<code_context>


 def test_workspace_dependency_no_version_written() -> None:
+    """Skip adding explicit versions for workspace-managed dependencies."""
     doc = tomlkit.parse("[dependencies]\nfoo = { workspace = true }\n")
</code_context>

<issue_to_address>
**suggestion (testing):** Missing test for markdown update failure warning.

Add a test that triggers a markdown update failure and verifies that the warning is correctly emitted to stderr.

Suggested implementation:

```python
def test_workspace_dependency_no_version_written() -> None:
    """Skip adding explicit versions for workspace-managed dependencies."""
    doc = tomlkit.parse("[dependencies]\nfoo = { workspace = true }\n")
    _update_dependency_version(doc, "foo", "1.2.3")
    deps = doc["dependencies"]["foo"]

def test_markdown_update_failure_warning(capsys) -> None:
    """Emit a warning to stderr when markdown update fails."""
    # Simulate markdown update failure by calling the function with bad input
    # This assumes _update_markdown returns False or raises on failure
    # Replace with actual function and failure mode as needed
    try:
        result = _update_markdown("bad input", "1.2.3")
    except Exception:
        # If the function raises, emit warning manually
        print("WARNING: Failed to update markdown", file=sys.stderr)
    else:
        if not result:
            print("WARNING: Failed to update markdown", file=sys.stderr)
    captured = capsys.readouterr()
    assert "WARNING: Failed to update markdown" in captured.err

```

- Ensure that `_update_markdown` is imported or available in the test file.
- Adjust the failure simulation to match the actual behavior of your markdown update function (e.g., whether it returns False or raises an exception).
- If your warning message is different, update the string in the assertion and print statement accordingly.
</issue_to_address>

### Comment 3
<location> `crate_tools/bump_version.py:586` </location>
<code_context>
     md_path.write_text(updated, encoding="utf-8")


+def _warn_on_markdown_update_failure(md_path: Path, version: str) -> None:
+    """Emit a warning if a markdown update fails."""
+    try:
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for the new _warn_on_markdown_update_failure function.

The new function _warn_on_markdown_update_failure is added, but there are no corresponding behavioural or unit tests verifying its correct operation or error handling. Add tests to cover this new functionality.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</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 crate_tools/bump_version.py
Comment thread crate_tools/unittests/test_bump_version.py
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crate_tools/bump_version.py (1)

598-607: Broad exception catch may mask programming errors.

The helper function _warn_on_markdown_update_failure catches TypeError and ValueError in addition to TOMLKitError and OSError. While this prevents crashes, it may hide programming mistakes or unexpected input issues that should be surfaced during development.

Consider whether TypeError and ValueError should be caught here. These typically indicate programming errors rather than expected failures. You might want to:

  1. Remove TypeError and ValueError from the catch list to let programming errors surface
  2. Or, add specific handling that logs more details about what went wrong for debugging

Current code:

except (TOMLKitError, OSError, TypeError, ValueError) as exc:

Safer alternative:

except (TOMLKitError, OSError) as exc:

This matches the past review comment from Sourcery AI on the same concern.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ac367d0 and 1d87bd7.

📒 Files selected for processing (6)
  • Makefile (1 hunks)
  • crate_tools/bump_version.py (17 hunks)
  • crate_tools/publish_patch.py (4 hunks)
  • crate_tools/publish_workspace_dependencies.py (1 hunks)
  • crate_tools/unittests/test_bump_version.py (10 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*.py: Python files must use snake_case filenames (e.g., http_client.py, task_queue.py)
Classes must use PascalCase
Variables and functions must use snake_case
Module-level constants must use UPPER_SNAKE_CASE
Prefix non-exported helpers or internal APIs with a single leading underscore
Use typing everywhere and maintain full static type coverage
Use TypedDict or @DataClass for structured data; prefer @DataClass(slots=True) for internal-only usage
Avoid Any; prefer precise types (TypeVar, Protocol, Literal, Union); use typing.cast only when necessary with justification; use object for unknown opaque values
Be explicit with return types (e.g., -> None, -> str) for all public functions and methods
Favor immutability: prefer tuples to lists and MappingProxyType for read-only mappings; document third-party frozendict if used
Use # pyright: ignore sparingly and include an explanation comment when used
Avoid side effects at import time (no global state mutation or actions on import)
Use docstrings (NumPy format) for public functions, classes, and modules
Explain tricky code with inline comments

**/*.py: Prefer context managers to encapsulate setup/teardown for resources (files, locks, connections) instead of manual try/finally blocks
Use contextlib.contextmanager (@contextmanager) to implement simple, linear setup/teardown context managers
Implement a class with enter and exit for context managers that require internal state or more complex lifecycle handling
For file I/O, prefer with open(...) as ... over open()/try/finally/close patterns
Choose @contextmanager when control flow is linear and no persistent state is needed
Choose a class-based context manager when there is internal state, lifecycle methods, re-entry, or advanced context needs

**/*.py: Exception classes must end with the suffix 'Error' (N818)
Prefer specific built-ins (e.g., TypeError, ValueError) or domain exceptions over raising Exception directly (TRY003/TRY004)
Preserve causal chains...

Files:

  • crate_tools/publish_workspace_dependencies.py
  • crate_tools/publish_patch.py
  • crate_tools/bump_version.py
  • crate_tools/unittests/test_bump_version.py
pyproject.toml

📄 CodeRabbit inference engine (.rules/python-00.md)

pyproject.toml: Enable Ruff for linting (replacing flake8, isort, pyflakes, etc.) and configure it
Use Ruff as the project formatter; let Ruff handle all formatting
Configure tools (Ruff, Pyright, Pytest) via pyproject.toml
Enforce strict mode in Pyright

Configure Ruff to enforce TRY, BLE, EM, LOG, N818, PERF203, and B017 in pyproject.toml

pyproject.toml: Use the PEP 621 [project] table with at least name and version defined
Include description and readme in [project]; set readme to the README file path (e.g., README.md)
Set requires-python in [project] to declare supported Python versions (e.g., >=3.10)
Specify license in [project] using license = { text = "" } or license = { file = "LICENSE" }
Provide authors with name and email in [project].authors
Use keywords and valid Trove classifiers in [project]
Declare runtime dependencies in [project].dependencies using PEP 508 specifiers
Group non-runtime deps under [project.optional-dependencies] (e.g., dev, docs)
Define CLI entry points under [project.scripts] (e.g., mycli = "pkg.cli:main")
Define GUI entry points under [project.gui-scripts] when needed
Register plugin entry points under [project.entry-points.'group.name']
Declare a build system: [build-system] requires = ["setuptools>=61.0", "wheel"], build-backend = "setuptools.build_meta"
Set [tool.uv].package = true to ensure your project is built/installed on uv sync/run
If omitting [build-system], set [tool.uv].package = true so uv still builds/installs your package
Use semantic versioning (e.g., 1.2.3) for the [project].version value
Keep build constraints minimal; omit [build-system] if you don’t need editable installs
Prefer exact or bounded dependency ranges (e.g., requests>=2.25,<3.0) to avoid unexpected major bumps
Use dynamic fields (e.g., dynamic = ["version"]) sparingly and only if the build backend supports them

Files:

  • pyproject.toml
**/unittests/test_*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

Colocate unit tests with code in an unittests/ subdirectory using test_ prefix

Files:

  • crate_tools/unittests/test_bump_version.py
{**/unittests/test_*.py,tests/**/*.py}

📄 CodeRabbit inference engine (.rules/python-00.md)

{**/unittests/test_*.py,tests/**/*.py}: Use pytest idioms: prefer fixtures over setup/teardown, parametrize broadly, avoid unnecessary mocks
Group related tests using classes with method names prefixed by test_
Write tests from a user's perspective: test public behaviour, not internals

Files:

  • crate_tools/unittests/test_bump_version.py
🧬 Code graph analysis (2)
crate_tools/publish_workspace_dependencies.py (1)
crate_tools/publish_patch.py (1)
  • apply_replacements (61-125)
crate_tools/unittests/test_bump_version.py (1)
crate_tools/bump_version.py (2)
  • _update_dependency_version (278-314)
  • replace_version_in_toml (534-564)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (15)
pyproject.toml (1)

27-27: LGTM! Test directory exclusion is appropriate.

The exclusion of test directories from type checking aligns with the project structure where tests are colocated in unittests/ subdirectories. This is a standard practice and matches the coding guidelines.

crate_tools/publish_workspace_dependencies.py (1)

13-13: LGTM! Absolute import improves clarity.

The change from a relative import to an absolute, namespace-qualified import (from crate_tools.publish_patch import ...) improves code clarity and aligns with the module being part of the crate_tools package structure.

crate_tools/publish_patch.py (3)

20-24: Runtime fallback for TYPE_CHECKING alias is correct.

The pattern of providing a runtime fallback for the cabc alias when TYPE_CHECKING is False is correct. The cast to "type[object]" ensures that at runtime, cabc can be used as a namespace-like object without importing the actual collections.abc module.


175-182: Type checking with runtime validation is appropriate.

The addition of runtime type checking to ensure section_item is either a Table or InlineTable before casting to MutableMapping is correct. Raising SystemExit when the check fails maintains the existing error handling pattern.


265-273: Cast to bypass tomlkit's incomplete type definitions is pragmatic.

The type annotation on line 265 and the cast on lines 271-272 to set trailing_comma are necessary because tomlkit's InlineTable type doesn't expose this attribute in its type definition. This is a pragmatic workaround for incomplete library types.

crate_tools/bump_version.py (6)

24-35: LGTM! TYPE_CHECKING guard follows best practices.

The imports are correctly structured with from __future__ import annotations at the top, followed by typing as typ and collections.abc as cabc aliases, and a TYPE_CHECKING guard for Token. This matches the coding guidelines perfectly.

Based on coding guidelines.


91-94: Defensive runtime check improves robustness.

Adding a runtime guard to raise ValueError when tok.map is None prevents potential crashes downstream. This is good defensive coding practice.


139-140: Skip logic prevents crashes on malformed tokens.

The check if not _is_matching_fence_token(tok, lang) or tok.map is None: continue correctly skips tokens that don't match the language or lack mapping data, preventing potential crashes.


353-353: Using Path.replace aligns with pathlib guidelines.

Replacing os.replace with Path(temp_name).replace(toml_path) follows the PTH (pathlib) coding guideline and is more idiomatic modern Python.

Based on coding guidelines.


163-172: Type annotations and runtime checks are correctly implemented.

The function signature updates with cabc.MutableMapping[str, typ.Any] and the runtime isinstance checks for workspace and package being mappings before mutation are correct. This follows the pattern established in the coding guidelines and maintains type safety.


216-223: Type cast pattern for tomlkit compatibility is pragmatic.

The pattern of checking isinstance(existing, toml_items.String) and then using a cast to access the value setter (with fallback for tomlkit < 0.14) is a practical solution for handling tomlkit's evolving API while maintaining type safety.

crate_tools/unittests/test_bump_version.py (4)

1-13: LGTM! Test organization and imports follow guidelines.

The module docstring, typing imports, and test organization follow pytest best practices and coding guidelines.

Based on coding guidelines.


21-40: Improved parameter naming and test documentation.

The parameter tuple structure and added docstring make the test intent clearer. The parameterization covers both string and dict-style dependencies with version prefix preservation.


80-126: Enhanced test parameterization with semantic outcome parameter.

Using pytest.param with explicit id= values and the semantic outcome parameter (Literal["update", "preserve"]) significantly improves test readability and output. The logic should_change = outcome == "update" is clear and maintainable.


71-76: Consider adding test for markdown update failure warning.

The previous review comment suggested adding a test that verifies the warning is emitted to stderr when markdown update fails. This test validates the success case but doesn't verify the error handling behavior introduced by _warn_on_markdown_update_failure in bump_version.py.

Consider adding a test that triggers and validates the warning behavior. Example:

def test_markdown_update_failure_emits_warning(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Emit warning to stderr when markdown update fails."""
    # Create a markdown file with invalid TOML that will fail parsing
    md_path = tmp_path / "broken.md"
    md_path.write_text("```toml\n[invalid\n```\n")
    
    from crate_tools.bump_version import _warn_on_markdown_update_failure
    _warn_on_markdown_update_failure(md_path, "1.0.0")
    
    captured = capsys.readouterr()
    assert "Warning: Failed to update" in captured.err
    assert str(md_path) in captured.err

This would verify the error handling path added in lines 598-607 of bump_version.py.

Comment thread Makefile Outdated
Use logging-backed warnings, reduce repeated typing annotations with mapping aliases, and cover markdown failure handling with targeted tests.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
crate_tools/bump_version.py (3)

169-177: Consider extracting nested conditionals into a helper for clarity.

The nested isinstance checks for workspace and package could be extracted into a small helper function to reduce cognitive load and improve testability.

Apply this refactor:

+def _get_package_from_workspace(doc: TomlMutableMapping) -> cabc.MutableMapping[str, typ.Any] | None:
+    """Return the package mapping from workspace if present."""
+    workspace = doc.get("workspace")
+    if isinstance(workspace, cabc.MutableMapping):
+        package = workspace.get("package")
+        if isinstance(package, cabc.MutableMapping):
+            return package
+    return None
+
 def _update_package_version(
     doc: TomlMutableMapping,
     version: str,
 ) -> None:
-    workspace = doc.get("workspace")
-    if isinstance(workspace, cabc.MutableMapping):
-        package = workspace.get("package")
-        if isinstance(package, cabc.MutableMapping):
-            package["version"] = version
-            return
+    package = _get_package_from_workspace(doc)
+    if package is not None:
+        package["version"] = version
+        return
     package = doc.get("package")
     if isinstance(package, cabc.MutableMapping):
         package["version"] = version

222-229: Fallback for tomlkit < 0.14 uses private attribute.

Accessing existing._original (line 227) and entry._original (line 255) relies on tomlkit internals. This is a known compatibility shim but increases maintenance burden if tomlkit changes its internals.

Consider one of the following:

  1. Document the minimum supported tomlkit version (0.14+) and remove the fallback.
  2. Add a test to verify the fallback path works with the oldest supported tomlkit version.
  3. Wrap the private attribute access in a helper with clear documentation:
def _set_string_value(item: toml_items.String, value: str) -> None:
    """Set value on a tomlkit String item, handling version differences."""
    try:
        cast_item = typ.cast("typ.Any", item)
        cast_item.value = value
    except AttributeError:  # tomlkit <0.14 lacks value setter
        item._original = value  # pyright: ignore[reportPrivateUsage]

609-614: Consider using logger.exception for automatic traceback.

Using logger.warning with manual exception formatting works but loses the traceback. If debugging these failures is important, consider logger.exception or at least exc_info=True:

-        logger.warning(
-            "Failed to update Markdown fence versions in %s to %s: %s",
-            md_path,
-            version,
-            exc,
-        )
+        logger.warning(
+            "Failed to update Markdown fence versions in %s to %s",
+            md_path,
+            version,
+            exc_info=True,
+        )

This provides more diagnostic context without changing the log level.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d87bd7 and cc92bf6.

📒 Files selected for processing (2)
  • crate_tools/bump_version.py (17 hunks)
  • crate_tools/unittests/test_bump_version.py (9 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*.py: Python files must use snake_case filenames (e.g., http_client.py, task_queue.py)
Classes must use PascalCase
Variables and functions must use snake_case
Module-level constants must use UPPER_SNAKE_CASE
Prefix non-exported helpers or internal APIs with a single leading underscore
Use typing everywhere and maintain full static type coverage
Use TypedDict or @DataClass for structured data; prefer @DataClass(slots=True) for internal-only usage
Avoid Any; prefer precise types (TypeVar, Protocol, Literal, Union); use typing.cast only when necessary with justification; use object for unknown opaque values
Be explicit with return types (e.g., -> None, -> str) for all public functions and methods
Favor immutability: prefer tuples to lists and MappingProxyType for read-only mappings; document third-party frozendict if used
Use # pyright: ignore sparingly and include an explanation comment when used
Avoid side effects at import time (no global state mutation or actions on import)
Use docstrings (NumPy format) for public functions, classes, and modules
Explain tricky code with inline comments

**/*.py: Prefer context managers to encapsulate setup/teardown for resources (files, locks, connections) instead of manual try/finally blocks
Use contextlib.contextmanager (@contextmanager) to implement simple, linear setup/teardown context managers
Implement a class with enter and exit for context managers that require internal state or more complex lifecycle handling
For file I/O, prefer with open(...) as ... over open()/try/finally/close patterns
Choose @contextmanager when control flow is linear and no persistent state is needed
Choose a class-based context manager when there is internal state, lifecycle methods, re-entry, or advanced context needs

**/*.py: Exception classes must end with the suffix 'Error' (N818)
Prefer specific built-ins (e.g., TypeError, ValueError) or domain exceptions over raising Exception directly (TRY003/TRY004)
Preserve causal chains...

Files:

  • crate_tools/unittests/test_bump_version.py
  • crate_tools/bump_version.py
**/unittests/test_*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

Colocate unit tests with code in an unittests/ subdirectory using test_ prefix

Files:

  • crate_tools/unittests/test_bump_version.py
{**/unittests/test_*.py,tests/**/*.py}

📄 CodeRabbit inference engine (.rules/python-00.md)

{**/unittests/test_*.py,tests/**/*.py}: Use pytest idioms: prefer fixtures over setup/teardown, parametrize broadly, avoid unnecessary mocks
Group related tests using classes with method names prefixed by test_
Write tests from a user's perspective: test public behaviour, not internals

Files:

  • crate_tools/unittests/test_bump_version.py
🧬 Code graph analysis (1)
crate_tools/unittests/test_bump_version.py (1)
crate_tools/bump_version.py (4)
  • _update_dependency_version (284-320)
  • _update_markdown_versions (576-601)
  • _warn_on_markdown_update_failure (604-614)
  • replace_version_in_toml (540-570)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (17)
crate_tools/bump_version.py (7)

21-41: LGTM! Type imports and aliases follow guidelines.

The use of typing.TYPE_CHECKING to guard runtime-unnecessary imports, collections.abc aliasing as cabc, and type aliases (TomlMapping, TomlMutableMapping) align with the coding guidelines for type safety and PEP 585/604 conventions.


97-99: LGTM! Runtime guard improves robustness.

Adding an explicit check for tok.map is None with a clear error message prevents obscure failures downstream when fence tokens lack positional data.


145-145: LGTM! Dual guard aligns with token structure expectations.

Checking both _is_matching_fence_token and tok.map is None before processing ensures that only valid, fully populated fence tokens are transformed.


359-359: LGTM! Pathlib atomic replace is safer and more idiomatic.

Replacing os.replace(temp_name, toml_path) with Path(temp_name).replace(toml_path) is more Pythonic and type-safe while maintaining the same atomic semantics.


665-665: LGTM! Centralized error handling improves maintainability.

Using the extracted _warn_on_markdown_update_failure helper makes the main function cleaner and the error handling testable in isolation.


604-615: Allow ValueError to propagate
ValueError from _process_fence_token signals a programming bug (tokens without map are filtered out), so it should not be caught here.


195-199: Cast safety verified: tomlkit’s Document, Table, and InlineTable implement collections.abc.Mapping, so typ.cast("TomlMapping", entry) is safe; existing fallback covers tomlkit < 0.14.

crate_tools/unittests/test_bump_version.py (10)

1-14: LGTM! Imports support new test coverage.

Adding TOMLKitError and _warn_on_markdown_update_failure enables testing the error handling path introduced in the main file.


22-31: LGTM! Structured parametrize improves clarity.

Changing from "body, expected, extra" to ("body", "expected", "extra") follows pytest best practices for tuple-based parameter specifications.


35-35: LGTM! Descriptive docstring improves test documentation.

Adding docstrings to test functions aligns with the coding guideline to document public functions and improves test readability.


44-78: LGTM! Comprehensive edge case coverage.

The new tests for trailing comments (line 44), quote style (line 52), missing dependencies (line 62), and workspace dependencies (line 72) provide excellent coverage of formatting preservation and conditional logic.


83-100: LGTM! pytest.param with id improves test output.

Using pytest.param(..., id="toml-fence") makes test failures easier to identify in CI output compared to numeric indices.


135-155: LGTM! Test validates warning behavior.

This test addresses the past review comment requesting coverage of markdown update failure warnings. The monkeypatch approach cleanly isolates the error handling logic.


140-141: Pragma comment placement is correct.

The # pragma: no cover - helper comment is appropriate here since this is a test helper function that only exists to raise an exception for testing purposes.


157-174: LGTM! Validates silent success path.

Testing that no warnings are emitted on success is important for preventing log spam in normal operation.


239-257: LGTM! Indentation preservation is critical for nested fences.

This test ensures that the fence replacement logic correctly handles indented code blocks, which is important for documentation with nested examples.


176-201: Assertion indices verified. Updated -3/-2 line assertions correctly preserve comments; test passes.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Oct 2, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

  • Consider introducing a type alias for repeated annotations like cabc.Mapping[str, object] to reduce verbosity and improve readability.
  • Might be better to use the logging module instead of print in _warn_on_markdown_update_failure so warnings integrate with existing logging configuration.
  • In test_update_markdown_versions_behavior you could parameterize the expected change directly instead of computing should_change from outcome to simplify the test logic.

@coderabbitai

coderabbitai Bot commented Oct 2, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if type aliases have been introduced for repeated annotations
echo "=== Checking for type aliases in bump_version.py ==="
rg -n "TomlMapping|TomlMutableMapping" crate_tools/bump_version.py

echo -e "\n=== Checking _warn_on_markdown_update_failure implementation ==="
ast-grep --pattern $'def _warn_on_markdown_update_failure($$$) {
  $$$
}'

echo -e "\n=== Checking test_update_markdown_versions_behavior ==="
ast-grep --pattern $'def test_update_markdown_versions_behavior($$$) {
  $$$
}'

Length of output: 607


🏁 Script executed:

#!/bin/bash
# Check _warn_on_markdown_update_failure implementation with context
echo "=== Checking _warn_on_markdown_update_failure implementation ==="
rg -n -A 10 "def _warn_on_markdown_update_failure" crate_tools/bump_version.py

echo -e "\n=== Checking test_update_markdown_versions_behavior ==="
rg -n -A 20 "def test_update_markdown_versions_behavior" crate_tools/unittests/test_bump_version.py

Length of output: 1430


@leynos: I've checked the current state of the code. Here's the status of each suggestion:

Type alias for repeated annotations: RESOLVED

  • Type aliases TomlMapping and TomlMutableMapping have been introduced at lines 36-37 in bump_version.py
  • They're being used consistently throughout the file instead of repeated cabc.Mapping[str, typ.Any] annotations

Using logging module in _warn_on_markdown_update_failure: RESOLVED

  • The function now uses logger.warning() (line 609) instead of print()
  • This properly integrates with existing logging configuration

Simplifying test_update_markdown_versions_behavior parameterization: NOT RESOLVED

  • The test still uses a description parameter that's passed to the assert statement
  • The test structure hasn't been simplified to parameterize the expected change directly
  • Currently at lines 119-132, it still computes and asserts with a description message rather than having cleaner parameterization

Two out of three suggestions have been addressed. Would you like me to provide a code example for simplifying the test parameterization?

@leynos

leynos commented Oct 2, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please provide revised guidance and diff for utilizing structural pattern matching here:

    169-177: Consider extracting nested conditionals into a helper for clarity.

    The nested isinstance checks for workspace and package could be extracted into a small helper function to reduce cognitive load and improve testability.

    Apply this refactor:

    +def _get_package_from_workspace(doc: TomlMutableMapping) -> cabc.MutableMapping[str, typ.Any] | None:
    +    """Return the package mapping from workspace if present."""
    +    workspace = doc.get("workspace")
    +    if isinstance(workspace, cabc.MutableMapping):
    +        package = workspace.get("package")
    +        if isinstance(package, cabc.MutableMapping):
    +            return package
    +    return None
    +
     def _update_package_version(
         doc: TomlMutableMapping,
         version: str,
     ) -> None:
    -    workspace = doc.get("workspace")
    -    if isinstance(workspace, cabc.MutableMapping):
    -        package = workspace.get("package")
    -        if isinstance(package, cabc.MutableMapping):
    -            package["version"] = version
    -            return
    +    package = _get_package_from_workspace(doc)
    +    if package is not None:
    +        package["version"] = version
    +        return
         package = doc.get("package")
         if isinstance(package, cabc.MutableMapping):
             package["version"] = version

Structural pattern matching will usually always work better than multiple nested isinstance calls.

@coderabbitai

coderabbitai Bot commented Oct 2, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the current implementation of _update_package_version function
echo "=== Current implementation of _update_package_version ==="
rg -n -A 15 "def _update_package_version" crate_tools/bump_version.py

# Check Python version requirements
echo -e "\n=== Check pyproject.toml for Python version requirement ==="
rg -n "python" pyproject.toml | head -20

Length of output: 632


🏁 Script executed:

#!/bin/bash
# Get the complete _update_package_version function
echo "=== Complete _update_package_version function ==="
rg -n -A 20 "def _update_package_version" crate_tools/bump_version.py

Length of output: 717


🏁 Script executed:

#!/bin/bash
# Get more lines to see the complete function
echo "=== Complete _update_package_version function with more context ==="
rg -n -A 25 "def _update_package_version" crate_tools/bump_version.py

Length of output: 872


@leynos: Great suggestion! Structural pattern matching is indeed more elegant here. Since the project requires Python >=3.10, we can leverage match/case to simplify the nested checks.

Here's the revised guidance using structural pattern matching:

Approach 1: Inline structural pattern matching

def _update_package_version(
    doc: TomlMutableMapping,
    version: str,
) -> None:
    """Update package version in ``doc`` if present.

    Examples
    --------
    >>> data = {"package": {"version": "0"}}
    >>> _update_package_version(data, "1")
    >>> data["package"]["version"]
    '1'

    """
    match doc.get("workspace"), doc.get("package"):
        case (workspace, _) if isinstance(workspace, cabc.MutableMapping):
            package = workspace.get("package")
            if isinstance(package, cabc.MutableMapping):
                package["version"] = version
                return
        case (_, package) if isinstance(package, cabc.MutableMapping):
            package["version"] = version

Approach 2: Helper function with structural pattern matching (Recommended)

This approach improves testability and readability:

def _get_package_from_workspace(doc: TomlMutableMapping) -> cabc.MutableMapping[str, typ.Any] | None:
    """Return the package mapping from workspace if present."""
    match doc.get("workspace"):
        case workspace if isinstance(workspace, cabc.MutableMapping):
            match workspace.get("package"):
                case package if isinstance(package, cabc.MutableMapping):
                    return package
    return None


def _update_package_version(
    doc: TomlMutableMapping,
    version: str,
) -> None:
    """Update package version in ``doc`` if present.

    Examples
    --------
    >>> data = {"package": {"version": "0"}}
    >>> _update_package_version(data, "1")
    >>> data["package"]["version"]
    '1'

    """
    match _get_package_from_workspace(doc):
        case package if package is not None:
            package["version"] = version
            return
    
    match doc.get("package"):
        case package if isinstance(package, cabc.MutableMapping):
            package["version"] = version

Benefits of Approach 2:

  • ✅ Separates workspace package lookup logic for better testability
  • ✅ Uses pattern matching to eliminate nested isinstance checks
  • ✅ More declarative and easier to understand intent
  • ✅ Each function has a single responsibility

Both approaches eliminate the nested isinstance conditionals, but Approach 2 provides better separation of concerns and is easier to test independently.

🐰

@leynos

leynos commented Oct 2, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai How about:

match doc:
    case {"workspace": {"package": cabc.MutableMapping() as package}}:
        package["version"] = version
    case {"package": cabc.MutableMapping() as package}:
        package["version"] = version

Learnings:

  1. the dict deconstruction works on any object implementing Mapping
  2. match/case only matches one leg at most.

Lastly, we are using Python 3.13+

Please revise the guidance with this in mind.

@coderabbitai

coderabbitai Bot commented Oct 2, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@leynos: Excellent! Your approach is much cleaner and more Pythonic. Here's the revised guidance:

Revised: Use structural pattern matching with dict deconstruction

Your solution elegantly handles both cases without nested conditionals:

def _update_package_version(
    doc: TomlMutableMapping,
    version: str,
) -> None:
    """Update package version in ``doc`` if present.

    Examples
    --------
    >>> data = {"package": {"version": "0"}}
    >>> _update_package_version(data, "1")
    >>> data["package"]["version"]
    '1'

    """
    match doc:
        case {"workspace": {"package": cabc.MutableMapping() as package}}:
            package["version"] = version
        case {"package": cabc.MutableMapping() as package}:
            package["version"] = version

Benefits:

  • ✅ No nested isinstance checks or helper functions needed
  • ✅ Dict deconstruction works on any Mapping implementation (including TOML documents)
  • ✅ Match only executes the first matching case, preventing duplicate updates
  • ✅ Clear and declarative - immediately shows the two lookup paths
  • ✅ Leverages Python 3.13+ pattern matching features

This is the optimal solution for this use case! 🎯


✏️ Learnings added
Learnt from: leynos
URL: https://github.com/leynos/crate-tools/pull/4

Timestamp: 2025-10-02T22:04:20.355Z
Learning: In Python 3.10+, structural pattern matching with dict deconstruction (e.g., `case {"key": {"nested": Type() as var}}:`) works on any object implementing the Mapping protocol, not just dict instances.

Learnt from: leynos
URL: https://github.com/leynos/crate-tools/pull/4

Timestamp: 2025-10-02T22:04:20.355Z
Learning: In Python's match/case statement, only the first matching case is executed. Once a pattern matches, the match statement completes without evaluating subsequent cases, preventing duplicate execution.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Oct 3, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of _update_package_version
echo "=== Current implementation of _update_package_version ==="
rg -n -A 12 "def _update_package_version" crate_tools/bump_version.py

Length of output: 408


🏁 Script executed:

#!/bin/bash
# Get the complete function body with more context
echo "=== Complete _update_package_version function body ==="
rg -n -A 20 "def _update_package_version" crate_tools/bump_version.py

Length of output: 679


@leynos: Yes, RESOLVED

The structural pattern matching has been implemented exactly as recommended. The current code (lines 169-173) uses:

match doc:
    case {"workspace": {"package": cabc.MutableMapping() as package}}:
        package["version"] = version
    case {"package": cabc.MutableMapping() as package}:
        package["version"] = version

This eliminates the nested isinstance checks and leverages Python 3.13+'s pattern matching with dict deconstruction, making the code clean and declarative. 🎯

@leynos

leynos commented Oct 3, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Has the following now been resolved?

Simplifying test_update_markdown_versions_behavior parameterization: NOT RESOLVED

  • The test still uses a description parameter that's passed to the assert statement
  • The test structure hasn't been simplified to parameterize the expected change directly
  • Currently at lines 119-132, it still computes and asserts with a description message rather than having cleaner parameterization

@coderabbitai

coderabbitai Bot commented Oct 3, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of test_update_markdown_versions_behavior
echo "=== Current test_update_markdown_versions_behavior implementation ==="
rg -n -A 25 "def test_update_markdown_versions_behavior" crate_tools/unittests/test_bump_version.py

Length of output: 1150


🏁 Script executed:

#!/bin/bash
# Check the parametrize decorator for test_update_markdown_versions_behavior
echo "=== Parametrize decorator for test_update_markdown_versions_behavior ==="
rg -n -B 15 "def test_update_markdown_versions_behavior" crate_tools/unittests/test_bump_version.py | head -30

Length of output: 312


🏁 Script executed:

#!/bin/bash
# Get more context to see the complete parametrize decorator
echo "=== Complete parametrize decorator and test function ==="
rg -n -B 50 "def test_update_markdown_versions_behavior" crate_tools/unittests/test_bump_version.py | grep -A 50 "@pytest.mark.parametrize"

Length of output: 687


@leynos: Yes, RESOLVED

The test parameterization has been simplified as requested. Here's what changed:

Current implementation (lines 80-129):

  • The @pytest.mark.parametrize decorator now takes ("md_text", "expected_text") directly
  • Each test case uses pytest.param() with descriptive id values ("toml-fence", "non-toml-fence")
  • The test function signature is: test_update_markdown_versions_behavior(tmp_path, md_text, expected_text)
  • The assertion is straightforward: assert updated == expected_text

What was removed:

  • ❌ No more description parameter
  • ❌ No more computing should_change from an outcome parameter
  • ❌ No more intermediate boolean logic

The expected content is now parameterized directly, making the test logic cleaner and easier to understand. Each test case explicitly shows both the input markdown and the expected output after version update. 🎯

@leynos
leynos merged commit 4d88893 into main Oct 3, 2025
4 checks passed
@leynos
leynos deleted the codex/fix-lint-errors-in-bump_version.py branch October 3, 2025 11:09
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