Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

FirstFlush/typed-api-response

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月30ζ—₯T04: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月30ζ—₯T00: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 πŸ™

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /