Skip to content

Conversation

@JayBazuzi
Copy link
Contributor

@JayBazuzi JayBazuzi commented Dec 7, 2025

Previous Behavior

version.version_number included a v prefix and could use - or .; code that used version_number would have to clean this up. Inconsistent cleanup has been a source of bugs.

New behavior

When writing version.py, always canonicalize the value before writing it, so that all consuming code can just use it as-is.

Summary by Sourcery

Canonicalize the project version number at the time version files are written so all consumers can use a normalized version string without additional cleanup.

New Features:

  • Add a setup script that writes a canonicalized version string into all version files based on a given version tag.

Enhancements:

  • Update version-related utilities and tests to assume an already-canonical version_number rather than re-normalizing it.
  • Adjust the GitHub publish workflow to use the new version-setting script when tagging releases.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 7, 2025

Reviewer's Guide

This PR centralizes and canonicalizes version number handling by introducing a dedicated script to generate normalized version.py files from a git tag, removing ad-hoc normalization logic from tests and setup, and updating CI to use the new approach.

Sequence diagram for canonicalized version setting in PyPI publish workflow

sequenceDiagram
    participant GitHubActions
    participant PublishWorkflow
    participant SetVersionStep
    participant SetVersionScript
    participant VersionPy as version_py
    participant ApprovalVersionPy as approvaltests_version_py

    GitHubActions->>PublishWorkflow: trigger workflow on tag
    PublishWorkflow->>SetVersionStep: run step set version
    SetVersionStep->>SetVersionScript: python setup/set_version.py tag
    SetVersionScript->>SetVersionScript: parse tag as Version
    SetVersionScript->>SetVersionScript: normalize to canonical version string
    SetVersionScript->>VersionPy: write version_number = "<canonical>"
    SetVersionScript->>ApprovalVersionPy: write version_number = "<canonical>"
    SetVersionScript-->>SetVersionStep: exit success
    SetVersionStep-->>PublishWorkflow: version files updated
    PublishWorkflow-->>GitHubActions: continue with build and publish
Loading

Updated class diagram for version handling utilities

classDiagram
    class setup_utils {
        +get_version() str
    }

    class set_version {
        +_VERSION_FILES
        +main() void
    }

    class version_py {
        +version_number str
    }

    class approvaltests_version_py {
        +version_number str
    }

    setup_utils --> version_py : imports version_number
    set_version --> version_py : writes version_number
    set_version --> approvaltests_version_py : writes version_number
    approvaltests_version_py <.. version_py : parallel version source
Loading

File-Level Changes

Change Details Files
Canonicalize version_number once at generation time and rely on it as-is everywhere.
  • Remove local Version(...) normalization from mypy acceptance test, using the imported version_number directly.
  • Update setup_utils.get_version to return version_number without further packaging.version normalization.
  • Update existing version.py files to store a normalized version string without a leading 'v'.
test__mypy_accepts_our_packages.py
setup/setup_utils.py
approvaltests/version.py
version.py
Introduce a reusable script to set canonical version numbers in all version.py files and wire it into the publish workflow.
  • Add setup/set_version.py which parses a version argument via packaging.version.Version, logs it, and writes the normalized value into all configured version.py files.
  • Replace inline shell-based version.py creation in the publish_to_pypi GitHub Actions workflow with a call to the new set_version.py script.
setup/set_version.py
.github/workflows/publish_to_pypi.yml

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

  • In setup/set_version.py, consider making _VERSION_FILES an explicit list or tuple of Path objects rather than a map(Path, [...]) for readability and easier inspection/debugging.
  • The paths in _VERSION_FILES are currently relative to the working directory; to make the script more robust when run from other locations, you could derive these paths from Path(__file__).parent.parent (project root) instead of assuming cwd.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `setup/set_version.py`, consider making `_VERSION_FILES` an explicit list or tuple of `Path` objects rather than a `map(Path, [...])` for readability and easier inspection/debugging.
- The paths in `_VERSION_FILES` are currently relative to the working directory; to make the script more robust when run from other locations, you could derive these paths from `Path(__file__).parent.parent` (project root) instead of assuming `cwd`.

## Individual Comments

### Comment 1
<location> `test__mypy_accepts_our_packages.py:12-13` </location>
<code_context>
-
 from version import version_number

-# normalize version number
-version_number = str(Version(version_number))
-
-
</code_context>

<issue_to_address>
**suggestion (testing):** Add an explicit test that `version_number` is already canonicalized instead of relying on implicit normalization here

Now that normalization happens in the version-generation step, this test no longer verifies that `version.version_number` is canonical. Please add a focused test (e.g., `test_version_is_canonical`) that imports `version_number` from `version.py` and asserts that:

- it can be parsed by `packaging.version.Version`
- it has no leading `v`
- it matches the expected normalized string for a fixed input (e.g., via mocking or a fixed tag)

This makes the canonicalization contract explicit and guards against regressions where a non-canonical string slips through while this test still passes.

Suggested implementation:

```python
import time
import typing
from packaging.version import Version

from version import version_number

```

```python
from version import version_number


def test_version_is_canonical() -> None:
    parsed = Version(version_number)

    # Should be parseable and already in normalized form
    assert not version_number.startswith("v")
    assert version_number == str(parsed)


def main() -> None:

```
</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 on lines -12 to -13
# normalize version number
version_number = str(Version(version_number))
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Add an explicit test that version_number is already canonicalized instead of relying on implicit normalization here

Now that normalization happens in the version-generation step, this test no longer verifies that version.version_number is canonical. Please add a focused test (e.g., test_version_is_canonical) that imports version_number from version.py and asserts that:

  • it can be parsed by packaging.version.Version
  • it has no leading v
  • it matches the expected normalized string for a fixed input (e.g., via mocking or a fixed tag)

This makes the canonicalization contract explicit and guards against regressions where a non-canonical string slips through while this test still passes.

Suggested implementation:

import time
import typing
from packaging.version import Version

from version import version_number
from version import version_number


def test_version_is_canonical() -> None:
    parsed = Version(version_number)

    # Should be parseable and already in normalized form
    assert not version_number.startswith("v")
    assert version_number == str(parsed)


def main() -> None:

@JayBazuzi JayBazuzi merged commit 767096b into main Dec 7, 2025
100 of 107 checks passed
@JayBazuzi JayBazuzi deleted the JayBazuzi/version branch December 7, 2025 17:05
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