Files

55 lines
2.2 KiB
Python

import os
import time
from fastapi import FastAPI, APIRouter
import debug
from uuid import uuid4
from typing import List
from contextlib import asynccontextmanager
from contextlib import AsyncExitStack
from typing import Callable
class SubApp:
def __init__(self, name:str, lifespan:Callable):
debug("START", name)
self.id = uuid4()
self.name = name
self.prefix = f"/api/{name}"
self.lifespan = lifespan
self.router = APIRouter()
debug("END")
def __str__(self):
return f"SubApp(name={self.name}, prefix={self.prefix}, id={self.id})"
class MainApp:
def __init__(self, sub_apps: List[SubApp]):
debug("START")
@asynccontextmanager
async def lifespan(app: FastAPI):
async with AsyncExitStack() as stack:
# [perf] per-lifespan boot timing. debug() is a no-op in the packaged build, so without this the packaged backend.log has no per-SubApp markers and a cold-start stall can only be guessed at. One perf_counter + flushed print per app pins exactly which lifespan (or the cold first-touch I/O entering it) dominates.
p_boot_t0 = time.perf_counter()
for sub_app in sub_apps:
debug(sub_app.name)
p_t0 = time.perf_counter()
await stack.enter_async_context(sub_app.lifespan())
p_dt = (time.perf_counter() - p_t0) * 1000
if p_dt > 50: # only flag a slow lifespan; keeps boot logs quiet
print(f"[perf] lifespan {sub_app.name} t={p_dt:.0f}ms", flush=True)
print(f"[perf] lifespans-total t={(time.perf_counter() - p_boot_t0) * 1000:.0f}ms", flush=True)
p_port = os.environ.get("OPENSWARM_PORT", "8324")
print(f"\nCheck out the API docs at: http://127.0.0.1:{p_port}/docs\n")
yield
self.app = FastAPI(lifespan=lifespan)
for sub_app in sub_apps:
self.app.include_router(
sub_app.router,
prefix=sub_app.prefix,
tags=[sub_app.name]
)
debug("END")