| """ |
| Datasets API Router - Explore and select datasets from HuggingFace Hub |
| """ |
|
|
| from fastapi import APIRouter, HTTPException, Query |
| from pydantic import BaseModel, Field |
| from typing import Optional, Dict, List, Any |
| from datetime import datetime |
| import logging |
| import httpx |
|
|
| from app.config import settings |
|
|
| logger = logging.getLogger(__name__) |
| router = APIRouter(prefix="/api/datasets", tags=["Datasets"]) |
|
|
|
|
| class DatasetInfo(BaseModel): |
| """Dataset information.""" |
| id: str |
| author: str |
| dataset_name: str |
| description: Optional[str] |
| downloads: int = 0 |
| likes: int = 0 |
| private: bool = False |
| tags: List[str] = [] |
| created_at: Optional[datetime] |
| last_modified: Optional[datetime] |
| |
|
|
| class DatasetSearchResult(BaseModel): |
| """Search result dataset.""" |
| id: str |
| author: str |
| dataset_name: str |
| downloads: int |
| likes: int |
| |
|
|
| class DatasetRecommendation(BaseModel): |
| """Dataset recommendation for a task.""" |
| dataset_id: str |
| reason: str |
| size: str |
| format: str |
|
|
|
|
| class DatasetSchema(BaseModel): |
| """Dataset schema information.""" |
| dataset_id: str |
| splits: List[str] |
| columns: List[str] |
| num_rows: Dict[str, int] |
| features: Dict[str, str] |
|
|
|
|
| HF_API_URL = "https://huggingface.co/api" |
|
|
|
|
| @router.get("/search", response_model=List[DatasetSearchResult]) |
| async def search_datasets( |
| query: str = Query(..., min_length=1, description="Search query"), |
| task: Optional[str] = Query(None, description="Filter by task"), |
| language: Optional[str] = Query(None, description="Filter by language"), |
| author: Optional[str] = Query(None, description="Filter by author"), |
| limit: int = Query(20, ge=1, le=100) |
| ): |
| """Search HuggingFace Hub for datasets.""" |
| async with httpx.AsyncClient() as client: |
| params = { |
| "search": query, |
| "limit": limit, |
| "full": "false" |
| } |
| |
| if task: |
| params["task_categories"] = task |
| if language: |
| params["language"] = language |
| if author: |
| params["author"] = author |
| |
| try: |
| response = await client.get( |
| f"{HF_API_URL}/datasets", |
| params=params, |
| timeout=30.0 |
| ) |
| response.raise_for_status() |
| data = response.json() |
| |
| return [ |
| DatasetSearchResult( |
| id=d.get("id", ""), |
| author=d.get("author", ""), |
| dataset_name=d.get("id", "").split("/")[-1] if "/" in d.get("id", "") else d.get("id", ""), |
| downloads=d.get("downloads", 0), |
| likes=d.get("likes", 0) |
| ) |
| for d in data |
| ] |
| except Exception as e: |
| logger.error(f"Dataset search failed: {e}") |
| raise HTTPException(status_code=500, detail=f"Search failed: {str(e)}") |
|
|
|
|
| @router.get("/info/{dataset_id:path}", response_model=DatasetInfo) |
| async def get_dataset_info(dataset_id: str): |
| """Get detailed information about a dataset.""" |
| async with httpx.AsyncClient() as client: |
| try: |
| response = await client.get( |
| f"{HF_API_URL}/datasets/{dataset_id}", |
| timeout=30.0 |
| ) |
| response.raise_for_status() |
| data = response.json() |
| |
| return DatasetInfo( |
| id=data.get("id", ""), |
| author=data.get("author", ""), |
| dataset_name=data.get("id", "").split("/")[-1] if "/" in data.get("id", "") else data.get("id", ""), |
| description=data.get("cardData", {}).get("description", ""), |
| downloads=data.get("downloads", 0), |
| likes=data.get("likes", 0), |
| private=data.get("private", False), |
| tags=data.get("tags", []), |
| created_at=datetime.fromisoformat(data["createdAt"].replace("Z", "+00:00")) if data.get("createdAt") else None, |
| last_modified=datetime.fromisoformat(data["lastModified"].replace("Z", "+00:00")) if data.get("lastModified") else None |
| ) |
| except httpx.HTTPStatusError as e: |
| if e.response.status_code == 404: |
| raise HTTPException(status_code=404, detail="Dataset not found") |
| raise HTTPException(status_code=e.response.status_code, detail=f"API error: {e}") |
| except Exception as e: |
| logger.error(f"Failed to get dataset info: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/schema/{dataset_id:path}") |
| async def get_dataset_schema(dataset_id: str): |
| """Get dataset schema including splits, columns, and row counts.""" |
| try: |
| from datasets import get_dataset_infos |
| |
| infos = get_dataset_infos(dataset_id) |
| if not infos: |
| raise HTTPException(status_code=404, detail="Dataset not found or inaccessible") |
| |
| info = list(infos.values())[0] |
| |
| splits = list(info.splits.keys()) if info.splits else [] |
| columns = list(info.features.keys()) if info.features else [] |
| num_rows = {k: v.num_examples for k, v in info.splits.items()} if info.splits else {} |
| features = {k: str(v) for k, v in info.features.items()} if info.features else {} |
| |
| return { |
| "dataset_id": dataset_id, |
| "splits": splits, |
| "columns": columns, |
| "num_rows": num_rows, |
| "features": features |
| } |
| except Exception as e: |
| logger.error(f"Failed to get dataset schema: {e}") |
| return { |
| "dataset_id": dataset_id, |
| "splits": [], |
| "columns": [], |
| "num_rows": {}, |
| "features": {}, |
| "error": str(e) |
| } |
|
|
|
|
| @router.get("/recommend/{task_type}", response_model=List[DatasetRecommendation]) |
| async def get_dataset_recommendations(task_type: str): |
| """Get dataset recommendations for a specific task.""" |
| recommendations = { |
| "causal-lm": [ |
| DatasetRecommendation(dataset_id="tiiuae/falcon-refinedweb", reason="Large-scale web data for language modeling", size="350GB+", format="Raw text"), |
| DatasetRecommendation(dataset_id="OpenAssistant/oasst1", reason="Human conversations for instruction tuning", size="~10M tokens", format="Conversations"), |
| DatasetRecommendation(dataset_id="HuggingFaceH4/ultrachat_200k", reason="High-quality dialogues", size="200k conversations", format="Conversations"), |
| DatasetRecommendation(dataset_id="teknium/openhermes", reason="Diverse instruction data", size="1M+ samples", format="Instructions"), |
| ], |
| "seq2seq": [ |
| DatasetRecommendation(dataset_id="cfilt/iitb-english-hindi", reason="Translation dataset", size="~1.5M pairs", format="Parallel text"), |
| DatasetRecommendation(dataset_id="wmt/wmt14-en-de", reason="WMT translation benchmark", size="~4.5M pairs", format="Parallel text"), |
| ], |
| "summarization": [ |
| DatasetRecommendation(dataset_id="cnn_dailymail", reason="News summarization standard", size="~300k articles", format="Article-Summary"), |
| DatasetRecommendation(dataset_id="xsum", reason="BBC article summaries", size="~22k articles", format="Article-Summary"), |
| DatasetRecommendation(dataset_id="samsum", reason="Dialogue summarization", size="~16k dialogues", format="Dialogue-Summary"), |
| ], |
| "token-classification": [ |
| DatasetRecommendation(dataset_id="conll2003", reason="Classic NER benchmark", size="~22k sentences", format="IOB tags"), |
| DatasetRecommendation(dataset_id="ontonotes", reason="Multi-domain NER", size="~80k sentences", format="IOB tags"), |
| DatasetRecommendation(dataset_id="wikiann", reason="Cross-lingual NER", size="Varies by language", format="IOB tags"), |
| ], |
| "sequence-classification": [ |
| DatasetRecommendation(dataset_id="imdb", reason="Movie reviews for sentiment", size="50k reviews", format="Text-Label"), |
| DatasetRecommendation(dataset_id="yelp_polarity", reason="Yelp reviews", size="560k reviews", format="Text-Label"), |
| DatasetRecommendation(dataset_id="ag_news", reason="News topic classification", size="~127k articles", format="Text-Label"), |
| ], |
| "question-answering": [ |
| DatasetRecommendation(dataset_id="squad", reason="Reading comprehension QA", size="~100k questions", format="Context-QA"), |
| DatasetRecommendation(dataset_id="squad_v2", reason="SQuAD with unanswerable", size="~150k questions", format="Context-QA"), |
| DatasetRecommendation(dataset_id="natural_questions", reason="Real user questions", size="~300k questions", format="Document-QA"), |
| ], |
| "translation": [ |
| DatasetRecommendation(dataset_id="wmt/wmt14-en-de", reason="English-German", size="~4.5M pairs", format="Parallel"), |
| DatasetRecommendation(dataset_id="wmt/wmt19-ru-en", reason="Russian-English", size="~4M pairs", format="Parallel"), |
| ], |
| "masked-lm": [ |
| DatasetRecommendation(dataset_id="bookcorpus", reason="Books dataset for MLM", size="~1B words", format="Raw text"), |
| DatasetRecommendation(dataset_id="wikitext", reason="Wikipedia text", size="Varies", format="Raw text"), |
| ], |
| } |
| |
| result = recommendations.get(task_type, []) |
| |
| if not result: |
| result = recommendations.get("causal-lm", []) |
| |
| return result[:5] |
|
|
|
|
| @router.get("/popular") |
| async def get_popular_datasets( |
| task: Optional[str] = None, |
| limit: int = Query(10, ge=1, le=50) |
| ): |
| """Get most popular datasets, optionally filtered by task.""" |
| async with httpx.AsyncClient() as client: |
| params = { |
| "sort": "downloads", |
| "direction": "-1", |
| "limit": limit |
| } |
| |
| if task: |
| params["task_categories"] = task |
| |
| try: |
| response = await client.get( |
| f"{HF_API_URL}/datasets", |
| params=params, |
| timeout=30.0 |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to get popular datasets: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/preview/{dataset_id:path}") |
| async def preview_dataset( |
| dataset_id: str, |
| split: str = Query("train", description="Dataset split to preview"), |
| rows: int = Query(10, ge=1, le=100) |
| ): |
| """Preview first N rows of a dataset.""" |
| try: |
| from datasets import load_dataset |
| |
| ds = load_dataset(dataset_id, split=split, streaming=True) |
| data = [] |
| for i, row in enumerate(ds): |
| if i >= rows: |
| break |
| data.append(row) |
| |
| return { |
| "dataset_id": dataset_id, |
| "split": split, |
| "rows": data, |
| "columns": list(data[0].keys()) if data else [] |
| } |
| except Exception as e: |
| logger.error(f"Failed to preview dataset: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/configs/{dataset_id:path}") |
| async def get_dataset_configs(dataset_id: str): |
| """Get available configurations for a dataset.""" |
| try: |
| from datasets import get_dataset_config_names |
| |
| configs = get_dataset_config_names(dataset_id) |
| return { |
| "dataset_id": dataset_id, |
| "configs": configs |
| } |
| except Exception as e: |
| logger.error(f"Failed to get configs: {e}") |
| return { |
| "dataset_id": dataset_id, |
| "configs": [], |
| "error": str(e) |
| } |
|
|
|
|
| @router.get("/splits/{dataset_id:path}") |
| async def get_dataset_splits( |
| dataset_id: str, |
| config: Optional[str] = None |
| ): |
| """Get available splits for a dataset.""" |
| try: |
| from datasets import get_dataset_split_names |
| |
| splits = get_dataset_split_names(dataset_id, config_name=config) |
| return { |
| "dataset_id": dataset_id, |
| "config": config, |
| "splits": splits |
| } |
| except Exception as e: |
| logger.error(f"Failed to get splits: {e}") |
| return { |
| "dataset_id": dataset_id, |
| "config": config, |
| "splits": [], |
| "error": str(e) |
| } |