Skip to content

FirstFlush/typed-api-response

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Typed API Response

A unified, type-safe API response format for Python β€” with full type inference, Pydantic support, and consistent structure for both success and error responses. Just pass your data or exception β€” build_api_response() handles the rest.

Works seamlessly with FastAPI, Django Ninja, or any Pydantic-based Python project. Accepts Pydantic models, dataclasses, or any structured object.

πŸ§ͺ Type Safety

This library is:

  • Designed for Pylance and mypy strict mode
  • Fully generic β€” type-safe through all layers
  • Uses overloads to preserve type inference

No need to type hint manually:

response = build_api_response(data=MySchema(...), status=200) # response.payload.data is inferred as MySchema βœ…

➑️ Want proof? See the typecheck file

This is a static analysis file for mypy. It uses reveal_type() to confirm that generic types and payload structures are preserved correctly.
You can run it with mypy or open it in VSCode and hover to inspect types inline β€” no need to execute the file.

πŸ”§ Features

  • βœ… Typed response builders for both success and error responses
  • βœ… Fully generic, Pylance-compliant with strict mode enabled
  • βœ… Single unified function: build_api_response(...)
  • βœ… Extensible metadata support via ResponseMeta
  • βœ… Automatically distinguishes between data and error
  • βœ… Raises clean custom exceptions on misconfiguration

πŸš€ Getting Started

Install with pip

pip install typed-api-response

Define your Pydantic response schema

from pydantic import BaseModel class MyOutputSchema(BaseModel): product_name: str description: str price: float qty: int on_sale: bool

Create a typed API response:

@router.post("/foo") def foo(): your_data = MyOutputSchema( product_name="Big Bag of Rice", description="The world's greatest rice ever made. Anywhere. Ever.", price=17.99, qty=47328, on_sale=False, ) response = build_api_response(data=your_data, status=200) # response.payload.data is inferred as MyOutputSchema βœ…

βœ… build_api_response() accepts any Pydantic model or well-typed object (e.g. a dataclass) and wraps it into a fully structured, metadata-rich response β€” with full type hint propagation and IDE support via generics.

Handling errors just as cleanly

You can also return exceptions using the same unified response format:

try: ... except Exception as e: return build_api_response(error=e, status=418)

βœ… build_api_response() wraps the exception in a type-safe, structured error payload β€” so your failure responses stay as consistent and predictable as your success ones.

🧱 API Structure

Unified Interface

def build_api_response( *, data: T | None = None, error: Exception | None = None, status: int, meta: ResponseMeta | None = None, ) -> ApiSuccessResponse[T] | ApiErrorResponse
  • Provide either data or error, not both
  • meta lets you attach timing, versioning, request ID, etc.
  • If neither data nor error is passed, raises ApiResponseBuilderError

Success Response Format

{ "status": 200, "meta": { "duration": null, "extra": null, "method": null, "path": null, "request_id": null, "timestamp": "2025-07-30T04:33:44.833Z", "version": null }, "payload": { "success": true, "data": { "product_name": "Big Bag of Rice", "description": "The world's greatest rice ever made. Anywhere. Ever.", "price": 17.99, "qty": 47328, "on_sale": false }, "error": null } }

Error Response Format

{ "status": 418, "meta": { "duration": null, "extra": null, "method": null, "path": null, "request_id": null, "timestamp": "2025-07-30T00:13:55.531Z", "version": null }, "payload": { "success": false, "data": null, "error": { "type": "ZeroDivisionError", "msg": "division by zero" } } }

🧠 Customizing Metadata

Use ResponseMeta to attach custom fields:

meta = ResponseMeta( request_id="abc123", version="v1.2.0", extra={"model": "en_streetninja", "debug": True} ) return build_api_response(data=result, status=200, meta=meta)

πŸ›‘οΈ Exceptions

This package raises:

  • ApiResponseBuilderError – if payload generation fails
  • ApiPayloadBuilderError – if payload data is inconsistent or incomplete

πŸ“¦ Components

  • ApiResponseBuilder – abstract base class for builders
  • ApiSuccessResponseBuilder – builds ApiSuccessResponse[T]
  • ApiErrorResponseBuilder – builds ApiErrorResponse
  • ResponseMeta – optional metadata block
  • Payload / SuccessPayload[T] / ErrorPayload – structured payload schemas

β˜• Support This Project

If this saved you time or made your API cleaner, feel free to buy me a coffee. Thanks for your support πŸ™