ddgs / api /main.py
motsobelal's picture
Upload 39 files
8628def verified
"""FastAPI application for DDGS API."""
import logging
from typing import Any
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from ddgs import DDGS
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create FastAPI app
app = FastAPI(
title="DDGS API",
description="A FastAPI wrapper for the DDGS (Dux Distributed Global Search) library",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Pydantic models for request/response
class TextSearchRequest(BaseModel):
"""Request model for search operations."""
query: str = Field(..., description="Search query")
region: str = Field("us-en", description="Region for search (e.g., us-en, uk-en, ru-ru)")
safesearch: str = Field("moderate", description="Safe search setting (on, moderate, off)")
timelimit: str | None = Field(None, description="Time limit (d, w, m, y) or custom date range")
max_results: int | None = Field(10, description="Maximum number of results to return")
page: int = Field(1, description="Page number of results")
backend: str = Field("auto", description="Search backend (auto, or specific engine)")
class ImagesSearchRequest(BaseModel):
"""Request model for image search operations."""
query: str = Field(..., description="Image search query")
region: str = Field("us-en", description="Region for search (e.g., us-en, uk-en, ru-ru)")
safesearch: str = Field("moderate", description="Safe search setting (on, moderate, off)")
timelimit: str | None = Field(None, description="Time limit (d, w, m, y) or custom date range")
max_results: int | None = Field(10, description="Maximum number of results to return")
page: int = Field(1, description="Page number of results")
backend: str = Field("auto", description="Search backend (auto, or specific engine)")
size: str | None = Field(None, description="Image size (Small, Medium, Large, Wallpaper)")
color: str | None = Field(
None,
description="Image color (Monochrome, Red, Orange, Yellow, Green, Blue, Purple, Pink, Brown, Black, Gray, Teal, White)", # noqa: E501
)
type_image: str | None = Field(None, description="Image type (photo, clipart, gif, transparent, line)")
layout: str | None = Field(None, description="Image layout (Square, Tall, Wide)")
license_image: str | None = Field(
None, description="Image license (any, Public, Share, ShareCommercially, Modify, ModifyCommercially)"
)
class NewsSearchRequest(BaseModel):
"""Request model for search operations."""
query: str = Field(..., description="Search query")
region: str = Field("us-en", description="Region for search (e.g., us-en, uk-en, ru-ru)")
safesearch: str = Field("moderate", description="Safe search setting (on, moderate, off)")
timelimit: str | None = Field(None, description="Time limit (d, w, m, y) or custom date range")
max_results: int | None = Field(10, description="Maximum number of results to return")
page: int = Field(1, description="Page number of results")
backend: str = Field("auto", description="Search backend (auto, or specific engine)")
class VideosSearchRequest(BaseModel):
"""Request model for video search operations."""
query: str = Field(..., description="Video search query")
region: str = Field("us-en", description="Region for search (e.g., us-en, uk-en, ru-ru)")
safesearch: str = Field("moderate", description="Safe search setting (on, moderate, off)")
timelimit: str | None = Field(None, description="Time limit (d, w, m) or custom date range")
max_results: int | None = Field(10, description="Maximum number of results to return")
page: int = Field(1, description="Page number of results")
backend: str = Field("auto", description="Search backend (auto, or specific engine)")
resolution: str | None = Field(None, description="Video resolution (high, standard)")
duration: str | None = Field(None, description="Video duration (short, medium, long)")
license_videos: str | None = Field(None, description="Video license (creativeCommon, youtube)")
class BooksSearchRequest(BaseModel):
"""Request model for book search operations."""
query: str = Field(..., description="Books search query")
max_results: int | None = Field(10, description="Maximum number of results to return")
page: int = Field(1, description="Page number of results")
backend: str = Field("auto", description="Search backend (auto, or specific engine)")
class SearchResponse(BaseModel):
"""Response model for search operations."""
results: list[dict[str, Any]]
class HealthResponse(BaseModel):
"""Response model for health check."""
status: str
version: str
service: str
@app.get("/", response_model=HealthResponse)
async def root() -> HealthResponse:
"""Root endpoint with basic service information."""
return HealthResponse(status="healthy", version="1.0.0", service="DDGS API")
@app.get("/health", response_model=HealthResponse)
async def health_check() -> HealthResponse:
"""Health check endpoint."""
return HealthResponse(status="healthy", version="1.0.0", service="DDGS API")
@app.post("/search/text", response_model=SearchResponse)
async def search_text(request: TextSearchRequest) -> SearchResponse:
"""Perform a text search."""
try:
results = DDGS().text(
query=request.query,
region=request.region,
safesearch=request.safesearch,
timelimit=request.timelimit,
max_results=request.max_results,
page=request.page,
backend=request.backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in text search: %s", e)
raise HTTPException(status_code=500, detail=f"Search failed: {e!s}") from e
@app.get("/search/text", response_model=SearchResponse)
async def search_text_get(
query: str,
region: str = "us-en",
safesearch: str = "moderate",
timelimit: str | None = None,
max_results: int = 10,
page: int = 1,
backend: str = "auto",
) -> SearchResponse:
"""Perform a text search via GET request."""
try:
results = DDGS().text(
query=query,
region=region,
safesearch=safesearch,
timelimit=timelimit,
max_results=max_results,
page=page,
backend=backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in text search (GET): %s", e)
raise HTTPException(status_code=500, detail=f"Search failed: {e!s}") from e
@app.post("/search/images", response_model=SearchResponse)
async def search_images(request: ImagesSearchRequest) -> SearchResponse:
"""Perform an image search."""
try:
results = DDGS().images(
query=request.query,
region=request.region,
safesearch=request.safesearch,
timelimit=request.timelimit,
max_results=request.max_results,
page=request.page,
backend=request.backend,
size=request.size,
color=request.color,
type_image=request.type_image,
layout=request.layout,
license_image=request.license_image,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in image search: %s", e)
raise HTTPException(status_code=500, detail=f"Image search failed: {e!s}") from e
@app.get("/search/images", response_model=SearchResponse)
async def search_images_get(
query: str,
region: str = "us-en",
safesearch: str = "moderate",
timelimit: str | None = None,
max_results: int = 10,
page: int = 1,
backend: str = "auto",
size: str | None = None,
color: str | None = None,
type_image: str | None = None,
layout: str | None = None,
license_image: str | None = None,
) -> SearchResponse:
"""Perform an image search via GET request."""
try:
results = DDGS().images(
query=query,
region=region,
safesearch=safesearch,
timelimit=timelimit,
max_results=max_results,
page=page,
backend=backend,
size=size,
color=color,
type_image=type_image,
layout=layout,
license_image=license_image,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in image search (GET): %s", e)
raise HTTPException(status_code=500, detail=f"Image search failed: {e!s}") from e
@app.post("/search/news", response_model=SearchResponse)
async def search_news(request: NewsSearchRequest) -> SearchResponse:
"""Perform a news search."""
try:
results = DDGS().news(
query=request.query,
region=request.region,
safesearch=request.safesearch,
timelimit=request.timelimit,
max_results=request.max_results,
page=request.page,
backend=request.backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in news search: %s", e)
raise HTTPException(status_code=500, detail=f"News search failed: {e!s}") from e
@app.get("/search/news", response_model=SearchResponse)
async def search_news_get(
query: str,
region: str = "us-en",
safesearch: str = "moderate",
timelimit: str | None = None,
max_results: int = 10,
page: int = 1,
backend: str = "auto",
) -> SearchResponse:
"""Perform a news search via GET request."""
try:
results = DDGS().news(
query=query,
region=region,
safesearch=safesearch,
timelimit=timelimit,
max_results=max_results,
page=page,
backend=backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in news search (GET): %s", e)
raise HTTPException(status_code=500, detail=f"News search failed: {e!s}") from e
@app.post("/search/videos", response_model=SearchResponse)
async def search_videos(request: VideosSearchRequest) -> SearchResponse:
"""Perform a video search."""
try:
results = DDGS().videos(
query=request.query,
region=request.region,
safesearch=request.safesearch,
timelimit=request.timelimit,
max_results=request.max_results,
page=request.page,
backend=request.backend,
resolution=request.resolution,
duration=request.duration,
license_videos=request.license_videos,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in video search: %s", e)
raise HTTPException(status_code=500, detail=f"Video search failed: {e!s}") from e
@app.get("/search/videos", response_model=SearchResponse)
async def search_videos_get(
query: str,
region: str = "us-en",
safesearch: str = "moderate",
timelimit: str | None = None,
max_results: int = 10,
page: int = 1,
backend: str = "auto",
resolution: str | None = None,
duration: str | None = None,
license_videos: str | None = None,
) -> SearchResponse:
"""Perform a video search via GET request."""
try:
results = DDGS().videos(
query=query,
region=region,
safesearch=safesearch,
timelimit=timelimit,
max_results=max_results,
page=page,
backend=backend,
resolution=resolution,
duration=duration,
license_videos=license_videos,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in video search (GET): %s", e)
raise HTTPException(status_code=500, detail=f"Video search failed: {e!s}") from e
@app.post("/search/books", response_model=SearchResponse)
async def search_books(request: BooksSearchRequest) -> SearchResponse:
"""Perform a book search."""
try:
results = DDGS().books(
query=request.query,
max_results=request.max_results,
page=request.page,
backend=request.backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in book search: %s", e)
raise HTTPException(status_code=500, detail=f"Book search failed: {e!s}") from e
@app.get("/search/books", response_model=SearchResponse)
async def search_books_get(
query: str,
max_results: int = 10,
page: int = 1,
backend: str = "auto",
) -> SearchResponse:
"""Perform a book search via GET request."""
try:
results = DDGS().books(
query=query,
max_results=max_results,
page=page,
backend=backend,
)
return SearchResponse(results=results)
except Exception as e:
logger.warning("Error in book search (GET): %s", e)
raise HTTPException(status_code=500, detail=f"Book search failed: {e!s}") from e
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000) # noqa: S104