Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Python Patterns

ASecurity

Provides idiomatic modern Python (3.11+) patterns covering type hints, dataclasses and pydantic at boundaries, pathlib, context managers, generators, async basics, explicit error handling, and a uv/ruff/pytest project layout. Use when writing, reviewing, or refactoring Python code or setting up a Python project.

5 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentspythonrustbashexpresstestingrefactoringapidatabasebackend

Works with

cliapi

Security Analysis

A100/100

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add harshadmadaye/OPM --skill python-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Patterns?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Python Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/harshadmadaye-python-patterns/badge)](https://www.skillsdirectory.com/skills/harshadmadaye-python-patterns)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
name: python-patterns
description: Provides idiomatic modern Python (3.11+) patterns covering type hints, dataclasses and pydantic at boundaries, pathlib, context managers, generators, async basics, explicit error handling, and a uv/ruff/pytest project layout. Use when writing, reviewing, or refactoring Python code or setting up a Python project.
---

# Python Patterns

Readable, explicit, typed. These patterns target Python 3.11 or newer with `uv` for
environments, `ruff` for lint and format, and `pytest` for tests.

## When to use

- Writing new Python modules, packages, scripts, or services.
- Reviewing Python diffs for idiom, typing, and error handling.
- Setting up or modernising a Python project's tooling.

## Core principles

- **Readability over cleverness.** Names say what things hold; comprehension over a three-line loop, a function over a three-clause comprehension.
- **Explicit over implicit.** No import-time side effects, no hidden global configuration.
- **EAFP where the exception is the normal control flow** (`try: d[key] except KeyError`), LBYL where a check is cheaper and clearer (`if path.exists()` before a destructive operation).
- **Immutability by default.** Frozen dataclasses, tuples, and new objects returned from functions; mutate in place only when the API is designed for it.

## Typing

Annotate every function signature. Use built-in generics and the union operator.

```python
from collections.abc import Iterable, Iterator, Callable, Sequence
from typing import Protocol, TypeVar, TypeAlias, Self

def first(items: Sequence[T]) -> T | None:
    return items[0] if items else None

JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None

class Renderable(Protocol):
    def render(self) -> str: ...

def render_all(items: Iterable[Renderable]) -> str:
    return "\n".join(item.render() for item in items)
```

- Prefer `collections.abc` types (`Iterable`, `Mapping`, `Sequence`) for parameters and concrete types (`list`, `dict`) for return values.
- `Protocol` for structural typing instead of abstract base classes when callers just need a shape.
- `Self` for fluent methods; `Literal[...]` for closed string sets; `TypedDict` for dict-shaped external payloads you do not control.
- Run `pyright` or `mypy --strict` in CI. Untyped third-party libraries get a `py.typed` stub or a narrow `cast` at the import boundary, not `Any` spreading through the codebase.

## Data at the boundaries

Two tools, two jobs:

- **`dataclass`** for internal data you construct yourself. Frozen unless mutation is the point; `slots=True` when you create many.
- **`pydantic.BaseModel`** at system boundaries: request bodies, config files, API responses, message payloads. It validates and coerces once, on the way in, then the rest of the code trusts the types.

```python
from dataclasses import dataclass, field
from datetime import datetime, UTC
from pydantic import BaseModel, Field, field_validator

@dataclass(frozen=True, slots=True)
class Money:
    amount_minor: int
    currency: str

    def add(self, other: "Money") -> "Money":
        if other.currency != self.currency:
            raise ValueError(f"currency mismatch: {self.currency} vs {other.currency}")
        return Money(self.amount_minor + other.amount_minor, self.currency)

class CreateOrder(BaseModel):
    customer_id: str = Field(min_length=1)
    items: list[str] = Field(min_length=1)
    note: str | None = None

    @field_validator("items")
    @classmethod
    def no_duplicates(cls, items: list[str]) -> list[str]:
        if len(set(items)) != len(items):
            raise ValueError("duplicate items")
        return items

class Settings(BaseModel):
    database_url: str
    log_level: str = "INFO"
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
```

Parse external input into a model at the edge (`CreateOrder.model_validate(payload)`); raise a
clear error there. Never pass raw `dict[str, Any]` deeper than the boundary. `NamedTuple` remains
fine for tiny immutable records like coordinates.

## Paths and files

`pathlib` everywhere; `os.path` string juggling is legacy.

```python
from pathlib import Path

CONFIG_DIR = Path.home() / ".myapp"

def load_config(path: Path = CONFIG_DIR / "config.toml") -> Settings:
    if not path.is_file():
        raise ConfigError(f"config not found: {path}")
    return Settings.model_validate(tomllib.loads(path.read_text(encoding="utf-8")))

for source in Path("data").glob("*.csv"):
    target = source.with_suffix(".parquet")
```

Always pass `encoding="utf-8"` when reading or writing text. Use `Path.resolve()` and check
`is_relative_to()` before writing to a path derived from user input.

## Context managers

Anything that acquires must release, and `with` is how Python says so.

```python
from contextlib import contextmanager
import time

@contextmanager
def timed(label: str) -> Iterator[None]:
    start = time.perf_counter()
    try:
        yield
    finally:
        logger.info("%s took %.3fs", label, time.perf_counter() - start)

class Transaction:
    def __init__(self, conn: Connection) -> None:
        self._conn = conn

    def __enter__(self) -> Self:
        self._conn.begin()
        return self

    def __exit__(self, exc_type, exc, tb) -> bool:
        if exc_type is None:
            self._conn.commit()
        else:
            self._conn.rollback()
        return False  # never swallow the exception
```

Use `contextlib.ExitStack` when the number of resources is dynamic. Return `False` from `__exit__`
unless suppressing is the whole point.

## Iteration and generators

```python
# Generator: lazy, constant memory
def read_records(path: Path) -> Iterator[Record]:
    with path.open(encoding="utf-8") as handle:
        for line in handle:
            yield Record.parse(line.rstrip("\n"))

total = sum(r.amount for r in read_records(path))       # generator expression, no list

# Comprehension for simple transforms; a named function once it needs two conditions
active_emails = [u.email for u in users if u.is_active]
```

Use `enumerate`, `zip(strict=True)`, `itertools` (`batched`, `groupby`, `chain`) before writing
index arithmetic. Build strings with `"".join(parts)` or an f-string, never `+=` in a loop.

## Async basics

Use `asyncio` for I/O-bound concurrency (HTTP, sockets, database drivers with async support).
Use a thread pool for blocking libraries you cannot change. Use processes for CPU-bound work.

```python
import asyncio
import httpx

async def fetch_json(client: httpx.AsyncClient, url: str) -> JSON:
    response = await client.get(url, timeout=10.0)
    response.raise_for_status()
    return response.json()

async def fetch_all(urls: list[str]) -> list[JSON | BaseException]:
    async with httpx.AsyncClient() as client:
        async with asyncio.TaskGroup() as group:
            tasks = [group.create_task(fetch_json(client, url)) for url in urls]
    return [task.result() for task in tasks]

# Blocking call inside async code
data = await asyncio.to_thread(legacy_blocking_read, path)
```

Rules: never call blocking I/O or `time.sleep` inside a coroutine; bound concurrency with
`asyncio.Semaphore` when fanning out to an external service; prefer `TaskGroup` (3.11+) over
bare `gather` so failures cancel siblings and propagate as an `ExceptionGroup`; pass timeouts explicitly.

## Error handling

- Catch specific exceptions. A bare `except:` or `except Exception: pass` is a bug.
- Chain with `raise ... from e` so the original traceback survives.
- Define a small hierarchy rooted at one application exception; callers catch the root when they
  want "anything ours", the leaf when they can act on it.
- Log with `logger.exception(...)` at the level that handles the error, once. Do not log and re-raise
  at every layer.
- User-facing messages are friendly; logs carry full context.

```python
class AppError(Exception):
    """Base for all errors raised by this application."""

class ConfigError(AppError): ...
class NotFoundError(AppError): ...

def load_user(user_id: str) -> User:
    try:
        row = repo.get(user_id)
    except RepositoryError as e:
        raise AppError(f"could not load user {user_id}") from e
    if row is None:
        raise NotFoundError(f"user not found: {user_id}")
    return User.model_validate(row)
```

## Logging and configuration

- `logging.getLogger(__name__)` per module; configure handlers once at the entry point.
- Lazy formatting: `logger.info("user %s logged in", user_id)`, not an f-string, so the string
  is built only when the level is enabled.
- Configuration comes from environment variables or a file, validated into a `Settings` model at
  startup. Missing required settings fail fast with a clear message. No secrets in source.

## Project layout with uv, ruff, pytest

```
myproject/
  pyproject.toml
  uv.lock
  README.md
  src/
    mypackage/
      __init__.py
      __main__.py          # python -m mypackage
      config.py
      orders/              # organise by feature, not by layer
        __init__.py
        models.py
        service.py
        repository.py
  tests/
    conftest.py
    orders/
      test_service.py
```

```toml
[project]
name = "mypackage"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["pydantic>=2", "httpx>=0.27"]

[dependency-groups]
dev = ["pytest>=8", "pytest-cov", "pytest-asyncio", "ruff", "pyright"]

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

[tool.ruff]
line-length = 100
target-version = "py311"

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

[tool.pyright]
strict = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q --strict-markers"
asyncio_mode = "auto"
```

Everyday commands:

```bash
uv sync                          # create .venv and install locked deps
uv add httpx                     # add a dependency and update the lock
uv add --group dev pytest-cov    # dev dependency
uv run pytest                    # run tests in the project env
uv run ruff check . --fix && uv run ruff format .
uv run pyright
uv run python -m mypackage
```

The `src/` layout guarantees tests import the installed package, not the working directory.
Ruff replaces black, isort, flake8, and pyupgrade; do not run them alongside it.

## Testing conventions

- One test module per source module, mirrored under `tests/`.
- Fixtures in `conftest.py`; prefer small factory functions over deep fixture graphs.
- `pytest.raises` with `match=` for error paths. `tmp_path` for filesystem tests. `monkeypatch`
  for environment variables.
- Parametrize instead of copy-pasting near-identical tests.
- Mock at boundaries (HTTP, clock, database) using `respx`, `freezegun`, or a fake repository
  class; do not mock your own modules.

## Anti-patterns

```python
def add(item, items=[]): ...                 # mutable default; use None and create inside
if type(obj) == list: ...                    # use isinstance
if value == None: ...                        # use `is None`
from os.path import *                        # explicit imports only
except: pass                                 # catch specific, handle or re-raise
print(f"debug {x}")                          # use logging; no print in library code
open(path).read()                            # no context manager, no encoding
results = []; for x in xs: results.append(f(x))   # [f(x) for x in xs]
```

## Review checklist

- [ ] Every public function is annotated; `Any` appears only at a justified boundary.
- [ ] External input is validated into a model at the edge.
- [ ] Files, sockets, locks, and transactions are opened with `with`.
- [ ] No bare `except`; exceptions are chained; errors are logged once.
- [ ] `pathlib` and `encoding="utf-8"` for all file access.
- [ ] Async code has no blocking calls and bounds its concurrency.
- [ ] `ruff check`, `ruff format --check`, `pyright`, and `pytest` all pass.

## Related skills

- `opm:tdd-workflow` - pytest RED/GREEN cycle with `uv run pytest`.
- `opm:verification-loop` - the uv/ruff/pytest gate sequence.

<!-- Adapted from affaan-m/ecc (MIT) -->

Attribution

harshadmadayeharshadmadaye
View sourceMore from harshadmadaye →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →