Spaces:
Sleeping
Sleeping
| # app.py | |
| from fastapi import FastAPI, HTTPException, Request, Depends, Security | |
| from fastapi.security import APIKeyHeader | |
| from fastapi.responses import JSONResponse | |
| from pydantic import BaseModel, Field | |
| from typing import Optional | |
| import uvicorn | |
| import time | |
| import os | |
| from moderator import get_moderator | |
| # Initialize FastAPI app | |
| app = FastAPI( | |
| title="Text Moderation API", | |
| description="API for moderating user-generated text content", | |
| version="1.0.0" | |
| ) | |
| # ============================================================================ | |
| # API KEY AUTHENTICATION | |
| # ============================================================================ | |
| # Get API key from environment variable (set in Hugging Face Space settings) | |
| API_KEY = os.getenv("MODERATION_API_KEY", "your-default-secret-key-here") | |
| API_KEY_NAME = "X-API-Key" | |
| # API Key header dependency | |
| api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=True) | |
| async def verify_api_key(api_key: str = Security(api_key_header)): | |
| """Verify the API key from request header""" | |
| if api_key != API_KEY: | |
| raise HTTPException( | |
| status_code=403, | |
| detail="Invalid API Key. Please provide a valid X-API-Key header." | |
| ) | |
| return api_key | |
| # ============================================================================ | |
| # REQUEST/RESPONSE MODELS | |
| # ============================================================================ | |
| class ModerationRequest(BaseModel): | |
| user_id: str = Field(..., description="User identifier") | |
| text: str = Field(..., description="Text content to moderate") | |
| class Config: | |
| json_schema_extra = { | |
| "example": { | |
| "user_id": "user123", | |
| "text": "I love this community!" | |
| } | |
| } | |
| class ModerationResponse(BaseModel): | |
| user_id: str | |
| action: str = Field(..., description="Action: allow, flag, or delete") | |
| reason: Optional[str] = Field(None, description="Reason for the action") | |
| toxic_score: Optional[float] = Field(None, description="Toxicity score (0-1)") | |
| hate_score: Optional[float] = Field(None, description="Hate speech score (0-1)") | |
| processing_time_ms: Optional[float] = Field(None, description="Processing time in milliseconds") | |
| # ============================================================================ | |
| # STARTUP | |
| # ============================================================================ | |
| async def startup_event(): | |
| print("Initializing moderator...") | |
| get_moderator() | |
| print("Moderator ready!") | |
| print(f"API Key authentication enabled (key length: {len(API_KEY)} characters)") | |
| # ============================================================================ | |
| # ENDPOINTS | |
| # ============================================================================ | |
| async def root(): | |
| return { | |
| "message": "Text Moderation API", | |
| "authentication": "Required - X-API-Key header", | |
| "endpoints": { | |
| "/moderate": "POST - Moderate text content (Protected)", | |
| "/health": "GET - Health check (Public)" | |
| } | |
| } | |
| async def health_check(): | |
| """Public health check endpoint - No API key required""" | |
| try: | |
| moderator = get_moderator() | |
| return { | |
| "status": "healthy", | |
| "models_loaded": moderator.toxic_model is not None and moderator.hate_model is not None, | |
| "device": moderator.device, | |
| "auth_enabled": True | |
| } | |
| except Exception as e: | |
| return JSONResponse( | |
| status_code=503, | |
| content={"status": "unhealthy", "error": str(e)} | |
| ) | |
| async def moderate_text( | |
| request: ModerationRequest, | |
| api_key: str = Depends(verify_api_key) # Protected endpoint | |
| ): | |
| """ | |
| Moderate text content and return action recommendation (Protected - Requires API Key) | |
| - **user_id**: Identifier for the user who posted the content | |
| - **text**: The text content to moderate | |
| - **X-API-Key**: API key in request header | |
| """ | |
| if not request.text or not request.text.strip(): | |
| raise HTTPException(status_code=400, detail="Text cannot be empty") | |
| if len(request.text) > 2000: | |
| raise HTTPException(status_code=400, detail="Text too long (max 2000 characters)") | |
| try: | |
| start_time = time.time() | |
| moderator = get_moderator() | |
| result = moderator.moderate(request.text) | |
| processing_time = (time.time() - start_time) * 1000 | |
| return ModerationResponse( | |
| user_id=request.user_id, | |
| action=result["action"], | |
| reason=result.get("reason"), | |
| toxic_score=result.get("toxic_score"), | |
| hate_score=result.get("hate_score"), | |
| processing_time_ms=round(processing_time, 2) | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Moderation error: {str(e)}") | |
| async def moderate_batch( | |
| requests: list[ModerationRequest], | |
| api_key: str = Depends(verify_api_key) # Protected endpoint | |
| ): | |
| """Moderate multiple texts in batch (Protected - Requires API Key)""" | |
| if len(requests) > 100: | |
| raise HTTPException(status_code=400, detail="Batch size too large (max 100)") | |
| moderator = get_moderator() | |
| responses = [] | |
| for req in requests: | |
| try: | |
| result = moderator.moderate(req.text) | |
| responses.append({ | |
| "user_id": req.user_id, | |
| "action": result["action"], | |
| "reason": result.get("reason"), | |
| "toxic_score": result.get("toxic_score"), | |
| "hate_score": result.get("hate_score") | |
| }) | |
| except Exception as e: | |
| responses.append({ | |
| "user_id": req.user_id, | |
| "action": "error", | |
| "reason": str(e) | |
| }) | |
| return {"results": responses} | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |