Files
claude-engineering-plugin/plugins/compound-engineering/skills/python-package-writer/SKILL.md
John Lamb fe3b1eee16 Merge upstream v2.67.0 with fork customizations preserved
Synced 79 commits from EveryInc/compound-engineering-plugin upstream while
preserving fork-specific customizations (Python/FastAPI pivot, Zoominfo-internal
review agents, deploy-wiring operational lessons, custom personas).

## Triage decisions (15 conflicts resolved)

Keep deleted (7) -- fork already removed these in prior cleanups:
- agents/design/{design-implementation-reviewer,design-iterator,figma-design-sync}
  (no fork successor; backend-Python focus doesn't need UI/Figma agents)
- agents/docs/ankane-readme-writer (replaced by python-package-readme-writer)
- agents/review/{data-migration-expert,performance-oracle,security-sentinel}
  (replaced by *-reviewer naming convention: data-migrations-reviewer,
  performance-reviewer, security-reviewer)

Keep local (1):
- agents/workflow/lint.md (Python tooling: ruff/mypy/djlint/bandit; upstream
  deleted the file). Fixed pre-existing duplicate "2." numbering bug.

Restore from upstream (1):
- agents/review/data-integrity-guardian.md (kept for GDPR/CCPA privacy
  compliance angle not covered by data-migrations-reviewer)

Merge both (6) -- upstream structural wins layered with fork intent:
- agents/research/best-practices-researcher.md (upstream <examples> removal +
  fork's Rails/Ruby -> Python/FastAPI translations)
- skills/ce-brainstorm/SKILL.md (universal-brainstorming routing + Slack
  context + non-obvious angles + fork's Deploy wiring flag)
- skills/ce-plan/SKILL.md (universal-planning routing + planning-bootstrap +
  fork's two Deploy wiring check bullets)
- skills/ce-review/SKILL.md (Run ID, model tiering haiku->sonnet, compact-JSON
  artifact contract, file-type awareness, cli-readiness-reviewer + fork's
  zip-agent-validator, design-conformance-reviewer, Stage 6 Zip Agent
  Validation)
- skills/ce-review/references/persona-catalog.md (cli-readiness row + adversarial
  refinement + fork's Language & Framework Conditional layer; 22 personas total)
- skills/ce-work/SKILL.md (Parallel Safety Check, parallel-subagent constraints,
  Phase 3-4 compression + fork's deploy-values self-review row, with duplicate
  checklist bullet collapsed to single occurrence)

## Auto-applied (no triage needed)

- 225 remote-only files: accepted as-is (new docs, brainstorms, plans,
  upstream skills, tests, scripts)
- 70 local-only files: 46 preserved as-is (kieran-python, tiangolo-fastapi,
  zip-agent-validator, design-conformance-reviewer, essay/proof commands,
  excalidraw-png-export, etc.); 24 stayed deleted (dhh-rails-style,
  andrew-kane-gem-writer, dspy-ruby Ruby skills no longer needed)

## README updated

- Removed Design section (3 deleted agents)
- Removed deleted Review entries (data-migration-expert, dhh-rails-reviewer,
  kieran-rails-reviewer, performance-oracle, security-sentinel)
- Added new Review entries: design-conformance-reviewer, previous-comments-reviewer,
  tiangolo-fastapi-reviewer, zip-agent-validator
- Workflow: added lint
- Docs: replaced ankane-readme-writer with python-package-readme-writer

## Known issues (not introduced by merge decisions)

- 9 detect-project-type.sh tests fail on macOS bash 3.2 (script uses
  `declare -A` which requires bash 4+). Upstream regression in commit 070092d
  (#568). Resolution: install bash 4+ via `brew install bash` locally;
  upstream fix tracked separately.
- 2 review-skill-contract tests reference deleted agents (dhh-rails-reviewer,
  data-migration-expert). Pre-existing fork inconsistency, not new.

bun run release:validate: passes (46 agents, 51 skills, 0 MCP servers)
2026-04-17 17:24:41 -05:00

9.8 KiB

name, description
name description
python-package-writer This skill should be used when writing Python packages following production-ready patterns and philosophy. It applies when creating new Python packages, refactoring existing packages, designing package APIs, or when clean, minimal, well-tested Python library code is needed. Triggers on requests like "create a package", "write a Python library", "design a package API", or mentions of PyPI publishing.

Python Package Writer

Write Python packages following battle-tested patterns from production-ready libraries. Emphasis on simplicity, minimal dependencies, comprehensive testing, and modern packaging standards (pyproject.toml, type hints, pytest).

Core Philosophy

Simplicity over cleverness. Zero or minimal dependencies. Explicit code over magic. Framework integration without framework coupling. Every pattern serves production use cases.

Package Structure (src layout)

The modern recommended layout with proper namespace isolation:

package-name/
├── pyproject.toml          # All metadata and configuration
├── README.md
├── LICENSE
├── py.typed                # PEP 561 marker for type hints
├── src/
│   └── package_name/       # Actual package code
│       ├── __init__.py     # Entry point, exports, version
│       ├── core.py         # Core functionality
│       ├── models.py       # Data models (Pydantic/dataclasses)
│       ├── exceptions.py   # Custom exceptions
│       └── py.typed        # Type hint marker (also here)
└── tests/
    ├── conftest.py         # Pytest fixtures
    ├── test_core.py
    └── test_models.py

Entry Point Structure

Every package follows this pattern in src/package_name/__init__.py:

"""Package description - one line."""

# Public API exports
from package_name.core import Client, process_data
from package_name.models import Config, Result
from package_name.exceptions import PackageError, ValidationError

__version__ = "1.0.0"
__all__ = [
    "Client",
    "process_data",
    "Config",
    "Result",
    "PackageError",
    "ValidationError",
]

pyproject.toml Configuration

Modern packaging with all metadata in one file:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "package-name"
version = "1.0.0"
description = "Brief description of what the package does"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
    { name = "Your Name", email = "you@example.com" }
]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Typing :: Typed",
]
keywords = ["keyword1", "keyword2"]

# Zero or minimal runtime dependencies
dependencies = []

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-cov>=4.0",
    "ruff>=0.4",
    "mypy>=1.0",
]
# Optional integrations
fastapi = ["fastapi>=0.100", "pydantic>=2.0"]

[project.urls]
Homepage = "https://github.com/username/package-name"
Documentation = "https://package-name.readthedocs.io"
Repository = "https://github.com/username/package-name"
Changelog = "https://github.com/username/package-name/blob/main/CHANGELOG.md"

[tool.hatch.build.targets.wheel]
packages = ["src/package_name"]

[tool.ruff]
target-version = "py310"
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]

[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_ignores = true

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra -q"

[tool.coverage.run]
source = ["src/package_name"]
branch = true

Configuration Pattern

Use module-level configuration with dataclasses or simple attributes:

# src/package_name/config.py
from dataclasses import dataclass, field
from os import environ
from typing import Any


@dataclass
class Config:
    """Package configuration with sensible defaults."""

    timeout: int = 30
    retries: int = 3
    api_key: str | None = field(default=None)
    debug: bool = False

    def __post_init__(self) -> None:
        # Environment variable fallbacks
        if self.api_key is None:
            self.api_key = environ.get("PACKAGE_API_KEY")


# Module-level singleton (optional)
_config: Config | None = None


def get_config() -> Config:
    """Get or create the global config instance."""
    global _config
    if _config is None:
        _config = Config()
    return _config


def configure(**kwargs: Any) -> Config:
    """Configure the package with custom settings."""
    global _config
    _config = Config(**kwargs)
    return _config

Error Handling

Simple hierarchy with informative messages:

# src/package_name/exceptions.py
class PackageError(Exception):
    """Base exception for all package errors."""
    pass


class ConfigError(PackageError):
    """Invalid configuration."""
    pass


class ValidationError(PackageError):
    """Data validation failed."""

    def __init__(self, message: str, field: str | None = None) -> None:
        self.field = field
        super().__init__(message)


class APIError(PackageError):
    """External API error."""

    def __init__(self, message: str, status_code: int | None = None) -> None:
        self.status_code = status_code
        super().__init__(message)


# Validate early with ValueError
def process(data: bytes) -> str:
    if not data:
        raise ValueError("Data cannot be empty")
    if len(data) > 1_000_000:
        raise ValueError(f"Data too large: {len(data)} bytes (max 1MB)")
    return data.decode("utf-8")

Type Hints

Always use type hints with modern syntax (Python 3.10+):

# Use built-in generics, not typing module
from collections.abc import Callable, Iterator, Mapping, Sequence

def process_items(
    items: list[str],
    transform: Callable[[str], str] | None = None,
    *,
    batch_size: int = 100,
) -> Iterator[str]:
    """Process items with optional transformation."""
    for item in items:
        if transform:
            yield transform(item)
        else:
            yield item


# Use | for unions, not Union
def get_value(key: str) -> str | None:
    return _cache.get(key)


# Use Self for return type annotations (Python 3.11+)
from typing import Self

class Client:
    def configure(self, **kwargs: str) -> Self:
        # Update configuration
        return self

Testing (pytest)

# tests/conftest.py
import pytest
from package_name import Config, configure


@pytest.fixture
def config() -> Config:
    """Fresh config for each test."""
    return configure(timeout=5, debug=True)


@pytest.fixture
def sample_data() -> bytes:
    """Sample input data."""
    return b"test data content"


# tests/test_core.py
import pytest
from package_name import process_data, PackageError


class TestProcessData:
    """Tests for process_data function."""

    def test_basic_functionality(self, sample_data: bytes) -> None:
        result = process_data(sample_data)
        assert result == "test data content"

    def test_empty_input_raises_error(self) -> None:
        with pytest.raises(ValueError, match="cannot be empty"):
            process_data(b"")

    def test_with_transform(self, sample_data: bytes) -> None:
        result = process_data(sample_data, transform=str.upper)
        assert result == "TEST DATA CONTENT"


class TestConfig:
    """Tests for configuration."""

    def test_defaults(self) -> None:
        config = Config()
        assert config.timeout == 30
        assert config.retries == 3

    def test_env_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("PACKAGE_API_KEY", "test-key")
        config = Config()
        assert config.api_key == "test-key"

FastAPI Integration

Optional FastAPI integration pattern:

# src/package_name/fastapi.py
"""FastAPI integration - only import if FastAPI is installed."""
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from fastapi import FastAPI

from package_name.config import get_config


def init_app(app: "FastAPI") -> None:
    """Initialize package with FastAPI app."""
    config = get_config()

    @app.on_event("startup")
    async def startup() -> None:
        # Initialize connections, caches, etc.
        pass

    @app.on_event("shutdown")
    async def shutdown() -> None:
        # Cleanup resources
        pass


# Usage in FastAPI app:
# from package_name.fastapi import init_app
# init_app(app)

Anti-Patterns to Avoid

  • __getattr__ magic (use explicit imports)
  • Global mutable state (use configuration objects)
  • * imports in __init__.py (explicit __all__)
  • Many runtime dependencies
  • Committing .venv/ or __pycache__/
  • Not including py.typed marker
  • Using setup.py (use pyproject.toml)
  • Mixing src layout and flat layout
  • print() for debugging (use logging)
  • Bare except: clauses

Reference Files

For deeper patterns, see: