Development Setup¶
Set up your local development environment.
Prerequisites¶
- Python 3.11 or higher
- Git
- uv (recommended) or pip
Quick Start¶
# Clone the repository
git clone https://github.com/mikeleppane/releasio.git
cd releasio
# Install dependencies with uv
uv sync --all-extras
# Run tests to verify setup
uv run pytest
Development Commands¶
Running Tests¶
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/releasio --cov-report=term-missing
# Run specific test file
uv run pytest tests/unit/test_version.py
# Run tests matching pattern
uv run pytest -k "test_parse"
Linting and Formatting¶
# Check linting
uv run ruff check src/ tests/
# Fix auto-fixable issues
uv run ruff check --fix src/ tests/
# Format code
uv run ruff format src/ tests/
# Check formatting only
uv run ruff format --check src/ tests/
Type Checking¶
Running the CLI¶
Project Structure¶
releasio/
├── src/releasio/ # Source code
│ ├── cli/ # CLI commands
│ ├── core/ # Business logic
│ ├── config/ # Configuration
│ ├── vcs/ # Git operations
│ ├── forge/ # GitHub integration
│ └── publish/ # PyPI publishing
├── tests/ # Test suite
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── docs/ # Documentation
└── pyproject.toml # Project config
Writing Tests¶
Test Structure¶
class TestVersionParsing:
"""Tests for version parsing."""
def test_parse_simple_version(self):
"""Parse a simple semantic version."""
version = Version.parse("1.2.3")
assert version.major == 1
assert version.minor == 2
assert version.patch == 3
def test_parse_with_prerelease(self):
"""Parse version with pre-release."""
version = Version.parse("1.0.0-beta.1")
assert version.pre_release == "beta"
assert version.pre_release_num == 1
@pytest.mark.parametrize("version_str,expected", [
("1.0.0", Version(1, 0, 0)),
("2.1.0", Version(2, 1, 0)),
])
def test_parse_various(self, version_str: str, expected: Version):
"""Parse various version strings."""
assert Version.parse(version_str) == expected
Using Fixtures¶
@pytest.fixture
def git_repo(tmp_path: Path) -> GitRepository:
"""Create a temporary Git repository."""
subprocess.run(["git", "init"], cwd=tmp_path, check=True)
subprocess.run(
["git", "config", "user.email", "test@example.com"],
cwd=tmp_path,
)
subprocess.run(
["git", "config", "user.name", "Test User"],
cwd=tmp_path,
)
return GitRepository(tmp_path)
def test_get_commits(git_repo: GitRepository):
"""Test getting commits from repository."""
# Create a commit
(git_repo.path / "file.txt").write_text("content")
subprocess.run(["git", "add", "."], cwd=git_repo.path)
subprocess.run(
["git", "commit", "-m", "feat: initial commit"],
cwd=git_repo.path,
)
commits = git_repo.get_commits_since_tag(None)
assert len(commits) == 1
Commit Messages¶
Use Conventional Commits:
# Features
git commit -m "feat: add custom parser support"
git commit -m "feat(cli): add verbose flag"
# Bug fixes
git commit -m "fix: handle empty commit list"
git commit -m "fix(changelog): escape special characters"
# Documentation
git commit -m "docs: add configuration examples"
# Refactoring
git commit -m "refactor: simplify version parsing"
# Tests
git commit -m "test: add Gitmoji parser tests"
# Chores
git commit -m "chore: update dependencies"
Pull Request Process¶
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes
- Test thoroughly:
uv run pytest - Lint your code:
uv run ruff check src/ tests/ - Commit with conventional commits
- Push your branch
- Open a pull request
PR Checklist¶
- Tests pass (
uv run pytest) - Linting passes (
uv run ruff check) - Type checking passes (
uv run mypy src/) - Documentation updated (if needed)
- Commit messages follow convention
Documentation¶
Building Docs Locally¶
# Install docs dependencies
uv sync --extra docs
# Serve docs locally
uv run mkdocs serve
# Build docs
uv run mkdocs build
Documentation Structure¶
docs/
├── index.md # Landing page
├── getting-started/ # Getting started guides
├── user-guide/ # User documentation
│ ├── cli/ # CLI commands
│ ├── configuration/ # Config reference
│ ├── versioning/ # Version management
│ ├── changelog/ # Changelog generation
│ └── commits/ # Commit parsing
├── github/ # GitHub integration
├── publishing/ # PyPI publishing
├── advanced/ # Advanced features
├── architecture/ # System design
└── contributing/ # This section
Code Style¶
Python Style¶
# Use type annotations everywhere
def calculate_bump(
commits: list[ParsedCommit],
config: CommitsConfig,
) -> BumpType:
...
# Use dataclasses for data structures
@dataclass(frozen=True, slots=True)
class ParsedCommit:
commit: Commit
commit_type: str | None
description: str
is_breaking: bool
# Use Path objects for file paths
config_path: Path = repo_path / "pyproject.toml"
# Prefer early returns
def get_version(path: Path) -> Version | None:
if not path.exists():
return None
content = path.read_text()
if not content.strip():
return None
return parse_version(content)
Error Handling¶
# Use custom exceptions
class ChangelogError(ReleaseError):
"""Raised when changelog generation fails."""
# Provide context in exceptions
raise ChangelogError(
f"Failed to generate changelog for {version}: {e}",
) from e
# Handle expected errors gracefully
try:
result = subprocess.run(args, check=True, capture_output=True)
except subprocess.CalledProcessError as e:
if "no commits" in e.stderr.lower():
return BumpType.NONE
raise GitError(f"Command failed: {e}") from e
Release Process¶
Releases are automated via GitHub Actions:
- Merge PR to
main - GitHub Action creates release PR
- Review and merge release PR
- GitHub Action creates tag and publishes
Getting Help¶
- Open an issue
- Check existing issues and discussions
- Read the Architecture docs
See Also¶
- Architecture Overview - System design
- API Reference - Code documentation