Contributing¶
Ferro welcomes contributions — bug reports, docs fixes, and code across both the Python and Rust layers. The guide below is the canonical contributor reference from the repository.
We welcome contributions to Ferro! This guide will help you get started with developing Ferro locally.
Prerequisites¶
Before starting, ensure you have:
- Python 3.13+: Ferro requires Python 3.13 or later
- Rust toolchain: Required for building the Rust core
- UV: Fast Python package manager
Getting Started¶
1. Clone the Repository¶
2. Install Dependencies¶
This will install all development dependencies including: - Testing tools (pytest, pytest-asyncio, pytest-cov) - Linting and formatting tools (ruff, prek) - Build tools (maturin) - Documentation tools (zensical) - Release tools (commitizen, python-semantic-release)
3. Install Pre-commit Hooks¶
# Install all hooks (file checks, linting, formatting)
uv run prek install
# Install commit message validation hook
uv run prek install --hook-type commit-msg
These hooks will automatically: - Check for trailing whitespace - Fix end-of-file issues - Validate YAML, TOML, and JSON files - Format Python code with Ruff - Format Rust code with rustfmt - Lint Rust code with clippy - Validate conventional commit messages
4. Build the Rust Extension¶
This compiles the Rust core and installs it in development mode. You'll need to re-run this command after making changes to Rust code.
Development Workflow¶
Running Tests¶
# Run all tests with coverage
uv run pytest
# Run specific test file
uv run pytest tests/test_models.py
# Run with verbose output
uv run pytest -v
# Run tests and generate coverage report
uv run pytest --cov=src --cov-report=html
Running Linters¶
# Run all pre-commit hooks
uv run prek run --all-files
# Run specific hooks
uv run ruff check . # Python linting
uv run ruff format . # Python formatting
cargo fmt # Rust formatting
cargo clippy # Rust linting
Building Documentation¶
# Serve documentation locally (with live reload)
uv run zensical serve
# Build documentation
uv run zensical build
# Documentation will be available at http://127.0.0.1:8000/
Testing Your Changes¶
Before submitting a PR, ensure:
-
All tests pass:
-
All linters pass:
-
Rust tests pass:
-
Code builds successfully:
Conventional Commits¶
Ferro uses Conventional Commits for automated version bumping and changelog generation. All commit messages must follow this format:
Commit Types¶
- feat: New feature (triggers minor version bump)
- fix: Bug fix (triggers patch version bump)
- docs: Documentation changes only
- refactor: Code refactoring (no functional changes)
- test: Adding or updating tests
- perf: Performance improvements (triggers patch version bump)
- build: Build system changes
- ci: CI/CD configuration changes
- chore: Other changes that don't modify src or test files
Examples¶
# Feature commits
git commit -m "feat: add support for many-to-many relations"
git commit -m "feat(queries): implement OR operator for filters"
# Bug fix commits
git commit -m "fix: resolve connection pool deadlock"
git commit -m "fix(migrations): handle nullable foreign keys correctly"
# Documentation commits
git commit -m "docs: update installation instructions"
git commit -m "docs(api): add examples for transaction usage"
# Breaking changes (triggers major version bump)
git commit -m "feat!: change Model.create() to require explicit save()"
# OR
git commit -m "feat: redesign query API
BREAKING CHANGE: Query.filter() now requires Q objects instead of kwargs"
Commit Validation¶
The pre-commit hook will automatically validate your commit message format. Invalid commits will be rejected with an error message.
If you need to bypass the hook (not recommended), use:
Pull Request Process¶
-
Create a feature branch:
-
Make your changes and commit:
-
Push to your fork:
-
Open a Pull Request on GitHub
-
Wait for CI checks to pass:
- All linters must pass
- All tests must pass
-
Code must build on all platforms
-
Address review feedback if any
-
Merge once approved!
PR Requirements¶
- ✅ All CI checks pass
- ✅ Conventional commit format followed
- ✅ Tests added for new features
- ✅ Documentation updated
- ✅ No merge conflicts with main
Release Process¶
Ferro uses automated releases. You don't need to manually bump versions or update the changelog.
How Releases Work¶
- Commits are merged to main
-
Changelog is automatically updated with unreleased changes
-
Maintainer triggers the release workflow
The release process is fully automated. To trigger a new release:
- Via GitHub CLI (Recommended):
- Via GitHub Web UI: Go to the Actions tab, select the Release workflow, and click Run workflow.
This will:
- Automatically determine the next version from conventional commits.
- Update pyproject.toml and Cargo.toml.
- Finalize CHANGELOG.md.
- Create and push the Git tag.
- Trigger the Build & Publish workflow to upload wheels to PyPI.
- Package is automatically published to PyPI
- Cross-platform wheels are built
- Package is uploaded using trusted publishing
Version Bumping¶
Version bumps are determined by commit types:
- Major (1.0.0 → 2.0.0): Commits with
BREAKING CHANGE:in body or!after type - Minor (1.0.0 → 1.1.0): Commits with
feat:type - Patch (1.0.0 → 1.0.1): Commits with
fix:orperf:type
Code Style¶
Python¶
- Follow PEP 8 style guide
- Use type hints for all functions
- Maximum line length: 100 characters (enforced by Ruff)
- Use Pydantic for data validation
- Write docstrings for all public APIs
Rust¶
- Follow Rust style guidelines (enforced by rustfmt)
- Use
cargo clippywarnings as errors - Write documentation for public APIs
- Use descriptive variable names
- Prefer explicit types over inference in function signatures
Testing¶
Python Tests¶
Located in tests/ directory. Use pytest with async support:
import pytest
from ferro import Model, FerroField, connect
@pytest.mark.asyncio
async def test_create_model():
await connect("sqlite::memory:")
user = await User.create(name="Alice")
assert user.name == "Alice"
Rust Tests¶
Located alongside Rust code with #[cfg(test)]:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sql_generation() {
let query = generate_select_query("users");
assert_eq!(query, "SELECT * FROM users");
}
}
Project Structure¶
ferro/
├── src/
│ ├── ferro/ # Python package
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── queries.py
│ │ └── ...
│ └── lib.rs # Rust core
├── tests/ # Python tests
├── docs/ # Documentation
├── .github/
│ └── workflows/ # CI/CD workflows
├── Cargo.toml # Rust dependencies
├── pyproject.toml # Python dependencies
└── README.md
Getting Help¶
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: https://syn54x.github.io/ferro-orm
License¶
By contributing to Ferro, you agree that your contributions will be licensed under the same license as the project (Apache 2.0 OR MIT).