gateway/app.py
2025-05-03 08:22:02 +02:00

201 lines
No EOL
6.6 KiB
Python

import os
os.environ["NUMEXPR_MAX_THREADS"] = "12"
from fastapi import FastAPI, HTTPException, Depends, Body, status, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordRequestForm
from contextlib import asynccontextmanager
from typing import Dict, Any
import logging
from logging.handlers import RotatingFileHandler
from datetime import timedelta
import pathlib
from modules.configuration import APP_CONFIG
def initLogging():
# Get log level from config (default to INFO if not found)
logLevelName = APP_CONFIG.get("APP_LOGGING_LOG_LEVEL", "WARNING")
logLevel = getattr(logging, logLevelName)
# Configure handlers based on config
handlers = []
# Add console handler if enabled
if APP_CONFIG.get("APP_LOGGING_CONSOLE_ENABLED", True):
consoleHandler = logging.StreamHandler()
handlers.append(consoleHandler)
# Add file handler if enabled
if APP_CONFIG.get("APP_LOGGING_FILE_ENABLED", True):
logFile = APP_CONFIG.get("APP_LOGGING_LOG_FILE", "app.log")
rotationSize = int(APP_CONFIG.get("APP_LOGGING_ROTATION_SIZE", 10485760)) # Default: 10MB
backupCount = int(APP_CONFIG.get("APP_LOGGING_BACKUP_COUNT", 5))
fileHandler = RotatingFileHandler(
logFile,
maxBytes=rotationSize,
backupCount=backupCount
)
handlers.append(fileHandler)
# Configure the logger
logging.basicConfig(
level=logLevel,
format=APP_CONFIG.get("APP_LOGGING_FORMAT", "%(asctime)s - %(levelname)s - %(name)s - %(message)s"),
datefmt=APP_CONFIG.get("APP_LOGGING_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"),
handlers=handlers
)
# Silence noisy third-party libraries - use the same level as the root logger
noisyLoggers = ["httpx", "urllib3", "asyncio", "fastapi.security.oauth2"]
for loggerName in noisyLoggers:
logging.getLogger(loggerName).setLevel(logLevel)
# Initialize logging
initLogging()
logger = logging.getLogger(__name__)
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
# Import auth module
from modules.auth import (
createAccessToken,
getCurrentActiveUser,
getUserContext,
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import models - import generically for INITIALIZATION
import modules.gatewayModel as gatewayModel
from modules.gatewayInterface import getGatewayInterface
gateway = getGatewayInterface()
# Define lifespan context manager for application startup/shutdown events
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup logic (if any)
logger.info("Application is starting up")
yield
# Shutdown logic
logger.info("Application has been shut down")
# Parse CORS origins from environment variable
def get_allowed_origins():
origins_str = APP_CONFIG.get("APP_ALLOWED_ORIGINS", "http://localhost:8080")
# Split by comma and strip whitespace
origins = [origin.strip() for origin in origins_str.split(",")]
logger.info(f"CORS allowed origins: {origins}")
return origins
# START APP
app = FastAPI(
title="PowerOn | Data Platform API",
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instanceLabel})",
lifespan=lifespan
)
# CORS configuration using environment variables
app.add_middleware(
CORSMiddleware,
allow_origins= get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
expose_headers=["*"],
max_age=86400 # Increased caching for preflight requests
)
# Static folder for frontend - work with absolute path
baseDir = pathlib.Path(__file__).parent
staticFolder = baseDir / "static"
os.makedirs(staticFolder, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(staticFolder)), name="static")
# General Elements
@app.get("/", tags=["General"])
async def root():
"""API status endpoint"""
return {"status": "online", "message": "Data Platform API is active"}
@app.get("/api/test", tags=["General"])
async def getTest():
return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
@app.options("/{fullPath:path}", tags=["General"])
async def optionsRoute(fullPath: str):
return Response(status_code=200)
@app.get("/api/environment", tags=["General"])
async def get_environment():
"""Get environment configuration for frontend"""
return {
"apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
"environment": APP_CONFIG.get("APP_ENV", "development"),
"instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
# Add other environment variables the frontend might need
}
# Token endpoint for login
@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
# Initialize Gateway interface without context
gateway = getGatewayInterface()
# Authenticate user
user = gateway.authenticateUser(formData.username, formData.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create token with tenant ID
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
accessToken = createAccessToken(
data={
"sub": user["username"],
"mandateId": user["mandateId"]
},
expiresDelta=accessTokenExpires
)
return {"accessToken": accessToken, "tokenType": "bearer"}
# Get user info
@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
return currentUser
# Include all routers
from routes.routeAttributes import router as attributesRouter
app.include_router(attributesRouter)
gateway = getGatewayInterface()
from routes.routeMandates import router as mandateRouter
app.include_router(mandateRouter)
gateway = getGatewayInterface()
from routes.routeUsers import router as userRouter
app.include_router(userRouter)
from routes.routeFiles import router as fileRouter
app.include_router(fileRouter)
from routes.routePrompts import router as promptRouter
app.include_router(promptRouter)
from routes.routeWorkflows import router as workflowRouter
app.include_router(workflowRouter)
#if __name__ == "__main__":
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)