Contributing

Thank you for your interest in contributing to tagkit! This guide outlines project-specific requirements and workflows.

Setting Up Your Development Environment

Prerequisites

  • Python 3.10 or higher

  • Git

  • A GitHub account

We assume you are familiar with basic git workflows (forking, cloning, branching, committing, pushing, and pull requests). For a refresher, see GitHub’s documentation.

Fork and Clone the Repository

  • Fork the repository on GitHub.

  • Clone your fork to your local machine.

  • Add the original repository as an upstream remote.

Create a Development Environment

  1. Download and install uv. See the uv installation guide for instructions.

  2. Create and activate a virtual environment and install project and dev dependencies:

    uv sync
    

    This sets up a virtual environment, installs the required dependencies, and configures nox for running tests and other tasks.

Development Workflow

  • Create a new branch for your feature or bugfix.

  • Make your changes and add tests.

  • Ensure all tests pass:

    uv run nox -s tests
    
  • Check code quality:

    uv run nox -s lint
    
  • Commit and push your changes to your fork.

  • Open a pull request on GitHub and fill out the pull request template.

Coding Standards

Code Style

We use Ruff for both code formatting (Black-compatible rules) and linting. Format your code with:

uv run nox -s format

Check for linting issues with:

uv run nox -s lint

To check formatting without making changes:

uv run nox -s format -- --check

To run all linting and formatting checks:

uv run nox -s check

Type Hints

Use type hints throughout the codebase. Example:

def example_function(param1: str, param2: int) -> bool:
    """Example function with type hints."""
    return len(param1) > param2

Documentation

All public functions, classes, and methods should have docstrings following the Google style:

def example_function(param1: str, param2: int) -> bool:
    """Example function with Google style docstring.

    Args:
        param1: Description of param1
        param2: Description of param2

    Returns:
        Description of return value

    Raises:
        ValueError: When param1 is empty
    """
    if not param1:
        raise ValueError("param1 cannot be empty")
    return len(param1) > param2

Configuration Validation

All configuration files in the project are validated using Pydantic models. This ensures that configuration errors are caught early with clear error messages.

Configuration files:

  • src/tagkit/conf/registry.yaml — EXIF tag registry (validated by RegistryConfig)

  • src/tagkit/conf/formatting.yaml — Tag value formatting rules (validated by FormattingConfig)

  • tests/conf/test-img-metadata.json — Test image metadata (validated by ImageMetadataConfig)

  • tests/conf/doctest-img-metadata.json — Doctest image metadata (validated by ImageMetadataConfig)

Pydantic models are defined in src/tagkit/conf/models.py. When modifying configuration files:

  1. Ensure your changes conform to the Pydantic models

  2. Run uv run pytest tests/test_conf_schemas.py to validate all configs

  3. Update the Pydantic models if you need to change the schema

The validation happens automatically when configs are loaded and during CI testing.

Pre-commit Hooks

We optionally support pre-commit hooks to help catch formatting and linting issues before you commit. To enable pre-commit hooks locally, install pre-commit and run:

uv run pre-commit install

This will automatically run code quality checks on staged files before each commit. Use of pre-commit is optional; the GitHub Actions workflow is the final gatekeeper for code quality. All code must pass the checks enforced by CI before it is accepted, regardless of local pre-commit usage.

GitHub Actions

We use GitHub Actions for continuous integration (CI). All pull requests and pushes trigger automated workflows that check code formatting, linting, and run the test suite. Your code must pass all required checks before it can be merged.

The main checks include:

  • Code formatting and linting (using Ruff and pre-commit)

  • Running the test suite (including doctests)

You can view the status of these checks on your pull request page under the “Checks” tab.

Testing

Writing Tests

We use pytest for testing. Place your tests in the tests/ directory, mirroring the structure of the src/ directory.

Running Tests

Run the test suite with:

uv run nox -s test

Or run specific tests with:

uv run pytest tests/test_image_exif.py

Test Coverage

Check coverage with:

uv run nox -s coverage

Test Image Configuration Files

