Skip to content

FastAPI Endpoint Generation

The haskoning_equation package can automatically expose Python functions as POST API endpoints using FastAPI, based on configuration in pyproject.toml. This eliminates boilerplate code and ensures type-safe request/response handling.

Features

  • Automatically registers functions as /module_name/function_name endpoints
  • Type-safe request/response validation with Pydantic
  • Function-based only — classes are not supported (wrap your class in a function to expose)
  • Only functions with complete type annotations are exposed
  • Skips endpoints already registered in FastAPI
  • Skips functions with missing parameter or return annotations
  • Support for FastAPI Request parameter to access request state, headers, and client info
  • Automatic conversion of io.BytesIO return types to StreamingResponse for file downloads
  • Automatic wrapping of non-BaseModel responses as { "result": ... }

Prerequisites

  • haskoning_equation installed
  • fastapi and uvicorn available (installed automatically if missing)
  • Valid pyproject.toml configuration with [tool.faster_api]

Configuration

pyproject.toml Setup

toml
[tool.faster_api]
include_modules = ["src.my_module"]
exclude_modules = ["src.my_module.internal"]

# Optional CORS settings
cors_settings = { 
    allow_origins = ["https://example.com"], 
    allow_methods = ["GET", "POST"], 
    allow_headers = ["Authorization"], 
    allow_credentials = true 
}

If cors_settings is not configured, CORS is allowed from all origins by default.

Basic Usage

Example Function

python
# src/my_module.py
def calculate_index(a: int, b: float) -> float:
    """Calculate a simple index."""
    return a * b

This function will automatically be exposed as a POST endpoint at /my_module/calculate_index.

Using Request Parameter

Access request-specific information (headers, state, client info) by adding a request: Request parameter:

python
from fastapi import Request
from pydantic import BaseModel

class InputModel(BaseModel):
    a: int
    b: int

class OutputModel(BaseModel):
    result: int
    user_id: str | None = None

def calculate_with_context(request: Request, input: InputModel) -> OutputModel:
    """Function that accesses request state set by middleware."""
    # Access user information from request state (set by middleware)
    user_id = getattr(request.state, "user_id", None)
    
    # Your calculation logic
    result = input.a + input.b
    
    return OutputModel(result=result, user_id=user_id)

The Request parameter is automatically injected by FastAPI and doesn't need to be included in the request body.

Returning Files with BytesIO

Functions that return io.BytesIO or Optional[io.BytesIO] are automatically converted to StreamingResponse for file downloads:

python
import io
from pydantic import BaseModel

class ExportRequest(BaseModel):
    filename: str
    data: list[dict]

def export_csv(request: ExportRequest) -> io.BytesIO:
    """Export data as CSV file."""
    # Create CSV content
    csv_content = "id,name,value\n"
    for row in request.data:
        csv_content += f"{row['id']},{row['name']},{row['value']}\n"
    
    # Return as BytesIO - automatically converted to StreamingResponse
    buffer = io.BytesIO(csv_content.encode('utf-8'))
    buffer.filename = "sample.csv"
    return buffer

Running the API

Standalone FastAPI App

Generate and run a standalone FastAPI app with all configured endpoints:

bash
poetry run faster-api

To run a dummy function defined in the package for quick testing:

bash
poetry run faster-api --dummy

The API will be available at http://localhost:8000. Visit http://localhost:8000/docs to see and test available endpoints.

Attach to Existing FastAPI App

Integrate endpoint generation into an existing FastAPI application:

python
from fastapi import FastAPI
from haskoning_equation.faster_api import FasterAPI, FasterAPIConfig

app = FastAPI()

faster_api_config = FasterAPIConfig(include_modules=['haskoning_hycalc.api'])
faster_api = FasterAPI(app, faster_api_config)
faster_api.register_all_endpoints()

Then run with:

bash
uvicorn app.main:app --reload

Serving Static Files

To display images in CardStyle.image_url, include the image in your package and ensure the backend serves it.

Project Structure

Organize your project to include static files:

haskoning_hycalc/
├─ haskoning_hycalc/
│  ├─ __init__.py
│  ├─ static/
│  │  └─ images/
│  │     └─ driehoek.jpg

URL Mapping

When using register_all_endpoints, the static directory is automatically mounted. Static files are served at: /your_package_name/static/...

Example: http://localhost:8000/haskoning_hycalc/static/images/driehoek.jpg

CardStyle with Image

python
from haskoning_equation.style import CardStyle

CardStyle(
    title="Driehoek",
    subtitle="Bereken zijdes of hoek van een rechthoekige driehoek",
    icon="mdi-angle-acute",
    min_inputs=2,
    image_url="/haskoning_hycalc/static/images/driehoek.jpg",
)

Frontend Usage

The frontend should prepend a configured API base URL (e.g., VITE_APP_API_BASE_URL). Normalize slashes to avoid double slashes or missing separators.

Best Practices

  • Only put public, non-sensitive assets in static/
  • Use versioned paths if you need cache busting
  • Keep file sizes reasonable for web delivery

Running Multiple Equation Apps

You can host multiple Equation applications in a single FastAPI instance:

python
from fastapi import FastAPI
from haskoning_equation import apply_equation_web_client_settings, EquationWebClientSettings
from haskoning_equation.faster_api import FasterAPI, FasterAPIConfig

app = FastAPI()

# Existing Nereda router
nereda_settings = EquationWebClientSettings(app_name="Nereda", route="/nereda")
app.include_router(nereda_router, prefix="/nereda", tags=["Nereda"])

# Add Hycalc endpoints
hycalc_settings = EquationWebClientSettings(app_name="Hycalc", route="/hycalc")
faster_api_config = FasterAPIConfig(
    title="Hycalc", 
    include_modules=['haskoning_hycalc.api'],
    equation_web_client_settings=[hycalc_settings]
)
faster_api = FasterAPI(app, faster_api_config)
faster_api.register_all_endpoints()

# Apply settings for both apps
apply_equation_web_client_settings(app, [nereda_settings, hycalc_settings])

Each app will be available at its configured route:

  • Nereda: http://localhost:8000/nereda
  • Hycalc: http://localhost:8000/hycalc

Troubleshooting

Function Not Exposed

  • Ensure all parameters and return types have type annotations
  • Check that the module is listed in include_modules
  • Verify the module is not in exclude_modules
  • Classes are not supported — wrap in a function

CORS Issues

  • Configure cors_settings in pyproject.toml to specify allowed origins
  • Default allows all origins if not configured

Static Files Not Loading

  • Verify the static folder exists in your package
  • Check the URL path matches your package name
  • Ensure files are included in your package distribution