Spaces:
No application file
No application file
Cool Shot Systems
Browse files- Dockerfile (1).txt +31 -0
- README (4).md +77 -0
- auth.py +61 -0
- main.py +74 -0
- requirements.txt +5 -0
Dockerfile (1).txt
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Set environment variables
|
| 4 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 5 |
+
ENV PYTHONUNBUFFERED=1
|
| 6 |
+
|
| 7 |
+
# Create non-root user for security
|
| 8 |
+
RUN useradd -m -u 1000 appuser
|
| 9 |
+
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# Install dependencies first for better caching
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 15 |
+
pip install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# Copy application code
|
| 18 |
+
COPY --chown=appuser:appuser . .
|
| 19 |
+
|
| 20 |
+
# Switch to non-root user
|
| 21 |
+
USER appuser
|
| 22 |
+
|
| 23 |
+
# Hugging Face Spaces runs on port 7860 by default
|
| 24 |
+
EXPOSE 7860
|
| 25 |
+
|
| 26 |
+
# Health check
|
| 27 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 28 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/health')" || exit 1
|
| 29 |
+
|
| 30 |
+
# Run the application
|
| 31 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README (4).md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Text Prediction API
|
| 3 |
+
emoji: 📝
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
app_port: 7860
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Text Prediction API
|
| 13 |
+
|
| 14 |
+
AI-powered text prediction service built with FastAPI.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
- 🔐 **Secure Authentication** - JWT Bearer token verification
|
| 19 |
+
- 🚀 **Fast Performance** - Built on FastAPI with async support
|
| 20 |
+
- 📊 **Health Monitoring** - Built-in health check endpoint
|
| 21 |
+
- 🐳 **Docker Ready** - Optimized for Hugging Face Spaces deployment
|
| 22 |
+
|
| 23 |
+
## API Endpoints
|
| 24 |
+
|
| 25 |
+
### Health Check
|
| 26 |
+
```
|
| 27 |
+
GET /health
|
| 28 |
+
```
|
| 29 |
+
Returns the service health status.
|
| 30 |
+
|
| 31 |
+
### Prediction
|
| 32 |
+
```
|
| 33 |
+
POST /predict
|
| 34 |
+
Authorization: Bearer <token>
|
| 35 |
+
Content-Type: application/json
|
| 36 |
+
|
| 37 |
+
{
|
| 38 |
+
"text": "Your input text here"
|
| 39 |
+
}
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
**Response:**
|
| 43 |
+
```json
|
| 44 |
+
{
|
| 45 |
+
"prediction": "Processed: Your input text here",
|
| 46 |
+
"confidence": 0.95,
|
| 47 |
+
"input_text": "Your input text here"
|
| 48 |
+
}
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Environment Variables
|
| 52 |
+
|
| 53 |
+
| Variable | Description | Required |
|
| 54 |
+
|----------|-------------|----------|
|
| 55 |
+
| `JWT_SECRET` | Secret key for JWT token verification | Yes |
|
| 56 |
+
| `ALLOWED_ORIGINS` | Comma-separated list of allowed CORS origins | No |
|
| 57 |
+
|
| 58 |
+
## Local Development
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
# Install dependencies
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
|
| 64 |
+
# Set environment variables
|
| 65 |
+
export JWT_SECRET=your_secret_key
|
| 66 |
+
|
| 67 |
+
# Run the server
|
| 68 |
+
uvicorn main:app --reload --port 8001
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
## Deployment
|
| 72 |
+
|
| 73 |
+
This service is designed to run on Hugging Face Spaces with Docker SDK.
|
| 74 |
+
|
| 75 |
+
1. Create a new Space with Docker SDK
|
| 76 |
+
2. Set the `JWT_SECRET` secret in Space settings
|
| 77 |
+
3. Push this code to the Space repository
|
auth.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from fastapi import Depends, HTTPException, status
|
| 3 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 4 |
+
from jose import jwt, JWTError
|
| 5 |
+
|
| 6 |
+
security = HTTPBearer()
|
| 7 |
+
|
| 8 |
+
def get_jwt_secret() -> str:
|
| 9 |
+
"""Get JWT secret from environment variable."""
|
| 10 |
+
secret = os.getenv("JWT_SECRET")
|
| 11 |
+
if not secret:
|
| 12 |
+
raise HTTPException(
|
| 13 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 14 |
+
detail="JWT_SECRET not configured"
|
| 15 |
+
)
|
| 16 |
+
return secret
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Verify Bearer token signature.
|
| 22 |
+
Returns the decoded token payload if valid.
|
| 23 |
+
Raises HTTPException if invalid.
|
| 24 |
+
"""
|
| 25 |
+
token = credentials.credentials
|
| 26 |
+
jwt_secret = get_jwt_secret()
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# Decode and verify the JWT token
|
| 30 |
+
payload = jwt.decode(
|
| 31 |
+
token,
|
| 32 |
+
jwt_secret,
|
| 33 |
+
algorithms=["HS256"],
|
| 34 |
+
options={"verify_aud": False} # Clerk tokens may not have standard audience
|
| 35 |
+
)
|
| 36 |
+
return payload
|
| 37 |
+
except JWTError as e:
|
| 38 |
+
raise HTTPException(
|
| 39 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 40 |
+
detail=f"Invalid token: {str(e)}",
|
| 41 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_current_user(token_payload: dict = Depends(verify_token)) -> dict:
|
| 46 |
+
"""
|
| 47 |
+
Extract user information from verified token.
|
| 48 |
+
"""
|
| 49 |
+
user_id = token_payload.get("sub")
|
| 50 |
+
if not user_id:
|
| 51 |
+
raise HTTPException(
|
| 52 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 53 |
+
detail="Invalid token: missing user ID",
|
| 54 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
return {
|
| 58 |
+
"user_id": user_id,
|
| 59 |
+
"email": token_payload.get("email"),
|
| 60 |
+
"claims": token_payload
|
| 61 |
+
}
|
main.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from fastapi import FastAPI, Depends, HTTPException
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from auth import get_current_user
|
| 7 |
+
|
| 8 |
+
app = FastAPI(
|
| 9 |
+
title="Text Prediction API",
|
| 10 |
+
description="AI-powered text prediction service",
|
| 11 |
+
version="0.1.0"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
# Configure CORS - use environment variable for allowed origins in production
|
| 15 |
+
allowed_origins = os.getenv("ALLOWED_ORIGINS", "").split(",") if os.getenv("ALLOWED_ORIGINS") else ["*"]
|
| 16 |
+
|
| 17 |
+
app.add_middleware(
|
| 18 |
+
CORSMiddleware,
|
| 19 |
+
allow_origins=allowed_origins,
|
| 20 |
+
allow_credentials=False,
|
| 21 |
+
allow_methods=["*"],
|
| 22 |
+
allow_headers=["*"],
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TextPredictRequest(BaseModel):
|
| 27 |
+
text: str
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class TextPredictResponse(BaseModel):
|
| 31 |
+
prediction: str
|
| 32 |
+
confidence: float
|
| 33 |
+
input_text: str
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@app.get("/")
|
| 37 |
+
async def root():
|
| 38 |
+
"""Health check endpoint."""
|
| 39 |
+
return {"status": "healthy", "service": "text-api"}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@app.get("/health")
|
| 43 |
+
async def health():
|
| 44 |
+
"""Health check endpoint."""
|
| 45 |
+
return {"status": "healthy"}
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@app.post("/predict", response_model=TextPredictResponse)
|
| 49 |
+
async def predict(
|
| 50 |
+
request: TextPredictRequest,
|
| 51 |
+
current_user: dict = Depends(get_current_user)
|
| 52 |
+
):
|
| 53 |
+
"""
|
| 54 |
+
Protected endpoint for text prediction.
|
| 55 |
+
Requires valid Bearer token.
|
| 56 |
+
"""
|
| 57 |
+
# Placeholder prediction logic
|
| 58 |
+
# In a real application, this would call an ML model
|
| 59 |
+
text = request.text
|
| 60 |
+
|
| 61 |
+
# Simple mock prediction
|
| 62 |
+
prediction = f"Processed: {text[:50]}..." if len(text) > 50 else f"Processed: {text}"
|
| 63 |
+
confidence = 0.95
|
| 64 |
+
|
| 65 |
+
return TextPredictResponse(
|
| 66 |
+
prediction=prediction,
|
| 67 |
+
confidence=confidence,
|
| 68 |
+
input_text=text
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
import uvicorn
|
| 74 |
+
uvicorn.run(app, host="0.0.0.0", port=8001)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.109.0
|
| 2 |
+
uvicorn>=0.27.0
|
| 3 |
+
python-jose[cryptography]>=3.3.0
|
| 4 |
+
httpx>=0.26.0
|
| 5 |
+
pydantic>=2.5.0
|