Skip to main content

python

Python development patterns, testing, async, data modeling, and debugging. Triggers on "python", "pytest", "pydantic", "async", "fastapi", "type hint", "dataclass".

Source facts

Repository
pypeaday/dotfiles
Last source activity
October 5, 2026 at 17:30
Detected SKILL.md language
English
Stars
2
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
python
description
Python development patterns, testing, async, data modeling, and debugging. Triggers on "python", "pytest", "pydantic", "async", "fastapi", "type hint", "dataclass".
# Python Development Skill ## Project Structure ### Standard src Layout ``` project/ ├── pyproject.toml ├── src/ │ └── mypackage/ │ ├── __init__.py │ ├── core.py │ ├── models.py │ └── exceptions.py ├── tests/ │ ├── conftest.py │ ├── test_core.py │ └── test_models.py └── README.md ``` ### When to Use What | Structure | Use Case | |-----------|----------| | src layout | Libraries, packages published to PyPI | | flat layout | Internal apps, scripts, services | | PEP 723 script | Single-file tools (`uv run` inline deps) | ## Type Hints ### Essential Patterns ```python from typing import Any from collections.abc import Sequence, Mapping, Iterator # Prefer builtins (3.10+) def process(items: list[str], config: dict[str, Any]) -> bool: ... # Union syntax (3.10+) def find(key: str) -> str | None: ... # Callable from collections.abc import Callable def retry(fn: Callable[..., Any], attempts: int = 3) -> Any: ... # TypeVar for generics from typing import TypeVar T = TypeVar("T") def first(items: Sequence[T]) -> T | None: return items[0] if items else None ``` ### When NOT to Type - Throwaway scripts - Test internals (type the fixtures, not every assertion) - When `Any` would be the only option — rethink the design instead ## Data Modeling ### Decision Tree ``` Need validation + serialization? → Pydantic BaseModel Need lightweight struct? → dataclass Need dict compatibility? → TypedDict Need immutable? → frozen dataclass or NamedTuple ``` ### Pydantic Patterns ```python from pydantic import BaseModel, Field, field_validator class Config(BaseModel): name: str = Field(min_length=1, max_length=100) replicas: int = Field(default=1, ge=1, le=100) tags: list[str] = Field(default_factory=list) @field_validator("name") @classmethod def name_must_be_slug(cls, v: str) -> str: if not v.replace("-", "").isalnum(): raise ValueError("name must be alphanumeric with hyphens") return v.lower() # Serialization config = Config(name="my-app", replicas=3) config.model_dump() # dict config.model_dump_json() # JSON string Config.model_validate(d) # from dict Config.model_validate_json(s) # from JSON ``` ### Dataclass Patterns ```python from dataclasses import dataclass, field @dataclass(frozen=True, slots=True) class Point: x: float y: float @dataclass class Config: name: str items: list[str] = field(default_factory=list) ``` ## Error Handling ### Exception Hierarchy ```python class AppError(Exception): """Base for all application errors.""" class NotFoundError(AppError): """Resource not found.""" def __init__(self, resource: str, id: str): self.resource = resource self.id = id super().__init__(f"{resource} {id} not found") class ValidationError(AppError): """Invalid input.""" ``` ### Patterns ```python # Specific catches, never bare except try: result = api_call() except httpx.TimeoutException: logger.warning("API timeout, retrying") result = api_call() except httpx.HTTPStatusError as e: if e.response.status_code == 404: raise NotFoundError("resource", id) from e raise # Context managers for cleanup from contextlib import contextmanager @contextmanager def managed_connection(url: str): conn = connect(url) try: yield conn finally: conn.close() ``` ### Anti-Patterns | ❌ Don't | ✅ Do | |----------|-------| | `except Exception: pass` | Catch specific exceptions, log or re-raise | | `except: ...` (bare except) | Always specify exception type | | `raise Exception("msg")` | Raise specific exception subclass | | Return `None` for errors | Raise exceptions or use `Result` type | | Catch and re-raise same exception | Only catch if you add context | ## Testing with pytest ### Fixtures ```python # conftest.py import pytest @pytest.fixture def sample_config(): return Config(name="test", replicas=1) @pytest.fixture def tmp_db(tmp_path): db_path = tmp_path / "test.db" db = Database(str(db_path)) db.init() yield db db.close() ``` ### Parametrize ```python @pytest.mark.parametrize("input,expected", [ ("hello", "HELLO"), ("", ""), ("Hello World", "HELLO WORLD"), ]) def test_uppercase(input, expected): assert uppercase(input) == expected ``` ### Testing Exceptions ```python def test_not_found_raises(): with pytest.raises(NotFoundError, match="user 123 not found"): find_user("123") ``` ### Mocking ```python from unittest.mock import patch, AsyncMock def test_api_call(mocker): mock_get = mocker.patch("mypackage.core.httpx.get") mock_get.return_value.json.return_value = {"status": "ok"} result = fetch_status() assert result == "ok" mock_get.assert_called_once() # Async mocking async def test_async_call(mocker): mocker.patch("mypackage.core.fetch", new=AsyncMock(return_value=42)) result = await process() assert result == 42 ``` ### Test Organization ``` tests/ ├── conftest.py # shared fixtures ├── unit/ │ ├── test_models.py # pure logic │ └── test_utils.py ├── integration/ │ └── test_api.py # real I/O └── fixtures/ └── sample.json # test data ``` ## Async Patterns ### httpx for HTTP ```python import httpx async def fetch_all(urls: list[str]) -> list[dict]: async with httpx.AsyncClient(timeout=30.0) as client: tasks = [client.get(url) for url in urls] responses = await asyncio.gather(*tasks, return_exceptions=True) return [r.json() for r in responses if not isinstance(r, Exception)] ``` ### asyncio Patterns ```python import asyncio # Gather with error handling results = await asyncio.gather(*tasks, return_exceptions=True) successes = [r for r in results if not isinstance(r, BaseException)] failures = [r for r in results if isinstance(r, BaseException)] # Semaphore for concurrency limiting sem = asyncio.Semaphore(10) async def limited_fetch(url): async with sem: return await client.get(url) # Timeout try: result = await asyncio.wait_for(slow_operation(), timeout=5.0) except asyncio.TimeoutError: logger.error("Operation timed out") ``` ## Logging ### structlog (preferred) ```python import structlog logger = structlog.get_logger() def process_order(order_id: str): log = logger.bind(order_id=order_id) log.info("processing_order") try: result = do_work(order_id) log.info("order_processed", result=result) except Exception: log.exception("order_failed") raise ``` ### stdlib logging ```python import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s", ) logger = logging.getLogger(__name__) ``` ## CLI Patterns ### click (simple) ```python import click @click.command() @click.argument("name") @click.option("--count", "-c", default=1, help="Number of greetings") def hello(name: str, count: int): """Greet NAME count times.""" for _ in range(count): click.echo(f"Hello, {name}!") ``` ### typer (modern, type-hint based) ```python import typer app = typer.Typer() @app.command() def deploy( env: str = typer.Argument(..., help="Target environment"), dry_run: bool = typer.Option(False, "--dry-run", "-n"), ): """Deploy to ENV.""" if dry_run: typer.echo(f"Would deploy to {env}") return do_deploy(env) ``` ## Debugging ### Quick Debug ```python # Drop into debugger at any point breakpoint() # Python 3.7+ # Or with environment variable # PYTHONBREAKPOINT=ipdb.set_trace python script.py ``` ### Reading Tracebacks 1. Read from **bottom up** — last frame is where error occurred 2. Note the **exception type and message** first 3. Scan frames for **your code** (skip stdlib/library frames) 4. Check the **variable values** at the failing line ## Common Anti-Patterns | Anti-Pattern | Problem | Fix | |-------------|---------|-----| | Mutable default args | `def f(items=[])` shared across calls | `def f(items=None): items = items or []` | | Global state | Hard to test, race conditions | Dependency injection, pass explicitly | | String typing | `if status == "active"` typos silently pass | Use `Enum` or `Literal` | | Wildcard imports | `from x import *` pollutes namespace | Import explicitly | | Nested try/except | Deep nesting obscures flow | Extract to functions | | God classes | 500+ line class doing everything | Split by responsibility | | Premature optimization | Complex code for imagined perf | Profile first (`py-spy`, `cProfile`) | ## Performance Profiling ```bash # CPU profiling py-spy record -o profile.svg -- python script.py # Memory profiling python -m tracemalloc script.py # Quick timing python -m timeit "expression" ```
View on GitHub