home / skills / dropseed / plain / annotations

annotations skill

/.claude/skills/annotations

This skill guides adding type annotations to Python projects, improving coverage, readability, and type safety for public APIs and core functionality.

npx playbooks add skill dropseed/plain --skill annotations

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
1.4 KB
---
name: annotations
description: Workflow for adding type annotations to Plain packages. Use this when adding or improving type coverage.
---

# Type Annotation Workflow

We are gradually adding type annotations using Python 3.13+.

## Workflow

1. **Check current coverage**:

    ```
    uv run plain code annotations <directory> --details
    ```

2. **Add annotations**: Focus on function/method signatures (parameters and return types)

3. **Type check**:

    ```
    ./scripts/type-check <directory>
    ```

4. **Format**: `./scripts/fix`

5. **Test**: `./scripts/test <package>`

6. **Verify improvement**:

    ```
    uv run plain code annotations <directory>
    ```

7. **Add to validation**: Once a directory reaches 100% coverage, add it to `FULLY_TYPED_PATHS` in `scripts/type-validate`

## Guidelines

- Add `from __future__ import annotations` when necessary
- Focus on public APIs and user-facing methods first
- Don't annotate `__init__` return types (type checkers infer `None`)
- Use explicit `return None` for functions with `-> Type | None` return type
- Some Django-style ORM patterns are inherently difficult to type - that's okay
- Goal is progress, not perfection

## Example

```bash
# Check coverage
uv run plain code annotations plain/plain/assets --details

# After adding annotations...
./scripts/type-check plain/plain/assets
./scripts/fix
./scripts/test plain
uv run plain code annotations plain/plain/assets  # Should show 100%
```

Overview

This skill provides a practical workflow for adding and improving Python type annotations across Plain packages. It guides you from measuring current coverage to committing fully-typed directories, with checks, formatting, and tests included. The goal is incremental, high-impact annotation focused on public APIs.

How this skill works

The workflow starts by measuring annotation coverage for a target directory. You then add or improve function and method signatures, run the repository type checker, apply automatic formatting, and execute tests. After verifying coverage gains, directories that reach 100% are added to the validation list so they remain fully typed.

When to use it

  • Adding initial type annotations to a Plain package or module
  • Improving type coverage before a release or refactor
  • Onboarding contributors to consistent typing practices
  • Validating that a directory has reached 100% coverage for inclusion in CI checks

Best practices

  • Run the coverage check before changing code to understand the current baseline
  • Prioritize public APIs and user-facing methods before internal helpers
  • Add from __future__ import annotations where required to avoid runtime imports
  • Avoid annotating __init__ return types; type checkers infer None
  • Use explicit return None when a function uses -> Type | None to make intent clear
  • Accept imperfect typing for difficult ORM or dynamic patterns—progress over perfection

Example use cases

  • Measure annotation coverage for a package: uv run plain code annotations <directory> --details
  • Add type hints to functions and methods, then run ./scripts/type-check <directory>
  • Format changes and apply repository fixes with ./scripts/fix before testing
  • Run package tests: ./scripts/test <package> to ensure behavior unchanged
  • Once coverage reaches 100%, add directory path to FULLY_TYPED_PATHS in scripts/type-validate

FAQ

Do I need to annotate private helper functions right away?

No. Focus on public APIs and user-facing methods first; annotate private helpers as needed or when touched during refactors.

Should I always add from __future__ import annotations?

Add it when forward references or postponed evaluation avoids import cycles or simplifies annotations; it is recommended for consistency with Python 3.13+ typing.