|
""" |
|
API v1 endpoints |
|
""" |
|
|
|
import time |
|
from fastapi import APIRouter, HTTPException, Request |
|
from slowapi import Limiter |
|
from slowapi.util import get_remote_address |
|
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST |
|
from fastapi.responses import Response |
|
|
|
from ...models.schemas import ( |
|
TranslationRequest, |
|
TranslationResponse, |
|
HealthResponse, |
|
LanguagesResponse, |
|
LanguageStatsResponse, |
|
LanguageInfo |
|
) |
|
from ...services.translation import ( |
|
translate_with_detection, |
|
translate_with_source, |
|
models_loaded |
|
) |
|
from ...services.languages import ( |
|
get_all_languages, |
|
get_languages_by_region, |
|
get_language_info, |
|
is_language_supported, |
|
get_popular_languages, |
|
get_african_languages, |
|
search_languages, |
|
get_language_statistics |
|
) |
|
from ...core.config import settings |
|
from ...core.logging import get_logger |
|
from ...core.metrics import TRANSLATION_COUNT, CHARACTER_COUNT, ERROR_COUNT |
|
from ...utils.helpers import get_nairobi_time |
|
|
|
logger = get_logger() |
|
limiter = Limiter(key_func=get_remote_address) |
|
|
|
|
|
app_start_time = time.time() |
|
|
|
|
|
router = APIRouter() |
|
|
|
|
|
@router.get( |
|
"/", |
|
response_model=HealthResponse, |
|
tags=["Health & Monitoring"], |
|
summary="Basic Health Check", |
|
description="Quick health check endpoint that returns basic API status information." |
|
) |
|
async def root(): |
|
""" |
|
## Basic Health Check |
|
|
|
Returns essential API status information including: |
|
- β
API operational status |
|
- π¦ Model loading status |
|
- β±οΈ System uptime |
|
- π·οΈ API version |
|
|
|
**Use this endpoint for:** |
|
- Load balancer health checks |
|
- Basic monitoring |
|
- API availability verification |
|
""" |
|
uptime = time.time() - app_start_time |
|
full_date, _ = get_nairobi_time() |
|
|
|
return HealthResponse( |
|
status="healthy" if models_loaded() else "degraded", |
|
version=settings.app_version, |
|
models_loaded=models_loaded(), |
|
uptime=uptime, |
|
timestamp=full_date |
|
) |
|
|
|
|
|
@router.get( |
|
"/health", |
|
response_model=HealthResponse, |
|
tags=["Health & Monitoring"], |
|
summary="Detailed Health Check", |
|
description="Comprehensive health check with detailed system status for monitoring systems.", |
|
responses={ |
|
200: {"description": "System is healthy"}, |
|
503: {"description": "System is unhealthy - models not loaded"} |
|
} |
|
) |
|
async def health_check(): |
|
""" |
|
## Detailed Health Check |
|
|
|
Comprehensive health check endpoint designed for monitoring systems like: |
|
- π Prometheus/Grafana |
|
- π¨ Alerting systems |
|
- π APM tools |
|
- π₯ Health monitoring dashboards |
|
|
|
**Returns detailed information about:** |
|
- System health status |
|
- Model loading status |
|
- API uptime |
|
- Timestamp information |
|
|
|
**HTTP Status Codes:** |
|
- `200`: All systems operational |
|
- `503`: Service unavailable (models not loaded) |
|
""" |
|
uptime = time.time() - app_start_time |
|
full_date, _ = get_nairobi_time() |
|
|
|
|
|
models_healthy = models_loaded() |
|
|
|
return HealthResponse( |
|
status="healthy" if models_healthy else "unhealthy", |
|
version=settings.app_version, |
|
models_loaded=models_healthy, |
|
uptime=uptime, |
|
timestamp=full_date |
|
) |
|
|
|
|
|
@router.get( |
|
"/metrics", |
|
tags=["Health & Monitoring"], |
|
summary="Prometheus Metrics", |
|
description="Prometheus-compatible metrics endpoint for monitoring and alerting.", |
|
responses={ |
|
200: {"description": "Metrics in Prometheus format", "content": {"text/plain": {}}}, |
|
404: {"description": "Metrics disabled"} |
|
} |
|
) |
|
async def get_metrics(): |
|
""" |
|
## Prometheus Metrics |
|
|
|
Returns metrics in Prometheus format for monitoring and alerting. |
|
|
|
**Available Metrics:** |
|
- π `sema_requests_total` - Total API requests by endpoint and status |
|
- β±οΈ `sema_request_duration_seconds` - Request duration histogram |
|
- π `sema_translations_total` - Translation count by language pair |
|
- π `sema_characters_translated_total` - Total characters translated |
|
- β `sema_errors_total` - Error count by type |
|
|
|
**Integration Examples:** |
|
```yaml |
|
# Prometheus scrape config |
|
scrape_configs: |
|
- job_name: 'sema-api' |
|
static_configs: |
|
- targets: ['your-api-url:port'] |
|
metrics_path: '/metrics' |
|
``` |
|
""" |
|
if not settings.enable_metrics: |
|
raise HTTPException(status_code=404, detail="Metrics disabled") |
|
|
|
return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST) |
|
|
|
|
|
@router.post( |
|
"/translate", |
|
response_model=TranslationResponse, |
|
tags=["Translation"], |
|
summary="Translate Text", |
|
description="Translate text between 200+ languages with automatic language detection.", |
|
responses={ |
|
200: {"description": "Translation successful"}, |
|
400: {"description": "Invalid request - empty text or invalid language code"}, |
|
413: {"description": "Text too long - exceeds character limit"}, |
|
429: {"description": "Rate limit exceeded"}, |
|
500: {"description": "Translation service error"} |
|
} |
|
) |
|
@limiter.limit(f"{settings.max_requests_per_minute}/minute") |
|
async def translate_endpoint( |
|
request: TranslationRequest, |
|
http_request: Request |
|
): |
|
""" |
|
## π Translate Text |
|
|
|
Translate text between 200+ languages using state-of-the-art neural machine translation. |
|
|
|
### β¨ Features |
|
- **Automatic Language Detection**: Leave `source_language` empty for auto-detection |
|
- **200+ Languages**: Full FLORES-200 language support |
|
- **High Performance**: Optimized CTranslate2 inference engine |
|
- **Usage Tracking**: Character count and request metrics |
|
- **Request Tracking**: Unique request IDs for debugging |
|
|
|
### π Limits & Constraints |
|
- **Rate Limit**: 60 requests per minute per IP address |
|
- **Character Limit**: Maximum 5000 characters per request |
|
- **Language Codes**: Must use FLORES-200 format (e.g., `eng_Latn`, `swh_Latn`) |
|
|
|
### π Language Code Examples |
|
| Language | Code | Example | |
|
|----------|------|---------| |
|
| English | `eng_Latn` | "Hello world" | |
|
| Swahili | `swh_Latn` | "Habari ya dunia" | |
|
| French | `fra_Latn` | "Bonjour le monde" | |
|
| Kikuyu | `kik_Latn` | "WΔ© mwega?" | |
|
| Spanish | `spa_Latn` | "Hola mundo" | |
|
|
|
### π Usage Examples |
|
|
|
**Auto-detect source language:** |
|
```json |
|
{ |
|
"text": "Habari ya asubuhi", |
|
"target_language": "eng_Latn" |
|
} |
|
``` |
|
|
|
**Specify source language:** |
|
```json |
|
{ |
|
"text": "Good morning", |
|
"source_language": "eng_Latn", |
|
"target_language": "swh_Latn" |
|
} |
|
``` |
|
|
|
### π Response Information |
|
The response includes: |
|
- Translated text |
|
- Detected/provided source language |
|
- Character count for usage tracking |
|
- Inference time for performance monitoring |
|
- Unique request ID for debugging |
|
- Timestamp in Nairobi timezone |
|
""" |
|
request_id = http_request.state.request_id |
|
|
|
|
|
if len(request.text) > settings.max_text_length: |
|
raise HTTPException( |
|
status_code=413, |
|
detail=f"Text too long. Maximum {settings.max_text_length} characters allowed." |
|
) |
|
|
|
full_date, _ = get_nairobi_time() |
|
character_count = len(request.text) |
|
|
|
|
|
logger.info( |
|
"translation_started", |
|
request_id=request_id, |
|
source_language=request.source_language, |
|
target_language=request.target_language, |
|
character_count=character_count |
|
) |
|
|
|
try: |
|
if request.source_language: |
|
|
|
translated_text, inference_time = translate_with_source( |
|
request.text, |
|
request.source_language, |
|
request.target_language |
|
) |
|
source_lang = request.source_language |
|
else: |
|
|
|
source_lang, translated_text, inference_time = translate_with_detection( |
|
request.text, |
|
request.target_language |
|
) |
|
|
|
|
|
TRANSLATION_COUNT.labels( |
|
source_lang=source_lang, |
|
target_lang=request.target_language |
|
).inc() |
|
|
|
CHARACTER_COUNT.inc(character_count) |
|
|
|
|
|
logger.info( |
|
"translation_completed", |
|
request_id=request_id, |
|
source_language=source_lang, |
|
target_language=request.target_language, |
|
character_count=character_count, |
|
inference_time=inference_time |
|
) |
|
|
|
return TranslationResponse( |
|
translated_text=translated_text, |
|
source_language=source_lang, |
|
target_language=request.target_language, |
|
inference_time=inference_time, |
|
character_count=character_count, |
|
timestamp=full_date, |
|
request_id=request_id |
|
) |
|
|
|
except Exception as e: |
|
|
|
logger.error( |
|
"translation_failed", |
|
request_id=request_id, |
|
error=str(e), |
|
error_type=type(e).__name__, |
|
source_language=request.source_language, |
|
target_language=request.target_language |
|
) |
|
|
|
|
|
ERROR_COUNT.labels(error_type="translation_error").inc() |
|
|
|
raise HTTPException( |
|
status_code=500, |
|
detail="Translation service temporarily unavailable. Please try again later." |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages", |
|
response_model=LanguagesResponse, |
|
tags=["Languages"], |
|
summary="Get All Supported Languages", |
|
description="Retrieve a complete list of all supported languages with metadata." |
|
) |
|
async def get_languages(): |
|
""" |
|
## π Get All Supported Languages |
|
|
|
Returns a comprehensive list of all 200+ supported languages with detailed metadata. |
|
|
|
### π Response Information |
|
Each language includes: |
|
- **English Name**: Standard English name |
|
- **Native Name**: Name in the language's native script |
|
- **Region**: Geographic region (Africa, Europe, Asia, etc.) |
|
- **Script**: Writing system (Latin, Arabic, Cyrillic, etc.) |
|
|
|
### π― Use Cases |
|
- **Frontend Language Selectors**: Populate dropdown menus |
|
- **API Integration**: Validate language codes before translation |
|
- **Documentation**: Generate language support documentation |
|
- **Analytics**: Track language usage patterns |
|
|
|
### π Language Coverage |
|
- **African Languages**: 25+ languages including Swahili, Hausa, Yoruba |
|
- **European Languages**: 40+ languages including major EU languages |
|
- **Asian Languages**: 80+ languages including Chinese, Japanese, Hindi |
|
- **Middle Eastern**: 15+ languages including Arabic, Hebrew, Persian |
|
- **Americas**: 30+ languages including indigenous languages |
|
""" |
|
languages = get_all_languages() |
|
return LanguagesResponse( |
|
languages={code: LanguageInfo(**info) for code, info in languages.items()}, |
|
total_count=len(languages) |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages/popular", |
|
response_model=LanguagesResponse, |
|
tags=["Languages"], |
|
summary="Get Popular Languages", |
|
description="Get the most commonly used languages for quick access." |
|
) |
|
async def get_popular_languages_endpoint(): |
|
""" |
|
## β Get Popular Languages |
|
|
|
Returns the most commonly requested languages for quick access and better UX. |
|
|
|
### π₯ Included Languages |
|
- **Global**: English, Spanish, French, German, Portuguese, Russian |
|
- **Asian**: Chinese, Japanese, Korean, Hindi, Arabic |
|
- **African**: Swahili, Hausa, Yoruba, Amharic, Somali, Kikuyu |
|
|
|
### π‘ Perfect For |
|
- **Quick Selection**: Show popular options first |
|
- **Mobile Apps**: Reduced list for smaller screens |
|
- **Default Options**: Pre-populate common language pairs |
|
""" |
|
languages = get_popular_languages() |
|
return LanguagesResponse( |
|
languages={code: LanguageInfo(**info) for code, info in languages.items()}, |
|
total_count=len(languages) |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages/african", |
|
response_model=LanguagesResponse, |
|
tags=["Languages"], |
|
summary="Get African Languages", |
|
description="Get all supported African languages." |
|
) |
|
async def get_african_languages_endpoint(): |
|
""" |
|
## π Get African Languages |
|
|
|
Returns all supported African languages - our specialty! |
|
|
|
### π― Featured African Languages |
|
- **East Africa**: Swahili, Kikuyu, Luo, Amharic, Somali, Tigrinya |
|
- **West Africa**: Hausa, Yoruba, Igbo, Wolof, Lingala |
|
- **Southern Africa**: Zulu, Xhosa, Afrikaans, Tswana, Sotho, Shona |
|
- **Central Africa**: Lingala, Umbundu |
|
|
|
### β¨ Special Features |
|
- High-quality translations for African languages |
|
- Cultural context preservation |
|
- Support for various scripts (Latin, Ethiopic) |
|
""" |
|
languages = get_african_languages() |
|
return LanguagesResponse( |
|
languages={code: LanguageInfo(**info) for code, info in languages.items()}, |
|
total_count=len(languages) |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages/region/{region}", |
|
response_model=LanguagesResponse, |
|
tags=["Languages"], |
|
summary="Get Languages by Region", |
|
description="Get all languages from a specific geographic region." |
|
) |
|
async def get_languages_by_region_endpoint(region: str): |
|
""" |
|
## πΊοΈ Get Languages by Region |
|
|
|
Filter languages by geographic region for targeted language support. |
|
|
|
### π Available Regions |
|
- **Africa**: African languages (Swahili, Hausa, Yoruba, etc.) |
|
- **Europe**: European languages (English, French, German, etc.) |
|
- **Asia**: Asian languages (Chinese, Japanese, Hindi, etc.) |
|
- **Middle East**: Middle Eastern languages (Arabic, Hebrew, Persian, etc.) |
|
- **Americas**: Languages from the Americas |
|
|
|
### π Usage Examples |
|
``` |
|
GET /languages/region/Africa |
|
GET /languages/region/Europe |
|
GET /languages/region/Asia |
|
``` |
|
""" |
|
languages = get_languages_by_region(region) |
|
if not languages: |
|
raise HTTPException( |
|
status_code=404, |
|
detail=f"No languages found for region: {region}. Available regions: Africa, Europe, Asia, Middle East, Americas" |
|
) |
|
|
|
return LanguagesResponse( |
|
languages={code: LanguageInfo(**info) for code, info in languages.items()}, |
|
total_count=len(languages) |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages/search", |
|
response_model=LanguagesResponse, |
|
tags=["Languages"], |
|
summary="Search Languages", |
|
description="Search for languages by name, native name, or language code." |
|
) |
|
async def search_languages_endpoint(q: str): |
|
""" |
|
## π Search Languages |
|
|
|
Search for languages using flexible text matching. |
|
|
|
### π― Search Capabilities |
|
- **English Names**: "Swahili", "French", "Chinese" |
|
- **Native Names**: "Kiswahili", "FranΓ§ais", "δΈζ" |
|
- **Language Codes**: "swh_Latn", "fra_Latn", "cmn_Hans" |
|
- **Partial Matches**: "Span" matches "Spanish" |
|
|
|
### π‘ Perfect For |
|
- **Autocomplete**: Real-time language search |
|
- **User Input**: Find languages by any name variation |
|
- **Validation**: Check if a language exists |
|
|
|
### π Query Examples |
|
``` |
|
GET /languages/search?q=Swahili |
|
GET /languages/search?q=δΈζ |
|
GET /languages/search?q=ara |
|
``` |
|
""" |
|
if not q or len(q.strip()) < 2: |
|
raise HTTPException( |
|
status_code=400, |
|
detail="Search query must be at least 2 characters long" |
|
) |
|
|
|
languages = search_languages(q.strip()) |
|
return LanguagesResponse( |
|
languages={code: LanguageInfo(**info) for code, info in languages.items()}, |
|
total_count=len(languages) |
|
) |
|
|
|
|
|
@router.get( |
|
"/languages/stats", |
|
response_model=LanguageStatsResponse, |
|
tags=["Languages"], |
|
summary="Get Language Statistics", |
|
description="Get comprehensive statistics about supported languages." |
|
) |
|
async def get_language_stats(): |
|
""" |
|
## π Language Statistics |
|
|
|
Get comprehensive statistics about our language support coverage. |
|
|
|
### π Statistics Include |
|
- **Total Languages**: Complete count of supported languages |
|
- **Regional Distribution**: Languages per geographic region |
|
- **Script Coverage**: Number of writing systems supported |
|
- **Detailed Breakdown**: Languages by region with counts |
|
|
|
### π― Use Cases |
|
- **Analytics Dashboards**: Display language coverage metrics |
|
- **Marketing Materials**: Showcase translation capabilities |
|
- **API Documentation**: Provide coverage statistics |
|
- **Business Intelligence**: Track language support growth |
|
""" |
|
stats = get_language_statistics() |
|
return LanguageStatsResponse(**stats) |
|
|
|
|
|
@router.get( |
|
"/languages/{language_code}", |
|
response_model=LanguageInfo, |
|
tags=["Languages"], |
|
summary="Get Language Information", |
|
description="Get detailed information about a specific language." |
|
) |
|
async def get_language_info_endpoint(language_code: str): |
|
""" |
|
## π Get Language Information |
|
|
|
Get detailed metadata about a specific language using its FLORES-200 code. |
|
|
|
### π Information Provided |
|
- **English Name**: Standard English name |
|
- **Native Name**: Name in native script |
|
- **Region**: Geographic region |
|
- **Script**: Writing system used |
|
|
|
### π― Use Cases |
|
- **Language Validation**: Check if a code is supported |
|
- **UI Display**: Show language names in interfaces |
|
- **Documentation**: Generate language-specific docs |
|
|
|
### π Example Codes |
|
``` |
|
GET /languages/swh_Latn # Swahili |
|
GET /languages/eng_Latn # English |
|
GET /languages/cmn_Hans # Chinese (Simplified) |
|
``` |
|
""" |
|
language_info = get_language_info(language_code) |
|
if not language_info: |
|
raise HTTPException( |
|
status_code=404, |
|
detail=f"Language code '{language_code}' not supported. Use /languages to see all supported languages." |
|
) |
|
|
|
return LanguageInfo(**language_info) |
|
|