pixelperfect / app /main.py
earncoding's picture
Create app/main.py
39a6384 verified
Raw History Blame Contribute Delete
4.83 kB
import os
import json
from fastapi import FastAPI, UploadFile, File, HTTPException, Security, Depends, Request
from fastapi.security.api_key import APIKeyHeader
from starlette.status import HTTP_403_FORBIDDEN, HTTP_429_TOO_MANY_REQUESTS
from fastapi.responses import StreamingResponse
from io import BytesIO
from PIL import Image
# Import the background removal function
from rembg import remove
# --- Rate Limiting ---
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
# Initialize the rate limiter
# This allows 5 requests per minute per IP address. Adjust as needed.
limiter = Limiter(key_func=get_remote_address, default_limits=["5/minute"])
# --- API Initialization ---
app = FastAPI(
title="Background Remover API",
description="A simple API to remove the background from images, hosted on Hugging Face Spaces.",
version="1.0.0"
)
# Add the rate limiter to the app
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# --- Security: API Key Authentication ---
# Define the API Key header
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
# Load API keys from Hugging Face Secrets (as a JSON string)
# For local testing, you can create a .env file and use python-dotenv
# Example JSON string: '{"your-internal-key-for-website": "internal", "a-paid-user-key": "paid"}'
API_KEYS_JSON = os.getenv("API_KEYS_JSON")
if not API_KEYS_JSON:
# Fallback for local development if the secret is not set
print("WARNING: API_KEYS_JSON secret not found. Using dummy keys for local development.")
API_KEYS = {
"my-secret-website-key": "internal",
"paid-user-123-key": "paid"
}
else:
API_KEYS = json.loads(API_KEYS_JSON)
async def get_api_key(api_key: str = Security(api_key_header)):
"""
Dependency function to validate the API key from the X-API-Key header.
"""
if not api_key or api_key not in API_KEYS:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
)
return {"key": api_key, "type": API_KEYS[api_key]}
# --- API Endpoints ---
@app.get("/", tags=["General"])
def read_root():
""" A simple health check endpoint. """
return {"status": "ok", "message": "Welcome to the Background Remover API!"}
@app.post("/remove-background/",
tags=["Image Processing"],
summary="Remove background from an image",
response_description="A PNG image with a transparent background.",
responses={
200: {
"content": {"image/png": {}},
"description": "Successful Response",
},
400: {"description": "Invalid file type or file too large"},
401: {"description": "Missing or invalid API Key"},
429: {"description": "Rate limit exceeded"},
}
)
@limiter.limit("5/minute") # Apply the rate limit to this specific endpoint
async def remove_background(
request: Request, # Required for the rate limiter
file: UploadFile = File(..., description="Image file to process (JPG, PNG)"),
api_key: dict = Depends(get_api_key) # This enforces API key authentication
):
"""
This endpoint receives an image, validates it, removes the background,
and returns the resulting image with a transparent background.
- **Authentication**: Requires a valid `X-API-Key` in the header.
- **Rate Limiting**: Limited to 5 requests per minute per IP.
- **File Validation**: Accepts only `image/jpeg` and `image/png` up to 10MB.
"""
# --- Input Validation ---
# 1. Check file type
if file.content_type not in ["image/jpeg", "image/png"]:
raise HTTPException(status_code=400, detail="Invalid file type. Please upload a JPG or PNG.")
# 2. Check file size (e.g., 10 MB limit)
# The `await file.read()` will load the whole file into memory.
# We read it once and then use the bytes.
contents = await file.read()
if len(contents) > 10 * 1024 * 1024: # 10 MB
raise HTTPException(status_code=400, detail="File too large. Maximum size is 10 MB.")
# Log the request (you can expand this for analytics)
print(f"Processing request for API key type: {api_key['type']}")
# --- Core Logic: Background Removal ---
try:
# Use rembg to remove the background
output_bytes = remove(contents)
# Return the result as a streaming response (efficient for binary data)
return StreamingResponse(BytesIO(output_bytes), media_type="image/png")
except Exception as e:
# If anything goes wrong during processing
print(f"Error processing image: {e}")
raise HTTPException(status_code=500, detail="Internal server error while processing the image.")