Spaces:
Sleeping
Sleeping
Kethan Dosapati commited on
Commit Β·
caa70b1
1
Parent(s): 4d002be
Add initial implementation of Yantrabodha Article API with FastAPI and Supabase integration
Browse files- Dockerfile +18 -0
- api/.env +3 -0
- api/database.py +9 -0
- api/endpoints/__init__.py +5 -0
- api/endpoints/match.py +30 -0
- api/endpoints/post.py +21 -0
- api/main.py +22 -0
- api/models.py +20 -0
- api/requirements.txt +4 -0
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces β FastAPI app (Yantrabodha API)
|
| 2 |
+
# Runs from repo root; app code lives in api/
|
| 3 |
+
|
| 4 |
+
FROM python:3.12-slim
|
| 5 |
+
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
USER user
|
| 8 |
+
ENV HOME=/home/user PATH=/home/user/.local/bin:$PATH
|
| 9 |
+
WORKDIR $HOME/app
|
| 10 |
+
|
| 11 |
+
COPY --chown=user api/requirements.txt .
|
| 12 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 13 |
+
|
| 14 |
+
COPY --chown=user api/ .
|
| 15 |
+
|
| 16 |
+
# HF Spaces expose port 7860 by default
|
| 17 |
+
EXPOSE 7860
|
| 18 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
api/.env
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Supabase β Project Settings β API
|
| 2 |
+
SUPABASE_URL=https://kdtbcdsugebjjxtufsiy.supabase.co
|
| 3 |
+
SUPABASE_SERVICE_KEY=sb_publishable__iDn02CEPpNdGShqBinK0Q_7tgniGQf
|
api/database.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Supabase client singleton."""
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
from supabase import create_client, Client
|
| 5 |
+
|
| 6 |
+
SUPABASE_URL = os.environ["SUPABASE_URL"]
|
| 7 |
+
SUPABASE_SERVICE_KEY = os.environ["SUPABASE_SERVICE_KEY"]
|
| 8 |
+
|
| 9 |
+
supabase: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
|
api/endpoints/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""API endpoint routers."""
|
| 2 |
+
from .match import router as match_router
|
| 3 |
+
from .post import router as post_router
|
| 4 |
+
|
| 5 |
+
__all__ = ["post_router", "match_router"]
|
api/endpoints/match.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MATCH endpoint β full-text search across articles."""
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from fastapi import APIRouter, Query
|
| 5 |
+
|
| 6 |
+
from database import supabase
|
| 7 |
+
|
| 8 |
+
router = APIRouter(tags=["match"])
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@router.get("")
|
| 12 |
+
def match_articles(
|
| 13 |
+
q: str = Query(..., min_length=1, description="Search query"),
|
| 14 |
+
language: Optional[str] = Query(None, description="Filter by language"),
|
| 15 |
+
type: Optional[str] = Query(None, description="Filter by type"),
|
| 16 |
+
limit: int = Query(5, ge=1, le=50, description="Max results"),
|
| 17 |
+
):
|
| 18 |
+
"""Full-text search across article titles and bodies."""
|
| 19 |
+
query = (
|
| 20 |
+
supabase.table("articles")
|
| 21 |
+
.select("id, title, body, language, tags, type, contributing_agent, confidence, created_at")
|
| 22 |
+
.text_search("fts", q, config="english")
|
| 23 |
+
.limit(limit)
|
| 24 |
+
)
|
| 25 |
+
if language:
|
| 26 |
+
query = query.eq("language", language)
|
| 27 |
+
if type:
|
| 28 |
+
query = query.eq("type", type)
|
| 29 |
+
result = query.execute()
|
| 30 |
+
return result.data
|
api/endpoints/post.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""POST endpoint β submit a new article."""
|
| 2 |
+
from fastapi import APIRouter, HTTPException
|
| 3 |
+
|
| 4 |
+
from database import supabase
|
| 5 |
+
from models import ArticleIn, ArticleOut
|
| 6 |
+
|
| 7 |
+
router = APIRouter(tags=["post"])
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@router.post("", response_model=ArticleOut, status_code=201)
|
| 11 |
+
def create_article(article: ArticleIn):
|
| 12 |
+
"""Insert a new article into the knowledge base."""
|
| 13 |
+
result = (
|
| 14 |
+
supabase.table("articles")
|
| 15 |
+
.insert(article.model_dump())
|
| 16 |
+
.execute()
|
| 17 |
+
)
|
| 18 |
+
if not result.data:
|
| 19 |
+
raise HTTPException(status_code=500, detail="Failed to insert article")
|
| 20 |
+
row = result.data[0]
|
| 21 |
+
return ArticleOut(id=row["id"], title=row["title"], created_at=row["created_at"])
|
api/main.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Yantrabodha Article API
|
| 3 |
+
=======================
|
| 4 |
+
FastAPI app backed by Supabase (Postgres). Endpoints:
|
| 5 |
+
- POST /post β submit a new article
|
| 6 |
+
- GET /match β full-text search across articles
|
| 7 |
+
|
| 8 |
+
Environment variables:
|
| 9 |
+
SUPABASE_URL β from Supabase Project Settings β API
|
| 10 |
+
SUPABASE_SERVICE_KEY β service role key (bypasses RLS)
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from fastapi import FastAPI
|
| 14 |
+
|
| 15 |
+
from database import supabase # noqa: F401 β ensure DB is loaded
|
| 16 |
+
from endpoints.match import router as match_router
|
| 17 |
+
from endpoints.post import router as post_router
|
| 18 |
+
|
| 19 |
+
app = FastAPI(title="Yantrabodha API", version="1.0.0")
|
| 20 |
+
|
| 21 |
+
app.include_router(post_router, prefix="/post")
|
| 22 |
+
app.include_router(match_router, prefix="/match")
|
api/models.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared Pydantic models for the API."""
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class ArticleIn(BaseModel):
|
| 8 |
+
title: str
|
| 9 |
+
body: str
|
| 10 |
+
language: str = "general"
|
| 11 |
+
tags: list[str] = []
|
| 12 |
+
type: str = "error"
|
| 13 |
+
contributing_agent: Optional[str] = None
|
| 14 |
+
confidence: str = "medium"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ArticleOut(BaseModel):
|
| 18 |
+
id: str
|
| 19 |
+
title: str
|
| 20 |
+
created_at: str
|
api/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
supabase
|
| 4 |
+
pydantic
|