Spaces:
Sleeping
Sleeping
..
Browse files- Dockerfile +10 -10
- db_pool.py +23 -0
- logger.py +16 -0
- model_loader.py +16 -0
- requirements.txt +2 -1
- routes.py +43 -0
- server.py +29 -0
Dockerfile
CHANGED
|
@@ -1,20 +1,20 @@
|
|
| 1 |
-
#
|
| 2 |
FROM python:3.9
|
| 3 |
|
| 4 |
-
# Create
|
| 5 |
RUN useradd -m -u 1000 user
|
| 6 |
USER user
|
| 7 |
ENV PATH="/home/user/.local/bin:$PATH"
|
| 8 |
|
| 9 |
-
# Set
|
| 10 |
WORKDIR /app
|
| 11 |
|
| 12 |
-
# Install
|
| 13 |
-
COPY --chown=user
|
| 14 |
-
RUN pip install --no-cache-dir
|
| 15 |
|
| 16 |
-
# Copy
|
| 17 |
-
COPY --chown=user .
|
| 18 |
|
| 19 |
-
# Run
|
| 20 |
-
CMD ["uvicorn", "
|
|
|
|
| 1 |
+
# Use official Python image
|
| 2 |
FROM python:3.9
|
| 3 |
|
| 4 |
+
# Create non-root user
|
| 5 |
RUN useradd -m -u 1000 user
|
| 6 |
USER user
|
| 7 |
ENV PATH="/home/user/.local/bin:$PATH"
|
| 8 |
|
| 9 |
+
# Set working dir
|
| 10 |
WORKDIR /app
|
| 11 |
|
| 12 |
+
# Install requirements
|
| 13 |
+
COPY --chown=user requirements.txt requirements.txt
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
|
| 16 |
+
# Copy project files
|
| 17 |
+
COPY --chown=user . .
|
| 18 |
|
| 19 |
+
# Run FastAPI app
|
| 20 |
+
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860"]
|
db_pool.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import psycopg2
|
| 3 |
+
from psycopg2 import pool
|
| 4 |
+
|
| 5 |
+
DB_URL = os.getenv("DATABASE_URL")
|
| 6 |
+
_pool = None
|
| 7 |
+
|
| 8 |
+
def init_db_pool():
|
| 9 |
+
global _pool
|
| 10 |
+
if DB_URL:
|
| 11 |
+
_pool = psycopg2.pool.SimpleConnectionPool(1, 5, dsn=DB_URL)
|
| 12 |
+
print("β
DB pool initialized")
|
| 13 |
+
|
| 14 |
+
def close_db_pool():
|
| 15 |
+
global _pool
|
| 16 |
+
if _pool:
|
| 17 |
+
_pool.closeall()
|
| 18 |
+
print("β
DB pool closed")
|
| 19 |
+
|
| 20 |
+
def get_conn():
|
| 21 |
+
if not _pool:
|
| 22 |
+
raise RuntimeError("DB pool not initialized")
|
| 23 |
+
return _pool.getconn()
|
logger.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import traceback
|
| 2 |
+
import db_pool
|
| 3 |
+
|
| 4 |
+
def sync_log_to_db(texts, results):
|
| 5 |
+
values = [(t, r["blur"], r["score"]) for t, r in zip(texts, results)]
|
| 6 |
+
try:
|
| 7 |
+
conn = db_pool.get_conn()
|
| 8 |
+
with conn.cursor() as cur:
|
| 9 |
+
cur.executemany("INSERT INTO cases(text, blur, score) VALUES(%s, %s, %s)", values)
|
| 10 |
+
conn.commit()
|
| 11 |
+
print(f"β
Logged {len(values)} rows")
|
| 12 |
+
return True
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print("β Logging failed:", e)
|
| 15 |
+
traceback.print_exc()
|
| 16 |
+
return False
|
model_loader.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
| 3 |
+
|
| 4 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 5 |
+
tokenizer = None
|
| 6 |
+
model = None
|
| 7 |
+
|
| 8 |
+
def load_model():
|
| 9 |
+
global tokenizer, model
|
| 10 |
+
|
| 11 |
+
model_id = "medoxz543/hate-speech"
|
| 12 |
+
print(f"π Loading model from Hugging Face Hub: {model_id}")
|
| 13 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 14 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_id).to(DEVICE)
|
| 15 |
+
model.eval()
|
| 16 |
+
print("β
Model ready")
|
requirements.txt
CHANGED
|
@@ -2,4 +2,5 @@ fastapi
|
|
| 2 |
uvicorn
|
| 3 |
transformers
|
| 4 |
torch
|
| 5 |
-
emoji
|
|
|
|
|
|
| 2 |
uvicorn
|
| 3 |
transformers
|
| 4 |
torch
|
| 5 |
+
emoji
|
| 6 |
+
psycopg2-binary
|
routes.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
import torch
|
| 5 |
+
import model_loader
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
if os.getenv("DATABASE_URL"):
|
| 9 |
+
from logger import sync_log_to_db
|
| 10 |
+
|
| 11 |
+
router = APIRouter()
|
| 12 |
+
DEVICE = model_loader.DEVICE
|
| 13 |
+
|
| 14 |
+
def predict(texts: list[str]) -> list[float]:
|
| 15 |
+
enc = model_loader.tokenizer(texts, padding=True, truncation=True, max_length=70, return_tensors="pt")
|
| 16 |
+
enc = {k: v.to(DEVICE) for k, v in enc.items()}
|
| 17 |
+
with torch.no_grad():
|
| 18 |
+
probs = model_loader.model(**enc).logits.softmax(-1)[:, 1]
|
| 19 |
+
return probs.cpu().tolist()
|
| 20 |
+
|
| 21 |
+
class TextRequest(BaseModel):
|
| 22 |
+
texts: list[str]
|
| 23 |
+
|
| 24 |
+
@router.get("/")
|
| 25 |
+
def root():
|
| 26 |
+
return {"status": "π’ HF Space is running"}
|
| 27 |
+
|
| 28 |
+
@router.post("/check-text")
|
| 29 |
+
def check_text(payload: TextRequest):
|
| 30 |
+
if not payload.texts:
|
| 31 |
+
raise HTTPException(400, "No texts provided")
|
| 32 |
+
scores = predict(payload.texts)
|
| 33 |
+
results = [{"blur": s >= 0.5, "score": round(s, 4)} for s in scores]
|
| 34 |
+
return {"timestamp": datetime.utcnow().isoformat(timespec="seconds"), "results": results}
|
| 35 |
+
|
| 36 |
+
@router.post("/log-results")
|
| 37 |
+
def log_results(payload: TextRequest):
|
| 38 |
+
if not os.getenv("DATABASE_URL"):
|
| 39 |
+
raise HTTPException(503, "Database is not configured")
|
| 40 |
+
scores = predict(payload.texts)
|
| 41 |
+
results = [{"blur": s >= 0.5, "score": round(s, 4)} for s in scores]
|
| 42 |
+
success = sync_log_to_db(payload.texts, results)
|
| 43 |
+
return {"logged": success}
|
server.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from fastapi import FastAPI
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
from routes import router
|
| 5 |
+
from model_loader import load_model
|
| 6 |
+
import db_pool
|
| 7 |
+
|
| 8 |
+
app = FastAPI()
|
| 9 |
+
|
| 10 |
+
app.add_middleware(
|
| 11 |
+
CORSMiddleware,
|
| 12 |
+
allow_origins=["*"],
|
| 13 |
+
allow_credentials=True,
|
| 14 |
+
allow_methods=["*"],
|
| 15 |
+
allow_headers=["*"],
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
app.include_router(router)
|
| 19 |
+
|
| 20 |
+
@app.on_event("startup")
|
| 21 |
+
def startup():
|
| 22 |
+
print("π Starting Hugging Face Space API")
|
| 23 |
+
load_model()
|
| 24 |
+
if os.getenv("DATABASE_URL"):
|
| 25 |
+
db_pool.init_db_pool()
|
| 26 |
+
|
| 27 |
+
@app.on_event("shutdown")
|
| 28 |
+
def shutdown():
|
| 29 |
+
db_pool.close_db_pool()
|