Skip to content

Python

The python format generates Python TypedDict classes for static type checking.

Basic Example

OpenAPI Schema:

yaml
components:
  schemas:
    User:
      type: object
      description: A user in the system
      required:
        - id
        - email
      properties:
        id:
          type: string
          description: Unique identifier
        email:
          type: string
          format: email
        name:
          type: string
        role:
          $ref: '#/components/schemas/UserRole'
    
    UserRole:
      type: string
      description: The role of a user
      enum: [admin, user, guest]

Generated Python:

python
# Generated by an automated build step. Do not edit.

from typing import TypedDict, NotRequired, Literal, Union, List, Dict, Any
from enum import Enum

class User(TypedDict):
    """A user in the system"""
    id: str
    """Unique identifier"""

    email: str
    name: NotRequired[str]
    role: NotRequired["UserRole"]
    """The role of a user"""

class UserRole(str, Enum):
    """The role of a user"""
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"

Configuration

jsonc
{
  "outputs": [
    {
      "path": "./backend/types/api.py",
      "format": "python",
      "contents": ["api:*"]
    }
  ]
}

Type Mapping

OpenAPI TypeOpenAPI FormatPython
string-str
stringdatestr
stringdate-timestr
stringemailstr
stringuuidstr
integer-int
integerint32int
integerint64int
number-float
numberfloatfloat
numberdoublefloat
boolean-bool
array-List[T]
object-TypedDict
object(no properties)Dict[str, Any]
null-None

Required vs Optional Fields

Required fields are defined normally, optional fields use NotRequired:

python
class User(TypedDict):
    id: str                      # Required
    name: NotRequired[str]       # Optional

Python Version

NotRequired requires Python 3.11+. For older versions, sparktype generates compatible syntax using total=False.

Enums

With generateEnums: true (default for Python):

python
class UserRole(str, Enum):
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"

With generateEnums: false:

python
UserRole = Literal["admin", "user", "guest"]

Enum member names are derived from values:

  • "admin"ADMIN
  • "some-value"SOME_VALUE
  • "SomeValue"SOMEVALUE

Nullable Fields

Nullable fields use union with None:

yaml
properties:
  nickname:
    type: string
    nullable: true
python
class User(TypedDict):
    nickname: NotRequired[str | None]

References

References become quoted forward references:

python
class Post(TypedDict):
    author: "User"  # Forward reference

This allows circular references and ordering flexibility.

Docstrings

Schema descriptions become docstrings:

python
class User(TypedDict):
    """A user in the system"""
    id: str
    """Unique identifier for the user"""

    email: str
    """User's email address"""

Composition

allOf (Inheritance)

Python TypedDicts support inheritance:

yaml
allOf:
  - $ref: '#/components/schemas/BaseEntity'
  - type: object
    properties:
      name:
        type: string
python
class Product(BaseEntity):
    name: NotRequired[str]

oneOf / anyOf (Union)

yaml
oneOf:
  - $ref: '#/components/schemas/Dog'
  - $ref: '#/components/schemas/Cat'
python
Pet = Union["Dog", "Cat"]

Usage Examples

With FastAPI

python
from fastapi import FastAPI
from .types.api import User, CreateUserRequest

app = FastAPI()

@app.post("/users")
async def create_user(request: CreateUserRequest) -> User:
    # Type checking enforced
    return {
        "id": "123",
        "email": request["email"],
        "name": request.get("name")
    }

With Type Checkers

Run mypy or pyright to catch type errors:

bash
mypy app/
pyright app/

Creating Instances

TypedDicts are used like regular dictionaries:

python
from .types.api import User

# Type-checked dictionary
user: User = {
    "id": "123",
    "email": "user@example.com",
    "name": "John"
}

# Access fields
print(user["email"])

# Optional field access
name = user.get("name", "Anonymous")

Validation

TypedDicts provide static type checking only. For runtime validation, consider:

Limitations

No Namespaces

Python format doesn't support namespaces. Use separate output files instead:

jsonc
"outputs": [
  { "path": "./types/users.py", "contents": ["users:*"] },
  { "path": "./types/products.py", "contents": ["products:*"] }
]

Forward References

All type references use quotes for forward reference compatibility:

python
author: "User"  # Not: author: User

Python Version Requirements

FeatureMinimum Version
TypedDict3.8
NotRequired3.11
Union syntax (|)3.10

Best Practices

Module Organization

Create an __init__.py to re-export types:

python
# types/__init__.py
from .users import User, UserRole
from .products import Product

Type Aliases

Create aliases for common patterns:

python
from typing import Dict, Any

JSON = Dict[str, Any]

Combining with Pydantic

For runtime validation, create Pydantic models alongside TypedDicts:

python
from pydantic import BaseModel
from .types.api import User as UserDict

class User(BaseModel):
    id: str
    email: str
    name: str | None = None
    
    def to_dict(self) -> UserDict:
        return self.model_dump()

See Also

Released under the MIT License.