All skills
wshobson avatar

/python-type-safety

@be57c0b
by Seth Hobsonwshobson/agents40k stars
4,281

Python type safety with type hints, generics, protocols, and strict type checking. Use when adding type annotations, implementing generic classes, defining structural interfaces, or configuring mypy/pyright.

Use this Skill: https://skilld.dev/gh/wshobson/agents/python-type-safety

This session only. Nothing lands on disk.

referencesdetails.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

python-type-safety — detailed worked examples

Advanced Patterns

Pattern 5: Generic Repository

Create type-safe data access patterns.

from typing import TypeVar, Generic
from abc import ABC, abstractmethod

T = TypeVar("T")
ID = TypeVar("ID")

class Repository(ABC, Generic[T, ID]):
    """Generic repository interface."""

    @abstractmethod
    async def get(self, id: ID) -> T | None:
        """Get entity by ID."""
        ...

    @abstractmethod
    async def save(self, entity: T) -> T:
        """Save and return entity."""
        ...

    @abstractmethod
    async def delete(self, id: ID) -> bool:
        """Delete entity, return True if existed."""
        ...

class UserRepository(Repository[User, str]):
    """Concrete repository for Users with string IDs."""

    async def get(self, id: str) -> User | None:
        row = await self._db.fetchrow(
            "SELECT * FROM users WHERE id = $1", id
        )
        return User(**row) if row else None

    async def save(self, entity: User) -> User:
        ...

    async def delete(self, id: str) -> bool:
        ...

Pattern 6: TypeVar with Bounds

Restrict generic parameters to specific types.

from typing import TypeVar
from pydantic import BaseModel

ModelT = TypeVar("ModelT", bound=BaseModel)

def validate_and_create(model_cls: type[ModelT], data: dict) -> ModelT:
    """Create a validated Pydantic model from dict."""
    return model_cls.model_validate(data)

# Works with any BaseModel subclass
class User(BaseModel):
    name: str
    email: str

user = validate_and_create(User, {"name": "Alice", "email": "a@b.com"})
# user is typed as User

# Type error: str is not a BaseModel subclass
result = validate_and_create(str, {"name": "Alice"})  # Error!

Pattern 7: Protocols for Structural Typing

Define interfaces without requiring inheritance.

from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    """Any class that can be serialized to/from dict."""

    def to_dict(self) -> dict:
        ...

    @classmethod
    def from_dict(cls, data: dict) -> "Serializable":
        ...

# User satisfies Serializable without inheriting from it
class User:
    def __init__(self, id: str, name: str) -> None:
        self.id = id
        self.name = name

    def to_dict(self) -> dict:
        return {"id": self.id, "name": self.name}

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(id=data["id"], name=data["name"])

def serialize(obj: Serializable) -> str:
    """Works with any Serializable object."""
    return json.dumps(obj.to_dict())

# Works - User matches the protocol
serialize(User("1", "Alice"))

# Runtime checking with @runtime_checkable
isinstance(User("1", "Alice"), Serializable)  # True

Pattern 8: Common Protocol Patterns

Define reusable structural interfaces.

from typing import Protocol

class Closeable(Protocol):
    """Resource that can be closed."""
    def close(self) -> None: ...

class AsyncCloseable(Protocol):
    """Async resource that can be closed."""
    async def close(self) -> None: ...

class Readable(Protocol):
    """Object that can be read from."""
    def read(self, n: int = -1) -> bytes: ...

class HasId(Protocol):
    """Object with an ID property."""
    @property
    def id(self) -> str: ...

class Comparable(Protocol):
    """Object that supports comparison."""
    def __lt__(self, other: "Comparable") -> bool: ...
    def __le__(self, other: "Comparable") -> bool: ...

Pattern 9: Type Aliases

Create meaningful type names.

Note: The type Alias = ... statement syntax (PEP 695) was introduced in Python 3.12, not 3.10. For projects targeting earlier versions (including 3.10/3.11), use the TypeAlias annotation (PEP 613, available since Python 3.10).

# Python 3.12+ type statement (PEP 695)
type UserId = str
type UserDict = dict[str, Any]

# Python 3.12+ type statement with generics (PEP 695)
type Handler[T] = Callable[[Request], T]
type AsyncHandler[T] = Callable[[Request], Awaitable[T]]
# Python 3.10-3.11 style (needed for broader compatibility)
from typing import TypeAlias
from collections.abc import Callable, Awaitable

UserId: TypeAlias = str
Handler: TypeAlias = Callable[[Request], Response]
# Usage
def register_handler(path: str, handler: Handler[Response]) -> None:
    ...

Pattern 10: Callable Types

Type function parameters and callbacks.

from collections.abc import Callable, Awaitable

# Sync callback
ProgressCallback = Callable[[int, int], None]  # (current, total)

# Async callback
AsyncHandler = Callable[[Request], Awaitable[Response]]

# With named parameters (using Protocol)
class OnProgress(Protocol):
    def __call__(
        self,
        current: int,
        total: int,
        *,
        message: str = "",
    ) -> None: ...

def process_items(
    items: list[Item],
    on_progress: ProgressCallback | None = None,
) -> list[Result]:
    for i, item in enumerate(items):
        if on_progress:
            on_progress(i, len(items))
        ...

Configuration

Strict Mode Checklist

For mypy --strict compliance:

# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
no_implicit_optional = true

Incremental adoption goals:

  • All function parameters annotated
  • All return types annotated
  • Class attributes annotated
  • Minimize Any usage (acceptable for truly dynamic data)
  • Generic collections use type parameters (list[str] not list)

For existing codebases, enable strict mode per-module using # mypy: strict or configure per-module overrides in pyproject.toml.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides safe, constructive instructional material for Python type safety, covering type annotations, generics, protocols, type narrowing, and strict configuration setup. It contains no executable code or malicious indicators.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    1 file scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at be57c0b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 4 months ago
  • Python
  • type-hints
  • mypy
  • pyright
  • generics
  • protocols
  • type-narrowing
  • static-analysis
  • typing

README badge

README badge for wshobson/agents/python-type-safety

Adds type annotations, generics, protocols, and strict type checking to Python code using mypy and pyright. Covers annotating function signatures, type narrowing, generic classes, and structural interfaces without inheritance.

Generated from the current SKILL.md.

Does this skill work with Python 3.9 or older?
Yes, but some features like the modern union syntax (T | U) require Python 3.10+. The skill covers older `Optional` and `Union` syntax for earlier versions.
Which type checkers does this skill cover?
The skill focuses on mypy and pyright, with examples showing how to run mypy in strict mode and configure pyright for strict checking in CI.
Does this skill explain how to migrate an untyped codebase?
The skill mentions enabling strict mode incrementally using per-module overrides for existing projects, but does not provide a detailed migration guide.
What is a Protocol and when should I use it instead of inheritance?
Protocols define structural interfaces without requiring inheritance, enabling duck typing with type safety. Use them when you need flexible, structural typing rather than explicit class hierarchies.

Generated from the current SKILL.md. These answers refresh after source changes.