Skip to content

Conversation

@jmgate
Copy link
Collaborator

@jmgate jmgate commented Jul 8, 2025

Type: Task

Description

Turn on all the latest and greatest Ruff linters, and adjust the code as needed to make them pass.

Summary by Sourcery

Enable all new Ruff linter rules and adjust the codebase, examples, and tests to satisfy updated lint and type requirements.

Enhancements:

  • Centralise stage name validation into a standalone function and replace the old static validator
  • Add type hints and return annotations across methods and functions
  • Remove redundant no-op pass statements in various classes
  • Update example scripts to include explicit return type annotations

Build:

  • Extend Ruff linting rules and per-file ignores in pyproject.toml
  • Add pyupgrade configuration to preserve runtime typing

CI:

  • Update pre-commit configuration to use the ruff-check hook

Tests:

  • Rename and update tests to call the new validate_stage_name function and adjust mock variable names
  • Add type annotations to test parameters and kwargs

@jmgate jmgate self-assigned this Jul 8, 2025
@sourcery-ai
Copy link

sourcery-ai bot commented Jul 8, 2025

Reviewer's Guide

This PR enables the full suite of Ruff lint rules by updating configurations and refactors core and example code to satisfy the new lint requirements—extracting utility functions, adding type annotations and signatures, removing redundant code, updating tests, and switching CI hooks.

Class diagram for validate_stage_name extraction and StagedScript changes

classDiagram
    class StagedScript {
        +__init__(stages: list[str], console_force_terminal: Optional[bool] = None, console_log_path: bool = True, print_commands: bool = True) -> None
        +parse_args(argv: list[str]) -> None
        +raise_parser_error(message: str) -> NoReturn
        +run(*, pretty_print: bool = False, print_command: Optional[bool] = None, **kwargs: Any) -> CompletedProcess
        -args: Namespace
        -commands_executed: list[str]
        -console
        -stages_to_run: set[str]
        -start_time
    }
    class validate_stage_name {
        +validate_stage_name(stage_name: str) -> None
    }
    StagedScript ..> validate_stage_name : uses
    class RetryStage {
        <<inherits>> TryAgain
    }
    class HelpFormatter {
        <<inherits>> ArgumentDefaultsHelpFormatter
        <<inherits>> RawDescriptionHelpFormatter
    }
Loading

Class diagram for decorator and wrapper function changes

classDiagram
    class stage {
        +stage(stage_name: str, heading: str) -> Callable
    }
    class decorator {
        +decorator(func: Callable) -> Callable
    }
    class wrapper {
        +wrapper(self, *args: Any, **kwargs: Any) -> None
    }
    stage ..> validate_stage_name : uses
    decorator o-- wrapper : returns
    stage o-- decorator : returns
    decorator ..> get_phase_method : uses
    decorator ..> run_retryable_phases : uses
    class get_phase_method {
        +get_phase_method(self, method_name: str) -> Callable
    }
    class run_retryable_phases {
        +run_retryable_phases(self, *args: Any, **kwargs: Any) -> None
    }
Loading

File-Level Changes

Change Details Files
Extract validate_stage_name into standalone function and remove its class static method
  • Define validate_stage_name at module level
  • Replace all calls to StagedScript._validate_stage_name with validate_stage_name
  • Remove the old static method implementation
