23f3003322 commited on
Commit
d1bf1d0
·
0 Parent(s):

initial commit

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.env.example ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Application Configuration
2
+ APP_NAME=LLM Analysis Quiz API
3
+ ENVIRONMENT=development
4
+
5
+ # Server Configuration
6
+ HOST=0.0.0.0
7
+ PORT=8000
8
+
9
+ # Security
10
+ API_SECRET=your-secret-key-here
11
+ ALLOWED_ORIGINS=*
12
+
13
+ # Logging
14
+ LOG_LEVEL=INFO
15
+ LOG_DIR=logs
16
+
17
+ # Task Processing
18
+ TASK_TIMEOUT=300
19
+ MAX_RETRIES=3
20
+
21
+ # External APIs
22
+ OPENAI_API_KEY=
23
+ ANTHROPIC_API_KEY=
24
+
25
+ # Database (optional)
26
+ DATABASE_URL=
27
+
28
+ # Redis (optional)
29
+ REDIS_URL=
30
+ LOGTAIL_SOURCE_TOKEN=
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .env
2
+ logs
README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: LLM QUIZ ANALYSIS API
3
+ emoji: 🚀
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ sdk_version: "0.0.1"
8
+ app_file: app/main.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at [https://huggingface.co/docs/hub/spaces-config-reference](https://huggingface.co/docs/hub/spaces-config-reference)
13
+
14
+ ## LLM Code Deployment API
15
+
16
+ This Space provides a production-grade FastAPI backend for automated application generation and deployment, as described in the LLM Code Deployment project. It supports secure secret-based access, LLM-driven app generation, automated deployment to GitHub Pages, and evaluation callbacks—built with best practices for industry and Hugging Face Spaces Docker deployments.
17
+
18
+ ### Key Features
19
+
20
+ - **POST `/handle-task`**: Receives and processes app brief requests, verifies secrets, and triggers app generation workflows.
21
+ - **Dockerized Deployment**: Secure, reproducible builds using a custom Dockerfile following Hugging Face recommendations.
22
+ - **Secrets Management**: Reads secrets via Hugging Face Spaces environment for maximum security (never hardcoded).
23
+ - **GitHub Automation**: Automatically creates public repos, populates README and LICENSE, and enables GitHub Pages.
24
+
25
+ ### Usage
26
+
27
+ 1. Set up required Space secrets (secrets and tokens via the Spaces UI).
28
+ 2. Deploy or push your code to this Space.
29
+ 3. POST a task request (see `/handle-task` endpoint documentation).
30
+
31
+ ### Development
32
+
33
+ See `requirements.txt` for dependencies.
34
+ Your FastAPI entry point should be specified in `app/main.py`.
35
+
36
+ ---
37
+
38
+ For further configuration options, please visit the [Spaces Configuration Reference](https://huggingface.co/docs/hub/spaces-config-reference).
app/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """
2
+ LLM Analysis Quiz API Application Package
3
+ """
4
+
5
+ __version__ = "1.0.0"
6
+ __author__ = "Your Name"
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (300 Bytes). View file
 
app/__pycache__/main.cpython-313.pyc ADDED
Binary file (3.86 kB). View file
 
app/api/__pycache__/dependencies.cpython-313.pyc ADDED
Binary file (2.08 kB). View file
 
app/api/dependencies.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API Dependencies
3
+ Reusable dependency injection functions
4
+ """
5
+
6
+ from fastapi import Request, HTTPException, status
7
+
8
+ from app.core.security import verify_secret
9
+ from app.core.exceptions import AuthenticationError
10
+ from app.core.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ async def get_request_body(request: Request) -> dict:
16
+ """
17
+ Extract and validate JSON body from request
18
+
19
+ Args:
20
+ request: FastAPI Request object
21
+
22
+ Returns:
23
+ dict: Parsed JSON body
24
+
25
+ Raises:
26
+ HTTPException: If JSON parsing fails
27
+ """
28
+ try:
29
+ body = await request.json()
30
+ logger.debug(f"Request body received: {list(body.keys())}")
31
+ return body
32
+ except Exception as e:
33
+ logger.error(f"Failed to parse JSON: {str(e)}")
34
+ raise HTTPException(
35
+ status_code=status.HTTP_400_BAD_REQUEST,
36
+ detail="Invalid JSON format in request body"
37
+ )
38
+
39
+
40
+ def verify_authentication(secret: str) -> bool:
41
+ """
42
+ Verify request authentication
43
+
44
+ Args:
45
+ secret: Secret from request
46
+
47
+ Returns:
48
+ bool: True if authenticated
49
+
50
+ Raises:
51
+ AuthenticationError: If authentication fails
52
+ """
53
+ if not verify_secret(secret):
54
+ raise AuthenticationError("Invalid secret. Authentication failed.")
55
+
56
+ return True
app/api/routes/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ API Routes Package
3
+ """
4
+
5
+ from app.api.routes import task, health
6
+
7
+ __all__ = ["task", "health"]
app/api/routes/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (330 Bytes). View file
 
app/api/routes/__pycache__/health.cpython-313.pyc ADDED
Binary file (1.63 kB). View file
 
app/api/routes/__pycache__/task.cpython-313.pyc ADDED
Binary file (2.82 kB). View file
 
app/api/routes/health.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Health Check Routes
3
+ Endpoints for monitoring application health
4
+ """
5
+
6
+ from fastapi import APIRouter
7
+
8
+ from app.core.config import settings
9
+ from app.core.logging import get_logger
10
+ from app.models.response import HealthResponse
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ @router.get("/", response_model=dict)
18
+ async def root():
19
+ """
20
+ Root endpoint - basic health check
21
+ """
22
+ logger.debug("Root endpoint accessed")
23
+ return {
24
+ "status": "online",
25
+ "service": settings.APP_NAME,
26
+ "version": settings.APP_VERSION
27
+ }
28
+
29
+
30
+ @router.get("/health", response_model=HealthResponse)
31
+ async def health_check():
32
+ """
33
+ Detailed health check endpoint
34
+ """
35
+ logger.debug("Health check requested")
36
+
37
+ return HealthResponse(
38
+ status="healthy",
39
+ environment=settings.ENVIRONMENT,
40
+ version=settings.APP_VERSION,
41
+ secret_configured=settings.is_secret_configured()
42
+ )
app/api/routes/task.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Task Processing Routes
3
+ Main API endpoint for handling task requests
4
+ """
5
+
6
+ from datetime import datetime
7
+ from fastapi import APIRouter, Request, status
8
+
9
+ from app.models.request import TaskRequest
10
+ from app.models.response import TaskResponse
11
+ from app.api.dependencies import verify_authentication
12
+ from app.services.task_processor import TaskProcessor
13
+ from app.core.logging import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+ router = APIRouter()
18
+ task_processor = TaskProcessor()
19
+
20
+
21
+ @router.post("/api/", response_model=TaskResponse, status_code=status.HTTP_200_OK)
22
+ async def handle_task(request: Request):
23
+ """
24
+ Main API endpoint for handling task requests
25
+
26
+ - Validates request format (HTTP 400 if invalid)
27
+ - Verifies secret (HTTP 403 if invalid)
28
+ - Processes task and returns results (HTTP 200 if successful)
29
+ """
30
+ start_time = datetime.now()
31
+
32
+ logger.info("📥 Task request received")
33
+
34
+ # Parse and validate request body with Pydantic
35
+ body = await request.json()
36
+ task_data = TaskRequest(**body)
37
+
38
+ logger.info(f"✅ Request validated for: {task_data.email}")
39
+
40
+ # Verify authentication
41
+ logger.info("🔐 Verifying authentication")
42
+ verify_authentication(task_data.secret)
43
+ logger.info("✅ Authentication successful")
44
+
45
+ # Process the task
46
+ logger.info("🚀 Starting task execution")
47
+ result_data = await task_processor.process(task_data)
48
+
49
+ # Calculate execution time
50
+ execution_time = (datetime.now() - start_time).total_seconds()
51
+ logger.info(f"⏱️ Task completed in {execution_time:.3f}s")
52
+
53
+ # Prepare response
54
+ response = TaskResponse(
55
+ success=True,
56
+ message="Task completed successfully",
57
+ data=result_data,
58
+ email=task_data.email,
59
+ task_url=str(task_data.url),
60
+ execution_time=execution_time
61
+ )
62
+
63
+ logger.info("✅ Response prepared successfully")
64
+ return response
app/core/__pycache__/config.cpython-313.pyc ADDED
Binary file (3.13 kB). View file
 
app/core/__pycache__/exceptions.cpython-313.pyc ADDED
Binary file (6.21 kB). View file
 
app/core/__pycache__/logging.cpython-313.pyc ADDED
Binary file (3.37 kB). View file
 
app/core/__pycache__/security.cpython-313.pyc ADDED
Binary file (2.06 kB). View file
 
app/core/config.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Application Configuration Management
3
+ Centralizes all environment variables and settings
4
+ """
5
+
6
+ import os
7
+ from typing import List
8
+ from pydantic_settings import BaseSettings
9
+ from pydantic import Field
10
+
11
+
12
+ class Settings(BaseSettings):
13
+ """
14
+ Application settings loaded from environment variables
15
+ """
16
+
17
+ # Application
18
+ APP_NAME: str = "LLM Analysis Quiz API"
19
+ APP_DESCRIPTION: str = "API endpoint for handling dynamic data analysis tasks"
20
+ APP_VERSION: str = "1.0.0"
21
+ ENVIRONMENT: str = Field(default="development", env="ENVIRONMENT")
22
+
23
+ # Server
24
+ HOST: str = Field(default="0.0.0.0", env="HOST")
25
+ PORT: int = Field(default=8000, env="PORT")
26
+
27
+ # Security
28
+ API_SECRET: str = Field(default="", env="API_SECRET")
29
+ ALLOWED_ORIGINS: List[str] = Field(
30
+ default=["*"],
31
+ env="ALLOWED_ORIGINS"
32
+ )
33
+
34
+ # Logging
35
+ LOG_LEVEL: str = Field(default="INFO", env="LOG_LEVEL")
36
+ LOG_DIR: str = Field(default="logs", env="LOG_DIR")
37
+ # Cloud Logging
38
+ LOGTAIL_SOURCE_TOKEN: str = Field(default="", env="LOGTAIL_SOURCE_TOKEN")
39
+
40
+
41
+ # Task Processing
42
+ TASK_TIMEOUT: int = Field(default=300, env="TASK_TIMEOUT") # 5 minutes
43
+ MAX_RETRIES: int = Field(default=3, env="MAX_RETRIES")
44
+
45
+ # External APIs (to be added as needed)
46
+ OPENAI_API_KEY: str = Field(default="", env="OPENAI_API_KEY")
47
+ ANTHROPIC_API_KEY: str = Field(default="", env="ANTHROPIC_API_KEY")
48
+
49
+ # Database (for future use)
50
+ DATABASE_URL: str = Field(default="", env="DATABASE_URL")
51
+
52
+ # Redis (for caching, future use)
53
+ REDIS_URL: str = Field(default="", env="REDIS_URL")
54
+
55
+ class Config:
56
+ env_file = ".env"
57
+ env_file_encoding = "utf-8"
58
+ case_sensitive = True
59
+
60
+ def is_secret_configured(self) -> bool:
61
+ """Check if API secret is configured"""
62
+ return bool(self.API_SECRET and self.API_SECRET.strip())
63
+
64
+ def is_production(self) -> bool:
65
+ """Check if running in production"""
66
+ return self.ENVIRONMENT.lower() == "production"
67
+
68
+ def is_development(self) -> bool:
69
+ """Check if running in development"""
70
+ return self.ENVIRONMENT.lower() == "development"
71
+
72
+
73
+ # Global settings instance
74
+ settings = Settings()
app/core/exceptions.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Custom Exceptions and Exception Handlers
3
+ Centralizes error handling logic
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import Any, Dict
8
+
9
+ from fastapi import FastAPI, Request, status
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.exceptions import RequestValidationError, HTTPException
12
+
13
+ from app.core.logging import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class TaskProcessingError(Exception):
19
+ """Raised when task processing fails"""
20
+ pass
21
+
22
+
23
+ class AuthenticationError(Exception):
24
+ """Raised when authentication fails"""
25
+ pass
26
+
27
+
28
+ def create_error_response(
29
+ error_type: str,
30
+ message: str,
31
+ status_code: int,
32
+ details: Any = None
33
+ ) -> JSONResponse:
34
+ """
35
+ Create a standardized error response
36
+
37
+ Args:
38
+ error_type: Type of error
39
+ message: Error message
40
+ status_code: HTTP status code
41
+ details: Optional additional details
42
+
43
+ Returns:
44
+ JSONResponse: Formatted error response
45
+ """
46
+ content = {
47
+ "success": False,
48
+ "error": error_type,
49
+ "message": message,
50
+ "timestamp": datetime.now().isoformat()
51
+ }
52
+
53
+ if details:
54
+ content["details"] = details
55
+
56
+ return JSONResponse(
57
+ status_code=status_code,
58
+ content=content
59
+ )
60
+
61
+
62
+ def register_exception_handlers(app: FastAPI):
63
+ """
64
+ Register all exception handlers with the FastAPI app
65
+
66
+ Args:
67
+ app: FastAPI application instance
68
+ """
69
+
70
+ @app.exception_handler(RequestValidationError)
71
+ async def validation_exception_handler(
72
+ request: Request,
73
+ exc: RequestValidationError
74
+ ):
75
+ """Handle Pydantic validation errors (HTTP 400)"""
76
+ error_details = exc.errors()
77
+ logger.error(f"❌ Validation Error: {error_details}")
78
+
79
+ # Extract readable error messages
80
+ error_messages = []
81
+ for error in error_details:
82
+ field = " -> ".join(str(loc) for loc in error['loc'])
83
+ message = error['msg']
84
+ error_messages.append(f"{field}: {message}")
85
+
86
+ return create_error_response(
87
+ error_type="ValidationError",
88
+ message="Invalid request format. " + "; ".join(error_messages),
89
+ status_code=status.HTTP_400_BAD_REQUEST,
90
+ details=error_details
91
+ )
92
+
93
+ @app.exception_handler(HTTPException)
94
+ async def http_exception_handler(request: Request, exc: HTTPException):
95
+ """Handle HTTP exceptions"""
96
+ logger.error(f"❌ HTTP {exc.status_code}: {exc.detail}")
97
+
98
+ return create_error_response(
99
+ error_type=f"HTTP{exc.status_code}",
100
+ message=exc.detail,
101
+ status_code=exc.status_code
102
+ )
103
+
104
+ @app.exception_handler(TaskProcessingError)
105
+ async def task_processing_exception_handler(
106
+ request: Request,
107
+ exc: TaskProcessingError
108
+ ):
109
+ """Handle task processing errors"""
110
+ logger.error(f"❌ Task Processing Error: {str(exc)}", exc_info=True)
111
+
112
+ return create_error_response(
113
+ error_type="TaskProcessingError",
114
+ message=str(exc),
115
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
116
+ )
117
+
118
+ @app.exception_handler(AuthenticationError)
119
+ async def authentication_exception_handler(
120
+ request: Request,
121
+ exc: AuthenticationError
122
+ ):
123
+ """Handle authentication errors"""
124
+ logger.warning(f"🚫 Authentication Error: {str(exc)}")
125
+
126
+ return create_error_response(
127
+ error_type="AuthenticationError",
128
+ message=str(exc),
129
+ status_code=status.HTTP_403_FORBIDDEN
130
+ )
131
+
132
+ @app.exception_handler(Exception)
133
+ async def general_exception_handler(request: Request, exc: Exception):
134
+ """Catch-all handler for unexpected exceptions"""
135
+ logger.error(
136
+ f"❌ Unhandled Exception: {str(exc)}",
137
+ exc_info=True
138
+ )
139
+
140
+ return create_error_response(
141
+ error_type="InternalServerError",
142
+ message="An unexpected error occurred while processing your request",
143
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
144
+ )
app/core/logging.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Production Logging with Better Stack (Logtail)
3
+ Persistent cloud logging for evaluation and debugging
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import logging
9
+ from pathlib import Path
10
+
11
+ from app.core.config import settings
12
+
13
+
14
+ def setup_logging():
15
+ """
16
+ Configure logging with cloud persistence via Better Stack
17
+ """
18
+
19
+ # Base formatter
20
+ formatter = logging.Formatter(
21
+ fmt='%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s',
22
+ datefmt='%Y-%m-%d %H:%M:%S'
23
+ )
24
+
25
+ # Configure root logger
26
+ root_logger = logging.getLogger()
27
+ root_logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
28
+ root_logger.handlers.clear()
29
+
30
+ # Console handler (for HF Spaces logs viewer)
31
+ console_handler = logging.StreamHandler(sys.stdout)
32
+ console_handler.setLevel(logging.INFO)
33
+ console_handler.setFormatter(formatter)
34
+ root_logger.addHandler(console_handler)
35
+
36
+ # Better Stack handler for persistent logging
37
+ if settings.LOGTAIL_SOURCE_TOKEN:
38
+ try:
39
+ from logtail import LogtailHandler
40
+
41
+ logtail_handler = LogtailHandler(source_token=settings.LOGTAIL_SOURCE_TOKEN)
42
+ logtail_handler.setLevel(logging.INFO)
43
+ logtail_handler.setFormatter(formatter)
44
+ root_logger.addHandler(logtail_handler)
45
+
46
+ logger = logging.getLogger(__name__)
47
+ logger.info("✅ Better Stack logging enabled - logs will persist")
48
+ except Exception as e:
49
+ logger = logging.getLogger(__name__)
50
+ logger.warning(f"⚠️ Failed to initialize Better Stack: {e}")
51
+ else:
52
+ logger = logging.getLogger(__name__)
53
+ logger.warning("⚠️ LOGTAIL_SOURCE_TOKEN not set - logs will not persist")
54
+
55
+ # Reduce third-party noise
56
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
57
+ logging.getLogger("httpx").setLevel(logging.WARNING)
58
+
59
+ logger = logging.getLogger(__name__)
60
+ logger.info("Logging initialized for %s", settings.ENVIRONMENT)
61
+
62
+
63
+ def get_logger(name: str) -> logging.Logger:
64
+ """Get a logger instance"""
65
+ return logging.getLogger(name)
app/core/security.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Security Utilities
3
+ Handles authentication and authorization
4
+ """
5
+
6
+ from app.core.config import settings
7
+ from app.core.logging import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ def verify_secret(provided_secret: str) -> bool:
13
+ """
14
+ Verify the provided secret against environment configuration
15
+
16
+ Args:
17
+ provided_secret: Secret from request
18
+
19
+ Returns:
20
+ bool: True if secret matches, False otherwise
21
+ """
22
+ if not settings.is_secret_configured():
23
+ logger.error("⚠️ API_SECRET not configured in environment")
24
+ return False
25
+
26
+ is_valid = provided_secret == settings.API_SECRET
27
+
28
+ if is_valid:
29
+ logger.info("✅ Secret verification successful")
30
+ else:
31
+ logger.warning("🚫 Secret verification failed")
32
+ logger.debug(
33
+ f"Expected length: {len(settings.API_SECRET)}, "
34
+ f"Got length: {len(provided_secret)}"
35
+ )
36
+
37
+ return is_valid
38
+
39
+
40
+ def mask_secret(secret: str, visible_chars: int = 4) -> str:
41
+ """
42
+ Mask secret for logging purposes
43
+
44
+ Args:
45
+ secret: Secret to mask
46
+ visible_chars: Number of characters to show at the end
47
+
48
+ Returns:
49
+ str: Masked secret
50
+ """
51
+ if not secret:
52
+ return ""
53
+
54
+ if len(secret) <= visible_chars:
55
+ return "*" * len(secret)
56
+
57
+ return "*" * (len(secret) - visible_chars) + secret[-visible_chars:]
app/main.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main FastAPI Application Entry Point
3
+ Initializes the FastAPI app with all configurations, middleware, and routes
4
+ """
5
+
6
+ from fastapi import FastAPI
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from contextlib import asynccontextmanager
9
+
10
+ from app.core.config import settings
11
+ from app.core.logging import setup_logging, get_logger
12
+ from app.core.exceptions import register_exception_handlers
13
+ from app.middleware.logging import LoggingMiddleware
14
+ from app.api.routes import task, health
15
+
16
+
17
+ # Initialize logger
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ @asynccontextmanager
22
+ async def lifespan(app: FastAPI):
23
+ """
24
+ Application lifespan manager for startup and shutdown events
25
+ """
26
+ # Startup
27
+ logger.info("=" * 80)
28
+ logger.info("🚀 Starting LLM Analysis Quiz API")
29
+ logger.info(f"Environment: {settings.ENVIRONMENT}")
30
+ logger.info(f"Version: {settings.APP_VERSION}")
31
+ logger.info(f"Secret configured: {settings.is_secret_configured()}")
32
+ logger.info("=" * 80)
33
+
34
+ if not settings.is_secret_configured():
35
+ logger.warning("⚠️ WARNING: API_SECRET not configured!")
36
+
37
+ yield
38
+
39
+ # Shutdown
40
+ logger.info("=" * 80)
41
+ logger.info("🛑 Shutting down LLM Analysis Quiz API")
42
+ logger.info("=" * 80)
43
+
44
+
45
+
46
+ def create_application() -> FastAPI:
47
+ """
48
+ Application factory pattern for creating FastAPI instance
49
+ """
50
+ app = FastAPI(
51
+ title=settings.APP_NAME,
52
+ description=settings.APP_DESCRIPTION,
53
+ version=settings.APP_VERSION,
54
+ lifespan=lifespan,
55
+ docs_url="/docs" if settings.ENVIRONMENT == "development" else None,
56
+ redoc_url="/redoc" if settings.ENVIRONMENT == "development" else None,
57
+ )
58
+
59
+ # Configure CORS
60
+ app.add_middleware(
61
+ CORSMiddleware,
62
+ allow_origins=settings.ALLOWED_ORIGINS,
63
+ allow_credentials=True,
64
+ allow_methods=["*"],
65
+ allow_headers=["*"],
66
+ )
67
+
68
+ # Add custom middleware
69
+ app.add_middleware(LoggingMiddleware)
70
+
71
+ # Register exception handlers
72
+ register_exception_handlers(app)
73
+
74
+ # Include routers
75
+ app.include_router(health.router, tags=["Health"])
76
+ app.include_router(task.router, tags=["Tasks"])
77
+
78
+ return app
79
+
80
+
81
+ # Initialize logging
82
+ setup_logging()
83
+
84
+ # Create app instance
85
+ app = create_application()
86
+
87
+
88
+ if __name__ == "__main__":
89
+ import uvicorn
90
+
91
+ logger.info(f"Starting server on {settings.HOST}:{settings.PORT}")
92
+
93
+ uvicorn.run(
94
+ "app.main:app",
95
+ host=settings.HOST,
96
+ port=settings.PORT,
97
+ reload=settings.ENVIRONMENT == "development",
98
+ log_level="info"
99
+ )
app/middleware/__pycache__/logging.cpython-313.pyc ADDED
Binary file (2.21 kB). View file
 
app/middleware/logging.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Logging Middleware
3
+ Logs all incoming requests and responses
4
+ """
5
+
6
+ import time
7
+ from fastapi import Request
8
+ from starlette.middleware.base import BaseHTTPMiddleware
9
+
10
+ from app.core.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class LoggingMiddleware(BaseHTTPMiddleware):
16
+ """
17
+ Middleware to log all HTTP requests and responses
18
+ """
19
+
20
+ async def dispatch(self, request: Request, call_next):
21
+ """
22
+ Process each request and log details
23
+ """
24
+ start_time = time.time()
25
+
26
+ # Log incoming request
27
+ logger.info(f"📥 {request.method} {request.url.path}")
28
+ logger.debug(f"Client: {request.client.host if request.client else 'Unknown'}")
29
+
30
+ # Process request
31
+ try:
32
+ response = await call_next(request)
33
+
34
+ # Calculate execution time
35
+ execution_time = time.time() - start_time
36
+
37
+ # Log response
38
+ logger.info(
39
+ f"📤 Response: {response.status_code} | "
40
+ f"Time: {execution_time:.3f}s"
41
+ )
42
+
43
+ return response
44
+
45
+ except Exception as e:
46
+ execution_time = time.time() - start_time
47
+ logger.error(
48
+ f"❌ Request failed after {execution_time:.3f}s: {str(e)}",
49
+ exc_info=True
50
+ )
51
+ raise
app/models/__pycache__/request.cpython-313.pyc ADDED
Binary file (2.34 kB). View file
 
app/models/__pycache__/response.cpython-313.pyc ADDED
Binary file (2.95 kB). View file
 
app/models/request.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Request Models (Pydantic Schemas)
3
+ Defines the structure of incoming requests
4
+ """
5
+
6
+ from typing import Optional, Dict, Any
7
+ from pydantic import BaseModel, Field, EmailStr, HttpUrl, validator
8
+
9
+
10
+ class TaskRequest(BaseModel):
11
+ """
12
+ Schema for task request validation
13
+ """
14
+ email: EmailStr = Field(
15
+ ...,
16
+ description="Student email ID",
17
+ examples=["[email protected]"]
18
+ )
19
+
20
+ secret: str = Field(
21
+ ...,
22
+ min_length=1,
23
+ description="Student-provided secret for authentication",
24
+ examples=["my-secret-key-123"]
25
+ )
26
+
27
+ url: HttpUrl = Field(
28
+ ...,
29
+ description="Unique task URL containing the quiz question",
30
+ examples=["https://example.com/quiz-834"]
31
+ )
32
+
33
+ task_description: Optional[str] = Field(
34
+ None,
35
+ description="Optional description of the task to perform"
36
+ )
37
+
38
+ additional_params: Optional[Dict[str, Any]] = Field(
39
+ None,
40
+ description="Optional additional parameters for task execution"
41
+ )
42
+
43
+ @validator('secret')
44
+ def secret_not_empty(cls, v):
45
+ """Ensure secret is not just whitespace"""
46
+ if not v or not v.strip():
47
+ raise ValueError('Secret cannot be empty or whitespace')
48
+ return v.strip()
49
+
50
+ class Config:
51
+ json_schema_extra = {
52
+ "example": {
53
+ "email": "[email protected]",
54
+ "secret": "your-secret-key",
55
+ "url": "https://example.com/quiz-834",
56
+ "task_description": "Scrape the webpage and analyze data"
57
+ }
58
+ }
app/models/response.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Response Models (Pydantic Schemas)
3
+ Defines the structure of API responses
4
+ """
5
+
6
+ from typing import Optional, Dict, Any
7
+ from datetime import datetime
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class TaskResponse(BaseModel):
12
+ """
13
+ Schema for successful task responses
14
+ """
15
+ success: bool = Field(
16
+ ...,
17
+ description="Whether the task completed successfully"
18
+ )
19
+
20
+ message: str = Field(
21
+ ...,
22
+ description="Status message"
23
+ )
24
+
25
+ data: Optional[Dict[str, Any]] = Field(
26
+ None,
27
+ description="Task result data"
28
+ )
29
+
30
+ email: Optional[str] = Field(
31
+ None,
32
+ description="Email from request"
33
+ )
34
+
35
+ task_url: Optional[str] = Field(
36
+ None,
37
+ description="Task URL processed"
38
+ )
39
+
40
+ execution_time: Optional[float] = Field(
41
+ None,
42
+ description="Execution time in seconds"
43
+ )
44
+
45
+ timestamp: str = Field(
46
+ default_factory=lambda: datetime.now().isoformat(),
47
+ description="Response timestamp"
48
+ )
49
+
50
+ class Config:
51
+ json_schema_extra = {
52
+ "example": {
53
+ "success": True,
54
+ "message": "Task completed successfully",
55
+ "data": {"result": "Analysis complete"},
56
+ "email": "[email protected]",
57
+ "task_url": "https://example.com/quiz-834",
58
+ "execution_time": 2.34,
59
+ "timestamp": "2025-11-27T23:48:00"
60
+ }
61
+ }
62
+
63
+
64
+ class HealthResponse(BaseModel):
65
+ """
66
+ Schema for health check responses
67
+ """
68
+ status: str = Field(..., description="Service status")
69
+ timestamp: str = Field(
70
+ default_factory=lambda: datetime.now().isoformat()
71
+ )
72
+ environment: Optional[str] = Field(None)
73
+ version: Optional[str] = Field(None)
74
+ secret_configured: Optional[bool] = Field(None)
app/services/__pycache__/task_processor.cpython-313.pyc ADDED
Binary file (2.4 kB). View file
 
app/services/task_processor.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Task Processing Service
3
+ Business logic for processing tasks
4
+ """
5
+
6
+ from typing import Dict, Any
7
+
8
+ from app.models.request import TaskRequest
9
+ from app.core.logging import get_logger
10
+ from app.core.exceptions import TaskProcessingError
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class TaskProcessor:
16
+ """
17
+ Service class for processing tasks
18
+ Handles the core business logic
19
+ """
20
+
21
+ def __init__(self):
22
+ logger.debug("TaskProcessor initialized")
23
+
24
+ async def process(self, task_data: TaskRequest) -> Dict[str, Any]:
25
+ """
26
+ Process a task based on the provided data
27
+
28
+ Args:
29
+ task_data: Validated task request
30
+
31
+ Returns:
32
+ Dict containing task results
33
+
34
+ Raises:
35
+ TaskProcessingError: If processing fails
36
+ """
37
+ logger.info(f"Processing task for: {task_data.email}")
38
+ logger.info(f"Task URL: {task_data.url}")
39
+
40
+ try:
41
+ # TODO: Implement actual task processing logic
42
+ # This will integrate with orchestrator and various modules
43
+
44
+ result = {
45
+ "status": "processed",
46
+ "task_url": str(task_data.url),
47
+ "message": "Task processing logic to be implemented",
48
+ "email": task_data.email
49
+ }
50
+
51
+ logger.info("✅ Task processed successfully")
52
+ return result
53
+
54
+ except Exception as e:
55
+ logger.error(f"❌ Task processing failed: {str(e)}", exc_info=True)
56
+ raise TaskProcessingError(f"Failed to process task: {str(e)}")
dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ RUN useradd -m -u 1000 user
4
+
5
+ WORKDIR /home/user/app
6
+
7
+
8
+ # Install dependencies
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+
11
+ RUN pip install -r requirements.txt
12
+
13
+ COPY --chown=user . .
14
+
15
+ USER user
16
+ ENV HOME=/home/user PATH=/home/user/.local/bin:$PATH
17
+
18
+ # Expose port
19
+ EXPOSE 7860
20
+
21
+ # Run with production settings
22
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Framework
2
+ fastapi==0.109.0
3
+ uvicorn[standard]==0.27.0
4
+ pydantic
5
+ pydantic-settings
6
+ pydantic[email]
7
+ python-dotenv==1.0.0
8
+ logtail-python
9
+
10
+ # HTTP Clients
11
+ httpx==0.26.0
12
+ requests==2.31.0
13
+
14
+ # Data Processing
15
+ # pandas==2.2.0
16
+ # numpy==1.26.3
17
+
18
+ # Add more as you build modules