Files
Patel Dhruv 49fd3f3faf Add new skill: Python PyPI Package Builder (#1302)
* Add python-pypi-package-builder skill for Python packaging

- Created `SKILL.md` defining decision-driven workflow for building, testing, versioning, and publishing Python packages.
- Added reference modules covering PyPA packaging, architecture patterns, CI/CD, testing, versioning strategy, and release governance.
- Implemented scaffold script to generate complete project structure with pyproject.toml, CI workflows, tests, and configuration.
- Included support for multiple build backends (setuptools_scm, hatchling, flit, poetry) with clear decision rules.
- Added secure release practices including tag-based versioning, branch protection, and OIDC Trusted Publishing.

* fix: correct spelling issues detected by codespell
2026-04-09 10:36:17 +10:00

13 KiB

Community Docs, PR Checklist, Anti-patterns, and Release Checklist

Table of Contents

  1. README.md required sections
  2. Docstrings — Google style
  3. CONTRIBUTING.md template
  4. SECURITY.md template
  5. GitHub Issue Templates
  6. PR Checklist
  7. Anti-patterns to avoid
  8. Master Release Checklist

1. README.md Required Sections

A good README is the single most important file for adoption. Users decide in 30 seconds whether to use your library based on the README.

# your-package

> One-line description — what it does and why it's useful.

[![PyPI version](https://badge.fury.io/py/your-package.svg)](https://pypi.org/project/your-package/)
[![Python Versions](https://img.shields.io/pypi/pyversions/your-package)](https://pypi.org/project/your-package/)
[![CI](https://github.com/you/your-package/actions/workflows/ci.yml/badge.svg)](https://github.com/you/your-package/actions/workflows/ci.yml)
[![Coverage](https://codecov.io/gh/you/your-package/branch/master/graph/badge.svg)](https://codecov.io/gh/you/your-package)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Installation

pip install your-package

# With Redis backend:
pip install "your-package[redis]"

## Quick Start

(A copy-paste working example — no setup required to run it)

from your_package import YourClient

client = YourClient(api_key="sk-...")
result = client.process({"input": "value"})
print(result)

## Features

- Feature 1
- Feature 2

## Configuration

| Parameter | Type | Default | Description |
|---|---|---|—--|
| api_key | str | required | Authentication credential |
| timeout | int | 30 | Request timeout in seconds |
| retries | int | 3 | Number of retry attempts |

## Backends

Brief comparison — in-memory vs Redis — and when to use each.

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md)

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)

## License

MIT — see [LICENSE](./LICENSE)

2. Docstrings — Google Style

Use Google-style docstrings for every public class, method, and function. IDEs display these as tooltips, mkdocs/sphinx can auto-generate documentation from them, and they convey intent clearly to contributors.

class YourClient:
    """
    Main client for <purpose>.

    Args:
        api_key: Authentication credential.
        timeout: Request timeout in seconds. Defaults to 30.
        retries: Number of retry attempts. Defaults to 3.

    Raises:
        ValueError: If api_key is empty or timeout is non-positive.

    Example:
        >>> from your_package import YourClient
        >>> client = YourClient(api_key="sk-...")
        >>> result = client.process({"input": "value"})
    """

3. CONTRIBUTING.md

# Contributing to your-package

## Development Setup

git clone https://github.com/you/your-package
cd your-package
pip install -e ".[dev]"
pre-commit install

## Running Tests

pytest

## Running Linting

ruff check .
black . --check
mypy your_package/

## Submitting a PR

1. Fork the repository
2. Create a feature branch: `git checkout -b feat/your-feature`
3. Make changes with tests
4. Ensure CI passes: `pre-commit run --all-files && pytest`
5. Update `CHANGELOG.md` under `[Unreleased]`
6. Open a PR — use the PR template

## Commit Message Format (Conventional Commits)

- `feat: add Redis backend`
- `fix: correct retry behavior on timeout`
- `docs: update README quick start`
- `chore: bump ruff to 0.5`
- `test: add edge cases for memory backend`

## Reporting Bugs

Use the GitHub issue template. Include Python version, package version,
and a minimal reproducible example.

4. SECURITY.md

# Security Policy

## Supported Versions

| Version | Supported |
|---|---|
| 1.x.x   | Yes       |
| < 1.0   | No        |

## Reporting a Vulnerability

Do NOT open a public GitHub issue for security vulnerabilities.

Report via: GitHub private security reporting (preferred)
or email: security@yourdomain.com

Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)

We aim to acknowledge within 48 hours and resolve within 14 days.

5. GitHub Issue Templates

.github/ISSUE_TEMPLATE/bug_report.md

---
name: Bug Report
about: Report a reproducible bug
labels: bug
---

**Python version:**
**Package version:**

**Describe the bug:**

**Minimal reproducible example:**
```python
# paste code here

Expected behavior:

Actual behavior:


### `.github/ISSUE_TEMPLATE/feature_request.md`

```markdown
---
name: Feature Request
about: Suggest a new feature or enhancement
labels: enhancement
---

**Problem this would solve:**

**Proposed solution:**

**Alternatives considered:**

6. PR Checklist

All items must be checked before requesting review. CI must be fully green.

Code Quality Gates

[ ] ruff check . — zero errors
[ ] black . --check — zero formatting issues
[ ] isort . --check-only — imports sorted correctly
[ ] mypy your_package/ — zero type errors
[ ] pytest — all tests pass
[ ] Coverage >= 80% (enforced by fail_under in pyproject.toml)
[ ] All GitHub Actions workflows green

Structure

[ ] pyproject.toml: name, dynamic/version, description, requires-python, license, authors,
    keywords (10+), classifiers, dependencies, all [project.urls] filled in
[ ] dynamic = ["version"] if using setuptools_scm
[ ] [tool.setuptools_scm] with local_scheme = "no-local-version"
[ ] setup.py shim present (if using setuptools_scm)
[ ] py.typed marker file exists in the package directory (empty file)
[ ] py.typed listed in [tool.setuptools.package-data]
[ ] "Typing :: Typed" classifier in pyproject.toml
[ ] __init__.py has __all__ listing all public symbols
[ ] __version__ via importlib.metadata (not hardcoded string)

Testing

[ ] conftest.py has shared fixtures for client and backend
[ ] Core happy path tested
[ ] Error conditions and edge cases tested
[ ] Each backend tested independently in isolation
[ ] Redis backend tested in separate CI job with redis service (if applicable)
[ ] asyncio_mode = "auto" in pyproject.toml (for async tests)
[ ] fetch-depth: 0 in all CI checkout steps

Optional Backend (if applicable)

[ ] BaseBackend abstract class defines the interface
[ ] MemoryBackend works with zero extra deps
[ ] RedisBackend raises ImportError with clear pip install hint if redis not installed
[ ] Both backends unit-tested independently
[ ] redis extra declared in [project.optional-dependencies]
[ ] README shows both install paths (base and [redis])

Changelog & Docs

[ ] CHANGELOG.md updated under [Unreleased]
[ ] README has: description, install, quick start, config table, badges, license
[ ] All public symbols have Google-style docstrings
[ ] CONTRIBUTING.md: dev setup, test/lint commands, PR instructions
[ ] SECURITY.md: supported versions, reporting process
[ ] .github/ISSUE_TEMPLATE/bug_report.md
[ ] .github/ISSUE_TEMPLATE/feature_request.md

CI/CD

[ ] ci.yml: lint + mypy + test matrix (all supported Python versions)
[ ] ci.yml: separate job for Redis backend with redis service
[ ] publish.yml: triggered on v*.*.* tags, uses Trusted Publishing (OIDC)
[ ] fetch-depth: 0 in all workflow checkout steps
[ ] pypi environment created in GitHub repo Settings → Environments
[ ] No API tokens in repository secrets

7. Anti-patterns to Avoid

Anti-pattern Why it's bad Correct approach
__version__ = "1.0.0" hardcoded with setuptools_scm Goes stale after first git tag Use importlib.metadata.version()
Missing fetch-depth: 0 in CI checkout setuptools_scm can't find tags → version = 0.0.0+dev Add fetch-depth: 0 to every checkout step
local_scheme not set +g<hash> suffix breaks PyPI uploads (local versions rejected) local_scheme = "no-local-version"
Missing py.typed file IDEs and mypy don't see package as typed Create empty py.typed in package root
py.typed not in package-data File missing from installed wheel — useless Add to [tool.setuptools.package-data]
Importing optional dep at module top ImportError on import your_package for all users Lazy import inside the function/class that needs it
Duplicating metadata in setup.py Conflicts with pyproject.toml; drifts Keep setup.py as 3-line shim only
No fail_under in coverage config Coverage regressions go unnoticed Set fail_under = 80
No mypy in CI Type errors silently accumulate Add mypy step to ci.yml
API tokens in GitHub Secrets for PyPI Security risk, rotation burden Use Trusted Publishing (OIDC)
Committing directly to main/master Bypasses CI checks Enforce via no-commit-to-branch pre-commit hook
Missing [Unreleased] section in CHANGELOG Changes pile up and get forgotten at release time Keep [Unreleased] updated every PR
Pinning exact dep versions in a library Breaks dependency resolution for users Use >= lower bounds only; avoid ==
No __all__ in __init__.py Users can accidentally import internal helpers Declare __all__ with every public symbol
from your_package import * in tests Tests pass even when imports are broken Always use explicit imports
No SECURITY.md No path for responsible vulnerability disclosure Add file with response timeline
Any everywhere in type hints Defeats mypy entirely Use object for truly arbitrary values
Union return types Forces every caller to write isinstance() checks Return concrete types; use overloads
setup.cfg + pyproject.toml both active Conflicts and confusing for contributors Migrate everything to pyproject.toml
Releasing on untagged commits Version number is meaningless Always tag before release
Not testing on all supported Python versions Breakage discovered by users, not you Matrix test in CI
license = {text = "MIT"} (old form) Deprecated; PEP 639 uses SPDX strings license = "MIT"
No issue templates Bug reports are inconsistent Add bug_report.md + feature_request.md

8. Master Release Checklist

Run through every item before pushing a release tag. CI must be fully green.

Code Quality

[ ] ruff check . — zero errors
[ ] ruff format . --check — zero formatting issues
[ ] mypy src/your_package/ — zero type errors
[ ] pytest — all tests pass
[ ] Coverage >= 80% (fail_under enforced in pyproject.toml)
[ ] All GitHub Actions CI jobs green (lint + test matrix)

Project Structure

[ ] pyproject.toml — name, description, requires-python, license (SPDX string), authors,
    keywords (10+), classifiers (Python versions + Typing :: Typed), urls (all 5 fields)
[ ] dynamic = ["version"] set (if using setuptools_scm or hatch-vcs)
[ ] [tool.setuptools_scm] with local_scheme = "no-local-version"
[ ] setup.py shim present (if using setuptools_scm)
[ ] py.typed marker file exists (empty file in package root)
[ ] py.typed listed in [tool.setuptools.package-data]
[ ] "Typing :: Typed" classifier in pyproject.toml
[ ] __init__.py has __all__ listing all public symbols
[ ] __version__ reads from importlib.metadata (not hardcoded)

Testing

[ ] conftest.py has shared fixtures for client and backend
[ ] Core happy path tested
[ ] Error conditions and edge cases tested
[ ] Each backend tested independently in isolation
[ ] asyncio_mode = "auto" in pyproject.toml (for async tests)
[ ] fetch-depth: 0 in all CI checkout steps

CHANGELOG and Docs

[ ] CHANGELOG.md: [Unreleased] entries moved to [x.y.z] - YYYY-MM-DD
[ ] README has: description, install commands, quick start, config table, badges
[ ] All public symbols have Google-style docstrings
[ ] CONTRIBUTING.md: dev setup, test/lint commands, PR instructions
[ ] SECURITY.md: supported versions, reporting process with timeline

Versioning

[ ] All CI checks pass on the commit you plan to tag
[ ] CHANGELOG.md updated and committed
[ ] Git tag follows format v1.2.3 (semver, v prefix)
[ ] No stale local_scheme suffixes will appear in the built wheel name

CI/CD

[ ] ci.yml: lint + mypy + test matrix (all supported Python versions)
[ ] publish.yml: triggered on v*.*.* tags, uses Trusted Publishing (OIDC)
[ ] pypi environment created in GitHub repo Settings → Environments
[ ] No API tokens stored in repository secrets

The Release Command Sequence

# 1. Run full local validation
ruff check . ; ruff format . --check ; mypy src/your_package/ ; pytest

# 2. Update CHANGELOG.md — move [Unreleased] to [x.y.z]
# 3. Commit the changelog
git add CHANGELOG.md
git commit -m "chore: prepare release vX.Y.Z"

# 4. Tag and push — this triggers publish.yml automatically
git tag vX.Y.Z
git push origin main --tags

# 5. Monitor: https://github.com/<you>/<pkg>/actions
# 6. Verify: https://pypi.org/project/your-package/