Skip to content
Merged
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
260 changes: 260 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Contributing to PyHamcrest

Thank you for your interest in contributing to PyHamcrest! This guide will help you get started.

## Table of Contents

- [Development Setup](#development-setup)
- [Running Tests](#running-tests)
- [Code Style and Linting](#code-style-and-linting)
- [Type Checking](#type-checking)
- [Adding Changelog Entries](#adding-changelog-entries)
- [Submitting Pull Requests](#submitting-pull-requests)
- [Release Process](#release-process-for-maintainers)

## Development Setup

### Prerequisites

- Python 3.10 or later (Python 3.14 recommended for development)
- Git
- `uv` (installed according to the [uv installation instructions](https://docs.astral.sh/uv/#installation))
- [prek](https://prek.j178.dev/) (or [pre-commit](https://pre-commit.com/), optionally)

### Installation

1. Fork and clone the repository:

```bash
git clone https://github.com/YOUR-USERNAME/PyHamcrest.git
cd PyHamcrest
```

2. Install pre-commit hooks:

```bash
prek install
```

This ensures code style checks run automatically before each commit.

### (Optional) use a virtualenv

With `uv` installed, prefixing all commands with `uv run` ensures they run with the project dependencies. If you want to work in a virtualenv, so that you can run bare commands, you can do so this way:

```shell
uv venv
uv pip install -e '.[dev]'
. .venv/bin/activate
```

In all instructions below, you can remove the `uv run` and `uv tool run` prefixes.

## Running Tests

### Quick Test Run

Run tests with pytest:

```bash
uv run pytest
```

Run a specific test file:

```bash
uv run pytest tests/hamcrest_unit_test/core/isequal_test.py
```

Run a specific test:

```bash
uv run pytest tests/hamcrest_unit_test/core/isequal_test.py::TestClass::test_name
```

### Test Coverage

Run tests with coverage:

```bash
uv run coverage run -m pytest
uv run coverage report
```

### Testing Across Python Versions

We use [tox](https://tox.wiki/) to test across multiple Python versions:

```bash
# Test all available Python versions (skips missing interpreters)
uv tool run tox -s

# Test specific Python version
uv tool run tox -e py312

# Test with numpy support
uv tool run tox -e py312-numpy
```

**IMPORTANT**: Always run `uv tool run tox -s` before submitting a pull request to verify tests pass across all available Python versions. The `-s` flag skips missing interpreters.

### Supported Python Versions

- Python 3.10, 3.11, 3.12, 3.13, 3.14
- PyPy 3.11

## Code Style and Linting

PyHamcrest uses automated code formatting and linting:

### Auto-formatting

Code is formatted with [Black](https://black.readthedocs.io/) and [Ruff](https://docs.astral.sh/ruff/).

Run pre-commit hooks on all files:

```bash
prek run --all-files
```

Or via tox:

```bash
uv tool run tox -e lint
```

### Style Notes

- Line length: 100 characters
- The codebase intentionally uses star imports (`from module import *`) to provide a clean public API
- Import order is managed by isort with a custom "hamcrests" profile

## Type Checking

PyHamcrest includes type annotations and is checked with [mypy](https://mypy-lang.org/).

Run type checking:

```bash
uv run mypy src/
```

Or via tox:

```bash
uv tool run tox -e typing
```

### Type Hint Tests

Type hints are tested using mypy with special test files in `tests/type-hinting/`. These files use a YAML format to test that type annotations work correctly. If you're adding or modifying type annotations, consider adding corresponding type tests.

## Adding Changelog Entries

**REQUIRED**: Every pull request that changes functionality must include a changelog entry.

**Exception**: Documentation-only changes (README, CONTRIBUTING, etc.) do not require changelog entries.

PyHamcrest uses [towncrier](https://towncrier.readthedocs.io/) to manage the changelog.

### How to Add a Changelog Entry

For every PR that changes code or functionality, create a changelog fragment file in the `changelog.d/` directory:

**Format**: `changelog.d/{issue_or_pr_number}.{category}.rst`

**Categories**:
- `feature` - New features or enhancements
- `bugfix` - Bug fixes
- `misc` - Miscellaneous changes (CI, docs, refactoring, etc.)

**Example**: If your PR is #123 and adds a new feature, create:

`changelog.d/123.feature.rst`:
```rst
Add support for matching custom objects with has_attributes matcher.
```

**Tips**:
- Keep descriptions concise and user-focused
- Write in the past tense ("Added", "Fixed", not "Add", "Fix")
- Don't include implementation details unless relevant to users
- The issue/PR number in the filename will be automatically linked in the generated changelog

## Submitting Pull Requests

### Before Submitting

1. ✅ Run tests: `uv tool run tox -s`
2. ✅ Run linting: `prek run --all-files`
3. ✅ Run type checking: `uv tool run tox -e typing`
4. ✅ **Add changelog entry (REQUIRED for code changes)** - see [Adding Changelog Entries](#adding-changelog-entries)
5. ✅ Update documentation if needed

### CI Checks

When you create a pull request, GitHub Actions will automatically run:

- Tests on Python 3.10, 3.11, 3.12, 3.13, 3.14, and PyPy 3.11
- Tests on Linux, macOS, and Windows
- Linting checks
- Type checking
- Documentation build

The CI workflow runs on:
- All pull requests to `main` or `master`
- Pushes to `main`, `master`, or branches starting with `ci-testing-*`

### PR Guidelines

- **Branch naming**: Use descriptive names like `fix-issue-123` or `add-new-matcher`
- **Commit messages**: Write clear, descriptive commit messages
- **PR description**: Explain what changes you made and why
- **Link issues**: Reference related issues in your PR description (e.g., "Fixes #123")
- **Keep PRs focused**: One feature or fix per PR makes review easier

## Release Process (for Maintainers)

PyHamcrest uses automated releases via git tags.

### Creating a Release

1. Ensure all PRs for the release are merged to `main`

2. Run the release script:

```bash
./release.sh V2.2.0
```

This script:
- Creates a git tag (e.g., `V2.2.0`)
- Runs `uv run towncrier build` to generate the changelog from fragments in `changelog.d/`
- Re-tags to include the changelog changes
- Prompts you to push the tag

3. Push the tag to trigger the release:

```bash
git push origin --tags "V2.2.0"
```

4. GitHub Actions automatically:
- Builds the package (wheel and sdist)
- Publishes to PyPI using trusted publishing
- Creates a GitHub release

### Versioning

- Format: `V{major}.{minor}.{patch}` (e.g., `V2.1.0`)
- Version is automatically derived from git tags using [hatch-vcs](https://github.com/ofek/hatch-vcs)
- No manual version bumping needed in code

## Questions?

If you have questions or need help:

- 💬 [Open a discussion](https://github.com/hamcrest/PyHamcrest/discussions)
- 🐛 [Report an issue](https://github.com/hamcrest/PyHamcrest/issues)

Thank you for contributing! 🎉
Loading