staged_script/staged_script.py
test/test_staged_script.py
Add type annotations across core and example modules and drop redundant pass statements
  • Add return type and parameter annotations to methods
  • Annotate *args/**kwargs with Any and add NoReturn where appropriate
  • Remove unused pass statements from empty methods
staged_script/staged_script.py
example/ex_6_creating_retryable_stages.py
example/ex_7_customizing_the_summary.py
Update Ruff configuration in pyproject.toml
  • Enable additional rule groups in extend-select
  • Configure per-file ignores for tests and docs
  • Add pyupgrade settings under tool.ruff.lint.pyupgrade
pyproject.toml
Switch pre-commit hook from ruff to ruff-check
  • Change hook id from ruff to ruff-check under ruff-pre-commit config
.pre-commit-config.yaml
Refactor tests to comply with new lint rules
  • Rename mock fixtures and function calls to match naming conventions
  • Prefix unused parameters with underscores
  • Update test calls from static method to standalone function
test/test_staged_script.py
test/test_staged_script_advanced_subclass.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

Copy link

@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 @jmgate - I've reviewed your changes - here's some feedback:

  • I noticed validate_stage_name was pulled out as a module‐level function but tests still call StagedScript.validate_stage_name—either reattach it to the class or update the tests to import and call it directly.
  • There are quite a few # noqa: ANN* suppressions—consider refining those function signatures (e.g. naming and typing of *args/**kwargs) to satisfy the annotation rules instead of disabling them.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- I noticed `validate_stage_name` was pulled out as a module‐level function but tests still call `StagedScript.validate_stage_name`—either reattach it to the class or update the tests to import and call it directly.
- There are quite a few `# noqa: ANN*` suppressions—consider refining those function signatures (e.g. naming and typing of `*args`/`**kwargs`) to satisfy the annotation rules instead of disabling them.

## Individual Comments

### Comment 1
<location> `staged_script/staged_script.py:61` </location>
<code_context>
+    Raises:
+        ValueError:  If the stage name is invalid.
+    """
+    if not re.match("^[a-z]+$", stage_name):
+        message = (
+            f"Stage name {stage_name!r} must contain only lowercase letters."
</code_context>

<issue_to_address>
Using re.match for a simple lowercase check may be overkill.

Consider replacing the regex with: if not (stage_name.islower() and stage_name.isalpha()): ... for better readability and efficiency.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    if not re.match("^[a-z]+$", stage_name):
        message = (
            f"Stage name {stage_name!r} must contain only lowercase letters."
        )
        raise ValueError(message)
=======
    if not (stage_name.islower() and stage_name.isalpha()):
        message = (
            f"Stage name {stage_name!r} must contain only lowercase letters."
        )
        raise ValueError(message)
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `staged_script/staged_script.py:313` </location>
<code_context>

             @functools.wraps(func)
-            def wrapper(self, *args, **kwargs) -> None:
+            def wrapper(self, *args: Any, **kwargs: dict[str, Any]) -> None:  # noqa: ANN001, ANN401
                 """
                 Turn a function into a stage.
</code_context>

<issue_to_address>
Type annotation for **kwargs as dict[str, Any] may not be accurate.

PEP 484 suggests annotating **kwargs as Any rather than dict[str, Any], since **kwargs is not strictly a dict. Please update the annotation accordingly.

Suggested implementation:

```python
                **kwargs: Any,

```

```python
            def wrapper(self, *args: Any, **kwargs: Any) -> None:  # noqa: ANN001, ANN401

```
</issue_to_address>

### Comment 3
<location> `test/test_staged_script.py:42` </location>
<code_context>

 def test__validate_stage_name() -> None:
     """Test the :func:`validate_stage_name` method."""
-    StagedScript._validate_stage_name("valid")
</code_context>

<issue_to_address>
Test for validate_stage_name should be updated to call the new standalone function.

Update the test to import and call validate_stage_name directly, rather than using StagedScript._validate_stage_name.
</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.

I should have done this ages ago.  Instead of `_validate_stage_name`
being a static method in the `StagedScript` class, make it a function in
the `staged_script` module.  We then no longer need to ignore the MyPy
error about `__class__` being undefined, and Ruff's SLF linter won't
complain about private member access.
@jmgate jmgate force-pushed the update-ruff-linters branch from 5a0d035 to a2d6bb8 Compare July 8, 2025 22:15
@codecov
Copy link

codecov bot commented Jul 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7f39996) to head (ef76d16).
⚠️ Report is 43 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #215   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            2         2           
  Lines          180       176    -4     
  Branches        15        15           
=========================================
- Hits           180       176    -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jmgate jmgate force-pushed the update-ruff-linters branch from a2d6bb8 to 7793809 Compare July 8, 2025 22:23
@jmgate
Copy link
Collaborator Author

jmgate commented Jul 8, 2025

@sourcery-ai review

Copy link

@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 @jmgate - I've reviewed your changes - here's some feedback:

  • Remove any unused imports (e.g. the re module) after refactoring validate_stage_name to avoid redundant code and lint warnings.
  • Instead of sprinkling # noqa: ANN401 in wrappers, consider fully annotating those parameters or configuring those exceptions in Ruff so you can drop the inline pragmas.
  • The new per-file ignores in pyproject.toml are quite broad; try scoping them more narrowly to only the specific rules or files that truly need them to avoid masking other issues.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Remove any unused imports (e.g. the `re` module) after refactoring `validate_stage_name` to avoid redundant code and lint warnings.
- Instead of sprinkling `# noqa: ANN401` in wrappers, consider fully annotating those parameters or configuring those exceptions in Ruff so you can drop the inline pragmas.
- The new per-file ignores in `pyproject.toml` are quite broad; try scoping them more narrowly to only the specific rules or files that truly need them to avoid masking other issues.

## Individual Comments

### Comment 1
<location> `staged_script/staged_script.py:45` </location>
<code_context>
 rich.traceback.install()


+def validate_stage_name(stage_name: str) -> None:
+    """
+    Validate a stage name.
</code_context>

<issue_to_address>
The new validate_stage_name function uses islower() and isalpha(), which is not strictly equivalent to the previous regex.

The previous regex only allowed ASCII lowercase letters, while the new approach permits all Unicode lowercase letters. If you want to restrict to ASCII, add stage_name.isascii() or revert to the regex. If broader Unicode support is intended, this change is fine, but note the behavioral difference.

Suggested implementation:

```python
def validate_stage_name(stage_name: str) -> None:
    """
    Validate a stage name.

    Ensure a stage name consists of only ASCII lowercase letters. This is
    both to simplify implementation details within the
    :class:`StagedScript` class, and to provide the best user experience
    for users of your :class:`StagedScript` subclasses.

    Args:
        stage_name:  The name of the stage.

    Raises:

```

```python
def validate_stage_name(stage_name: str) -> None:
    """
    Validate a stage name.

    Ensure a stage name consists of only ASCII lowercase letters. This is
    both to simplify implementation details within the
    :class:`StagedScript` class, and to provide the best user experience
    for users of your :class:`StagedScript` subclasses.

    Args:
        stage_name:  The name of the stage.

    Raises:
        ValueError: If the stage name contains non-ASCII, non-lowercase, or non-letter characters.
    """
    if not (stage_name.isascii() and stage_name.islower() and stage_name.isalpha()):
        raise ValueError(
            f"Invalid stage name '{stage_name}'. Stage names must consist of only ASCII lowercase letters."
        )

```
</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.

@jmgate jmgate force-pushed the update-ruff-linters branch from 7793809 to ef76d16 Compare July 8, 2025 22:41
@jmgate
Copy link
Collaborator Author

jmgate commented Jul 8, 2025

All of the CodeFactor issues were preexisting—I'm not sure why it's complaining about them for this PR.

@jmgate jmgate merged commit 13177c7 into master Jul 8, 2025
13 of 14 checks passed
@jmgate jmgate deleted the update-ruff-linters branch July 8, 2025 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants