|
| 1 | +# FastAPI Runtime Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The FastAPI runtime enables native support for FastAPI applications in Azure Functions Python Worker. It acts as an adapter layer that discovers FastAPI routes, converts them to Azure Functions, and handles request routing. |
| 6 | + |
| 7 | +## Architecture Diagram |
| 8 | + |
| 9 | +``` |
| 10 | +┌─────────────────────────────────────────────────────────────┐ |
| 11 | +│ Azure Functions Host │ |
| 12 | +└──────────────────────┬──────────────────────────────────────┘ |
| 13 | + │ |
| 14 | + │ gRPC Communication |
| 15 | + │ |
| 16 | +┌──────────────────────▼──────────────────────────────────────┐ |
| 17 | +│ Proxy Worker │ |
| 18 | +│ (workers/proxy_worker/) │ |
| 19 | +└──────────────────────┬──────────────────────────────────────┘ |
| 20 | + │ |
| 21 | + │ Python Import/Call |
| 22 | + │ |
| 23 | +┌──────────────────────▼──────────────────────────────────────┐ |
| 24 | +│ FastAPI Runtime Package │ |
| 25 | +│ (runtimes/fastapi/) │ |
| 26 | +│ │ |
| 27 | +│ ┌────────────────────────────────────────────────────────┐ │ |
| 28 | +│ │ handle_event.py - Main Event Handler │ │ |
| 29 | +│ │ - worker_init_request() │ │ |
| 30 | +│ │ - functions_metadata_request() │ │ |
| 31 | +│ │ - function_load_request() │ │ |
| 32 | +│ │ - invocation_request() │ │ |
| 33 | +│ └────────────┬──────────────────┬────────────────────────┘ │ |
| 34 | +│ │ │ │ |
| 35 | +│ ┌────────────▼──────────┐ ┌───▼──────────────────────┐ │ |
| 36 | +│ │ indexer.py │ │ converter.py │ │ |
| 37 | +│ │ - Find FastAPI app │ │ - Convert routes to │ │ |
| 38 | +│ │ - Scan routes │ │ Azure Functions │ │ |
| 39 | +│ │ - Extract metadata │ │ - Generate bindings │ │ |
| 40 | +│ └───────────────────────┘ └───────────────────────────┘ │ |
| 41 | +│ │ |
| 42 | +│ ┌──────────────────────────────────────────────────────┐ │ |
| 43 | +│ │ handler.py - Request/Response Handler │ │ |
| 44 | +│ │ - Convert Azure Functions request to ASGI │ │ |
| 45 | +│ │ - Execute FastAPI route handler │ │ |
| 46 | +│ │ - Convert response back to Azure Functions format │ │ |
| 47 | +│ └──────────────────────────────────────────────────────┘ │ |
| 48 | +└──────────────────────┬──────────────────────────────────────┘ |
| 49 | + │ |
| 50 | + │ Direct Call |
| 51 | + │ |
| 52 | +┌──────────────────────▼──────────────────────────────────────┐ |
| 53 | +│ User's FastAPI App │ |
| 54 | +│ (function_app.py) │ |
| 55 | +│ │ |
| 56 | +│ app = FastAPI() │ |
| 57 | +│ │ |
| 58 | +│ @app.get("/hello") │ |
| 59 | +│ async def hello(): │ |
| 60 | +│ return {"message": "Hello"} │ |
| 61 | +└──────────────────────────────────────────────────────────────┘ |
| 62 | +``` |
| 63 | + |
| 64 | +## Request Flow |
| 65 | + |
| 66 | +### 1. Initialization (Worker Init) |
| 67 | + |
| 68 | +``` |
| 69 | +Host → Proxy Worker → FastAPI Runtime |
| 70 | + ↓ |
| 71 | + indexer.py |
| 72 | + ↓ |
| 73 | + Discover FastAPI app |
| 74 | + ↓ |
| 75 | + Scan all routes |
| 76 | + ↓ |
| 77 | + converter.py |
| 78 | + ↓ |
| 79 | + Convert to Azure Functions metadata |
| 80 | +``` |
| 81 | + |
| 82 | +### 2. Metadata Discovery (Function Metadata Request) |
| 83 | + |
| 84 | +``` |
| 85 | +Host → Proxy Worker → FastAPI Runtime → Return list of functions |
| 86 | + (one per FastAPI route) |
| 87 | +``` |
| 88 | + |
| 89 | +### 3. Function Invocation (HTTP Request) |
| 90 | + |
| 91 | +``` |
| 92 | +HTTP Request → Host → Proxy Worker → FastAPI Runtime |
| 93 | + ↓ |
| 94 | + handler.py |
| 95 | + ↓ |
| 96 | + Extract Azure Functions request |
| 97 | + ↓ |
| 98 | + Call FastAPI route handler |
| 99 | + ↓ |
| 100 | + Format response |
| 101 | + ↓ |
| 102 | +Host ← Proxy Worker ← FastAPI Runtime |
| 103 | +``` |
| 104 | + |
| 105 | +## Key Components |
| 106 | + |
| 107 | +### indexer.py - FastAPI Route Discovery |
| 108 | + |
| 109 | +**Purpose**: Discovers and catalogs all routes in a FastAPI application |
| 110 | + |
| 111 | +**Key Classes**: |
| 112 | +- `FastAPIIndexer`: Main indexer class |
| 113 | +- `FastAPIFunctionMetadata`: Metadata for each discovered route |
| 114 | + |
| 115 | +**Process**: |
| 116 | +1. Import the user's Python module |
| 117 | +2. Find the FastAPI app instance |
| 118 | +3. Iterate through `app.routes` |
| 119 | +4. Extract metadata for each route: |
| 120 | + - Route path (e.g., `/api/users/{id}`) |
| 121 | + - HTTP methods (GET, POST, etc.) |
| 122 | + - Handler function reference |
| 123 | + - Async vs sync |
| 124 | + |
| 125 | +### converter.py - Azure Functions Adapter |
| 126 | + |
| 127 | +**Purpose**: Converts FastAPI route metadata to Azure Functions format |
| 128 | + |
| 129 | +**Key Classes**: |
| 130 | +- `FastAPIConverter`: Converts routes to functions |
| 131 | +- `AzureFunctionInfo`: Azure Functions metadata structure |
| 132 | + |
| 133 | +**Process**: |
| 134 | +1. Take FastAPI route metadata |
| 135 | +2. Create HTTP trigger binding for each route |
| 136 | +3. Create HTTP output binding |
| 137 | +4. Generate function metadata compatible with Python worker |
| 138 | + |
| 139 | +**Binding Structure**: |
| 140 | +```python |
| 141 | +{ |
| 142 | + "name": "req", |
| 143 | + "type": "httpTrigger", |
| 144 | + "direction": "in", |
| 145 | + "methods": ["get"], |
| 146 | + "route": "api/users" |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +### handler.py - Request/Response Processing |
| 151 | + |
| 152 | +**Purpose**: Executes FastAPI routes and handles request/response conversion |
| 153 | + |
| 154 | +**Key Functions**: |
| 155 | +- `execute_fastapi_route()`: Main execution entry point |
| 156 | +- `FastAPIHandler.handle_request()`: Request processing |
| 157 | +- `_format_response()`: Response formatting |
| 158 | + |
| 159 | +**Request Flow**: |
| 160 | +1. Receive Azure Functions HTTP request |
| 161 | +2. Extract relevant data (method, URL, headers, body) |
| 162 | +3. Call the FastAPI route handler directly |
| 163 | +4. Convert result to Azure Functions response format |
| 164 | + |
| 165 | +### handle_event.py - Event Processing |
| 166 | + |
| 167 | +**Purpose**: Main event handler that responds to Azure Functions worker protocol |
| 168 | + |
| 169 | +**Key Functions**: |
| 170 | +- `worker_init_request()`: Initialize runtime, index app |
| 171 | +- `functions_metadata_request()`: Return discovered functions |
| 172 | +- `function_load_request()`: Load specific function |
| 173 | +- `invocation_request()`: Execute function |
| 174 | +- `function_environment_reload_request()`: Reload/re-index app |
| 175 | + |
| 176 | +## Data Flow |
| 177 | + |
| 178 | +### Route to Function Conversion |
| 179 | + |
| 180 | +``` |
| 181 | +FastAPI Route: |
| 182 | + Path: /api/users/{user_id} |
| 183 | + Method: GET |
| 184 | + Handler: async def get_user(user_id: int) |
| 185 | +
|
| 186 | + ↓ (indexer.py) |
| 187 | +
|
| 188 | +FastAPIFunctionMetadata: |
| 189 | + name: "get_api_users_user_id" |
| 190 | + route_path: "/api/users/{user_id}" |
| 191 | + http_methods: ["GET"] |
| 192 | + route_handler: <function get_user> |
| 193 | + is_async: True |
| 194 | +
|
| 195 | + ↓ (converter.py) |
| 196 | +
|
| 197 | +AzureFunctionInfo: |
| 198 | + name: "get_api_users_user_id" |
| 199 | + bindings: [ |
| 200 | + { |
| 201 | + "type": "httpTrigger", |
| 202 | + "direction": "in", |
| 203 | + "methods": ["get"], |
| 204 | + "route": "api/users/{user_id}" |
| 205 | + }, |
| 206 | + { |
| 207 | + "type": "http", |
| 208 | + "direction": "out" |
| 209 | + } |
| 210 | + ] |
| 211 | +
|
| 212 | + ↓ (handle_event.py) |
| 213 | +
|
| 214 | +RpcFunctionMetadata: |
| 215 | + (protobuf message sent to host) |
| 216 | +``` |
| 217 | + |
| 218 | +### Request Execution Flow |
| 219 | + |
| 220 | +``` |
| 221 | +HTTP GET /api/users/123 |
| 222 | +
|
| 223 | + ↓ |
| 224 | +
|
| 225 | +Azure Functions Host |
| 226 | + - Routes to function "get_api_users_user_id" |
| 227 | +
|
| 228 | + ↓ |
| 229 | +
|
| 230 | +Proxy Worker |
| 231 | + - Forwards invocation request |
| 232 | +
|
| 233 | + ↓ |
| 234 | +
|
| 235 | +FastAPI Runtime (invocation_request) |
| 236 | + - Looks up function metadata |
| 237 | + - Extracts HTTP request data |
| 238 | +
|
| 239 | + ↓ |
| 240 | +
|
| 241 | +handler.py (execute_fastapi_route) |
| 242 | + - Calls get_user(user_id=123) |
| 243 | + - Returns result |
| 244 | +
|
| 245 | + ↓ |
| 246 | +
|
| 247 | +Format Response |
| 248 | + { |
| 249 | + "status_code": 200, |
| 250 | + "headers": {...}, |
| 251 | + "body": '{"user": {...}}' |
| 252 | + } |
| 253 | +
|
| 254 | + ↓ |
| 255 | +
|
| 256 | +Return to Host → HTTP Response |
| 257 | +``` |
| 258 | + |
| 259 | +## Key Design Decisions |
| 260 | + |
| 261 | +### 1. Direct Handler Invocation |
| 262 | +- Routes are executed by calling the FastAPI handler directly |
| 263 | +- Bypasses ASGI server for performance |
| 264 | +- Simplifies integration with Azure Functions |
| 265 | + |
| 266 | +### 2. Route-to-Function Mapping |
| 267 | +- Each FastAPI route becomes a separate Azure Function |
| 268 | +- Maintains granular control and monitoring |
| 269 | +- Enables per-route configuration |
| 270 | + |
| 271 | +### 3. Metadata-Driven Indexing |
| 272 | +- App is indexed once during initialization |
| 273 | +- Metadata cached for fast lookups |
| 274 | +- Re-indexing supported for hot reload |
| 275 | + |
| 276 | +### 4. Protobuf Communication |
| 277 | +- Uses same protocol as standard Python worker |
| 278 | +- Seamless integration with host |
| 279 | +- No changes needed to Azure Functions infrastructure |
| 280 | + |
| 281 | +## Integration Points |
| 282 | + |
| 283 | +### With Proxy Worker |
| 284 | +- Proxy worker imports and calls FastAPI runtime functions |
| 285 | +- Uses standard Python function calls (not gRPC internally) |
| 286 | +- Passes protobuf objects for requests/responses |
| 287 | + |
| 288 | +### With User's FastAPI App |
| 289 | +- Runtime imports user's module dynamically |
| 290 | +- Discovers FastAPI app instance via reflection |
| 291 | +- Maintains reference to route handlers for invocation |
| 292 | + |
| 293 | +### With Azure Functions Host |
| 294 | +- Communicates via gRPC (through proxy worker) |
| 295 | +- Uses standard worker protocol |
| 296 | +- Reports functions via metadata requests |
| 297 | + |
| 298 | +## Future Enhancements |
| 299 | + |
| 300 | +### 1. Full ASGI Protocol Support |
| 301 | +- Implement complete ASGI lifecycle |
| 302 | +- Support ASGI middleware |
| 303 | +- Enable streaming responses |
| 304 | + |
| 305 | +### 2. Advanced FastAPI Features |
| 306 | +- Dependency injection support |
| 307 | +- Background tasks |
| 308 | +- WebSocket support |
| 309 | +- Lifespan events |
| 310 | + |
| 311 | +### 3. Performance Optimizations |
| 312 | +- Connection pooling |
| 313 | +- Response caching |
| 314 | +- Lazy loading of routes |
| 315 | + |
| 316 | +### 4. Development Experience |
| 317 | +- Hot reload support |
| 318 | +- Better error messages |
| 319 | +- FastAPI-specific debugging tools |
| 320 | + |
| 321 | +## Testing Strategy |
| 322 | + |
| 323 | +### Unit Tests |
| 324 | +- `test_indexer.py`: Route discovery |
| 325 | +- `test_converter.py`: Metadata conversion |
| 326 | +- `test_handler.py`: Request/response handling (TODO) |
| 327 | + |
| 328 | +### Integration Tests |
| 329 | +- `test_example_app.py`: End-to-end with sample app |
| 330 | +- Real FastAPI app indexing and conversion |
| 331 | + |
| 332 | +### Manual Testing |
| 333 | +- Deploy to Azure Functions |
| 334 | +- Test with actual HTTP requests |
| 335 | +- Verify monitoring and logging |
0 commit comments