
Python Error Handling
Apply structured Python exception design—custom API errors, status-aware handlers, and context-rich failures—while implementing backends and integrations.
Install
npx skills add https://github.com/wshobson/agents --skill python-error-handlingWhat is this skill?
- Domain-specific exceptions with structured fields such as status_code and response_body on ApiError
- RateLimitError subtype carrying retry_after for HTTP 429 flows
- match/case response handling pattern mapping status codes to typed errors
- Worked advanced patterns beyond bare try/except (custom exception hierarchy with context)
- Patterns suitable for API clients and service boundaries in typed Python
Adoption & trust: 8k installs on skills.sh; 36.5k GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Journey fit
Error-handling patterns land first when writing Python services and API clients during Build, but the same conventions matter when hardening Ship paths and fixing production faults. Backend subphase is the canonical shelf for domain exceptions, response mapping, and integration client guardrails in Python code.
Common Questions / FAQ
Is Python Error Handling safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Python Error Handling
# python-error-handling — detailed worked examples ## Advanced Patterns ### Pattern 5: Custom Exceptions with Context Create domain-specific exceptions that carry structured information. ```python class ApiError(Exception): """Base exception for API errors.""" def __init__( self, message: str, status_code: int, response_body: str | None = None, ) -> None: self.status_code = status_code self.response_body = response_body super().__init__(message) class RateLimitError(ApiError): """Raised when rate limit is exceeded.""" def __init__(self, retry_after: int) -> None: self.retry_after = retry_after super().__init__( f"Rate limit exceeded. Retry after {retry_after}s", status_code=429, ) # Usage def handle_response(response: Response) -> dict: match response.status_code: case 200: return response.json() case 401: raise ApiError("Invalid credentials", 401) case 404: raise ApiError(f"Resource not found: {response.url}", 404) case 429: retry_after = int(response.headers.get("Retry-After", 60)) raise RateLimitError(retry_after) case code if 400 <= code < 500: raise ApiError(f"Client error: {response.text}", code) case code if code >= 500: raise ApiError(f"Server error: {response.text}", code) ``` ### Pattern 6: Exception Chaining Preserve the original exception when re-raising to maintain the debug trail. ```python import httpx class ServiceError(Exception): """High-level service operation failed.""" pass def upload_file(path: str) -> str: """Upload file and return URL.""" try: with open(path, "rb") as f: response = httpx.post("https://upload.example.com", files={"file": f}) response.raise_for_status() return response.json()["url"] except FileNotFoundError as e: raise ServiceError(f"Upload failed: file not found at '{path}'") from e except httpx.HTTPStatusError as e: raise ServiceError( f"Upload failed: server returned {e.response.status_code}" ) from e except httpx.RequestError as e: raise ServiceError(f"Upload failed: network error") from e ``` ### Pattern 7: Batch Processing with Partial Failures Never let one bad item abort an entire batch. Track results per item. ```python from dataclasses import dataclass @dataclass class BatchResult[T]: """Results from batch processing.""" succeeded: dict[int, T] # index -> result failed: dict[int, Exception] # index -> error @property def success_count(self) -> int: return len(self.succeeded) @property def failure_count(self) -> int: return len(self.failed) @property def all_succeeded(self) -> bool: return len(self.failed) == 0 def process_batch(items: list[Item]) -> BatchResult[ProcessedItem]: """Process items, capturing individual failures. Args: items: Items to process. Returns: BatchResult with succeeded and failed items by index. """ succeeded: dict[int, ProcessedItem] = {} failed: dict[int, Exception] = {} for idx, item in enumerate(items): try: result = process_single_item(item) succeeded[idx] = result except Exception as e: failed[idx] = e return BatchResult(succeeded=succeeded, failed=failed) # Caller handles partial results result = process_batch(items) if not result.all_succeeded: logger.warning( f"Batch completed with {result.failure_count} failures", failed_indices=list(result.failed.keys()), ) ``` ### Pattern 8: Progress Reporting for Long Operations Provide visibility into batch progress without coupling business logic to UI. ```python from collections.abc import Callable ProgressCallback = Callable[