The project uses JSON configuration files to generate temporary JPG test images with specific EXIF metadata. There are two files with distinct purposes:

  • tests/conf/test-img-metadata.json — images for unit and integration tests (pytest).

  • tests/conf/doctest-img-metadata.json — images for documentation and docstring doctests.

Purpose:

  • Use test-img-metadata.json when writing tests that require particular EXIF metadata.

  • Use doctest-img-metadata.json only for examples referenced in documentation or docstrings; doctest examples must refer exclusively to files defined here so examples remain reproducible.

Schema (summary)

  • Top-level keys: filenames ending with .jpg or .jpeg.

  • Each filename maps to an object with a required tags array.

  • Each tag object must include:

    • id (integer) — EXIF tag ID

    • name (string) — tag name

    • value (string, number, or array) — tag value

    • ifd (string) — one of "0th", "1st", "Exif", "GPS"

Example entry:

{
  "filename.jpg": {
    "tags": [
      {
        "id": 271,
        "name": "Make",
        "value": "TestMake",
        "ifd": "0th"
      },
      {
        "id": 272,
        "name": "Model",
        "value": "TestModel",
        "ifd": "0th"
      }
    ]
  }
}

Usage in tests and doctests:

  • Pytest tests use the test_images fixture which creates a temporary directory containing images defined in tests/conf/test-img-metadata.json:

    def test_example(test_images):
        image_path = test_images / "minimal.jpg"
        # use the image in your test
    
  • Doctests (in MyST docs and docstrings) run in a temporary directory pre-populated with images from tests/conf/doctest-img-metadata.json. Reference these files by filename in examples so doctests remain stable.

Schema validation:

  • The schema is defined using Pydantic models in src/tagkit/conf/models.py.

  • CI validates both config files using tests/test_conf_schemas.py.

  • Quick local validation:

uv run pytest tests/test_conf_schemas.py -q

If you add or modify images used in doctests, update tests/conf/doctest-img-metadata.json first, then reference those files in docs/docstrings.

Building and Previewing Documentation

Building Documentation

Build the documentation locally with:

uv run nox -s docs

The built documentation will be in the docs/build/html/ directory.

Previewing Documentation

You can preview the documentation by opening docs/build/html/index.html in your browser or by running:

python -m http.server -d docs/build/html

Or preview live docs by running:

uv run nox -s livedocs

Then visit http://localhost:8000 in your browser.

Spell Checking Documentation

All Markdown documentation files (.md) in the docs/ directory are automatically spell checked using cspell as part of the pre-commit hooks and continuous integration process. This helps ensure that the documentation is clear and free of typographical errors.

Note: Only Markdown documentation files are spell checked. Source code files (e.g., .py) are not checked for spelling errors.

If you encounter a false positive (a word flagged as a typo that is actually correct, such as a technical term, acronym, or project-specific word), you can add it to the shared cspell.json configuration file in the project root. This file contains a list of allowed custom words for the project. To add a new word:

  1. Open cspell.json in the project root.

  2. Add your word to the "words" array, keeping the list in alphabetical order if possible.

  3. Commit the change along with your documentation update.

Example:

{
  "version": "0.2",
  "words": [
    "apidocs",
    "autodoc",
    "yournewword"  // Add your word here
  ]
}

This ensures that the word will not be flagged as a typo in future documentation builds or pre-commit checks.

Tip for VS Code users:

If you use the CSpell extension in VS Code, you can quickly add a flagged word to the project’s cspell.json using the Quick Fix menu:

  1. Hover over the underlined word flagged as a spelling error in your Markdown file.

  2. Click the lightbulb icon, right-click and select “Quick Fix…”, or press Ctrl+. (Windows/Linux) or Cmd+. (Mac) to open the Quick Fix menu.

  3. Choose “Add to cspell.json” from the menu.

  4. The word will be automatically added to the words array in cspell.json in your project root.

Do not choose “Add to workspace settings” or “Add to user settings” as this will not update the project’s shared configuration file.

Getting Help

If you need help with contributing to tagkit, you can:

  • Open an issue on GitHub

  • Reach out to the maintainers

  • Join our community discussions

Thank you for contributing to tagkit!