Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .bumpversion.cfg

This file was deleted.

17 changes: 4 additions & 13 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Publish to PyPI

on:
release:
types: [published]
types:
- published

jobs:
build-and-publish:
Expand All @@ -24,18 +25,8 @@ jobs:
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Verify version matches tag
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
PACKAGE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "Error: Tag version ($TAG_VERSION) does not match package version ($PACKAGE_VERSION)"
exit 1
fi
echo "Version check passed: $PACKAGE_VERSION"

pip install build twine setuptools_scm

- name: Build package
run: python -m build

Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,8 @@ All new features should include appropriate tests.
Releases are managed by maintainers:

1. Update CHANGELOG.md
2. Run `bump2version [major|minor|patch]` to create version tag
3. Push tags: `git push --tags`
4. Create a GitHub Release - this triggers PyPI publishing automatically
2. Push a version tag: `git tag v<version> && git push --tags`
3. Create a GitHub Release from that tag - this triggers PyPI publishing automatically

## Questions?

Expand Down
10 changes: 6 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
requires = ["setuptools>=61.0", "setuptools_scm>=8.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.uv]
Expand All @@ -13,13 +13,12 @@ dev-dependencies = [
"pytest-xdist>=3.0.0",
"hypothesis>=6.0.0",
"tox>=4.0.0",
"bump2version>=1.0.0",
"basedpyright>=1.0.0",
]

[project]
name = "alpha-hwr"
version = "0.5.0"
dynamic = ["version"]
description = "Modern Python library and CLI for Grundfos ALPHA HWR pumps via Bluetooth Low Energy"
readme = "README.md"
authors = [
Expand Down Expand Up @@ -81,7 +80,6 @@ dev = [
"mypy>=1.0.0",
"ruff>=0.1.0",
"tox>=4.0.0",
"bump2version>=1.0.0",
"basedpyright>=1.0.0",
]
docs = [
Expand Down Expand Up @@ -126,3 +124,7 @@ markers = [
database = false
max_examples = 100
suppress_health_check = ["too_slow"]

[tool.setuptools_scm]
version_scheme = "post-release"
local_scheme = "no-local-version"
7 changes: 6 additions & 1 deletion src/alpha_hwr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
Grundfos ALPHA HWR Client Library
"""

__version__ = "0.5.0"
from importlib.metadata import version, PackageNotFoundError

try:
__version__ = version("alpha-hwr")
except PackageNotFoundError:
__version__ = "0.0.0+unknown"

from .client import AlphaHWRClient, discover_devices
from .models import (
Expand Down
140 changes: 22 additions & 118 deletions tests/test_version_consistency.py
Original file line number Diff line number Diff line change
@@ -1,139 +1,43 @@
"""Test version consistency across the project files."""
"""Test version configuration for the project."""

import re
from pathlib import Path


def get_version_from_pyproject():
"""Extract version from pyproject.toml."""
def test_version_consistency():
"""Test that pyproject.toml is correctly configured for setuptools_scm."""
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
content = pyproject_path.read_text()
match = re.search(r'version = "([^"]+)"', content)
if match:
return match.group(1)
return None


def get_version_from_init():
"""Extract version from __init__.py."""
init_path = (
Path(__file__).parent.parent / "src" / "alpha_hwr" / "__init__.py"
# Version must be dynamic, not static
assert 'dynamic = ["version"]' in content, (
"pyproject.toml must declare version as dynamic for setuptools_scm"
)
content = init_path.read_text()
match = re.search(r'__version__ = "([^"]+)"', content)
if match:
return match.group(1)
return None


def get_version_from_mkdocs():
"""Extract version from mkdocs.yml."""
mkdocs_path = Path(__file__).parent.parent / "mkdocs.yml"
content = mkdocs_path.read_text()
match = re.search(r'version: "([^"]+)"', content)
if match:
return match.group(1)
return None


def get_version_from_docs_index():
"""Extract version from docs/index.md."""
docs_index_path = Path(__file__).parent.parent / "docs" / "index.md"
content = docs_index_path.read_text()
match = re.search(r"\*Version: ([^\*]+)\*", content)
if match:
return match.group(1)
return None


def get_version_from_bumpversion_cfg():
"""Extract current_version from .bumpversion.cfg."""
bumpversion_path = Path(__file__).parent.parent / ".bumpversion.cfg"
content = bumpversion_path.read_text()
match = re.search(r"current_version = (.+)", content)
if match:
return match.group(1).strip()
return None


def test_version_consistency():
"""Test that version is consistent across all project files."""
pyproject_version = get_version_from_pyproject()
init_version = get_version_from_init()
mkdocs_version = get_version_from_mkdocs()
docs_index_version = get_version_from_docs_index()
bumpversion_version = get_version_from_bumpversion_cfg()
assert (
re.search(r'^\s*version\s*=\s*"[^"]+"', content, re.MULTILINE) is None
), "pyproject.toml must not have a static version field in [project]"

# Ensure all versions were found
assert pyproject_version is not None, "Version not found in pyproject.toml"
assert init_version is not None, "Version not found in __init__.py"
assert mkdocs_version is not None, "Version not found in mkdocs.yml"
assert docs_index_version is not None, "Version not found in docs/index.md"
assert bumpversion_version is not None, (
"Version not found in .bumpversion.cfg"
# setuptools_scm must be in build requirements
assert "setuptools_scm" in content, (
"setuptools_scm must be listed in [build-system].requires"
)

# Check consistency
assert init_version == pyproject_version, (
f"Version mismatch: __init__.py ({init_version}) != "
f"pyproject.toml ({pyproject_version})"
)
assert mkdocs_version == pyproject_version, (
f"Version mismatch: mkdocs.yml ({mkdocs_version}) != "
f"pyproject.toml ({pyproject_version})"
)
assert docs_index_version == pyproject_version, (
f"Version mismatch: docs/index.md ({docs_index_version}) != "
f"pyproject.toml ({pyproject_version})"
)
assert bumpversion_version == pyproject_version, (
f"Version mismatch: .bumpversion.cfg ({bumpversion_version}) != "
f"pyproject.toml ({pyproject_version})"
# [tool.setuptools_scm] section must exist
assert "[tool.setuptools_scm]" in content, (
"pyproject.toml must have a [tool.setuptools_scm] section"
)


def test_bumpversion_search_patterns():
"""Test that bumpversion search patterns match actual file content."""
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
def test_init_uses_importlib_metadata():
"""Test that __init__.py reads version from importlib.metadata."""
init_path = (
Path(__file__).parent.parent / "src" / "alpha_hwr" / "__init__.py"
)
mkdocs_path = Path(__file__).parent.parent / "mkdocs.yml"
docs_index_path = Path(__file__).parent.parent / "docs" / "index.md"
bumpversion_path = Path(__file__).parent.parent / ".bumpversion.cfg"

# Get current version
version = get_version_from_pyproject()
assert version is not None, "Could not determine current version"

# Check that patterns would match
pyproject_content = pyproject_path.read_text()
assert f'version = "{version}"' in pyproject_content, (
f'pyproject.toml does not contain expected pattern: version = "{version}"'
)

init_content = init_path.read_text()
assert f'__version__ = "{version}"' in init_content, (
f'__init__.py does not contain expected pattern: __version__ = "{version}"'
)

mkdocs_content = mkdocs_path.read_text()
assert f'version: "{version}"' in mkdocs_content, (
f'mkdocs.yml does not contain expected pattern: version: "{version}"\n'
f"This usually means the indentation or quotes don't match the bumpversion config."
)
content = init_path.read_text()

docs_index_content = docs_index_path.read_text()
assert f"*Version: {version}*" in docs_index_content, (
f"docs/index.md does not contain expected pattern: *Version: {version}*"
assert "importlib.metadata" in content, (
"__init__.py must use importlib.metadata to read the package version"
)

# Verify .bumpversion.cfg has correct patterns
bumpversion_content = bumpversion_path.read_text()

# Check mkdocs.yml pattern includes proper indentation
# The pattern should have 2 spaces before 'version:'
assert 'search = version: "{current_version}"' in bumpversion_content, (
".bumpversion.cfg mkdocs.yml search pattern must include proper indentation "
"(2 spaces before 'version:')"
assert "PackageNotFoundError" in content, (
"__init__.py must handle PackageNotFoundError from importlib.metadata"
)