Initial commit: Document Translation API with Excel, Word, PowerPoint support

This commit is contained in:
2025-11-30 10:48:58 +01:00
commit 793d94c93e
41 changed files with 3617 additions and 0 deletions

20
utils/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""Utils package initialization"""
from .file_handler import FileHandler, file_handler
from .exceptions import (
TranslationError,
UnsupportedFileTypeError,
FileSizeLimitExceededError,
LanguageNotSupportedError,
DocumentProcessingError,
handle_translation_error
)
__all__ = [
'FileHandler', 'file_handler',
'TranslationError',
'UnsupportedFileTypeError',
'FileSizeLimitExceededError',
'LanguageNotSupportedError',
'DocumentProcessingError',
'handle_translation_error'
]

51
utils/exceptions.py Normal file
View File

@@ -0,0 +1,51 @@
"""
Custom exceptions for the Document Translation API
"""
from fastapi import HTTPException
class TranslationError(Exception):
"""Base exception for translation errors"""
pass
class UnsupportedFileTypeError(TranslationError):
"""Raised when an unsupported file type is provided"""
pass
class FileSizeLimitExceededError(TranslationError):
"""Raised when a file exceeds the size limit"""
pass
class LanguageNotSupportedError(TranslationError):
"""Raised when a language code is not supported"""
pass
class DocumentProcessingError(TranslationError):
"""Raised when there's an error processing the document"""
pass
def handle_translation_error(error: Exception) -> HTTPException:
"""
Convert translation errors to HTTP exceptions
Args:
error: Exception that occurred
Returns:
HTTPException with appropriate status code and message
"""
if isinstance(error, UnsupportedFileTypeError):
return HTTPException(status_code=400, detail=str(error))
elif isinstance(error, FileSizeLimitExceededError):
return HTTPException(status_code=413, detail=str(error))
elif isinstance(error, LanguageNotSupportedError):
return HTTPException(status_code=400, detail=str(error))
elif isinstance(error, DocumentProcessingError):
return HTTPException(status_code=500, detail=str(error))
else:
return HTTPException(status_code=500, detail="An unexpected error occurred during translation")

142
utils/file_handler.py Normal file
View File

@@ -0,0 +1,142 @@
"""
Utility functions for file handling and validation
"""
import os
import uuid
from pathlib import Path
from typing import Optional
from fastapi import UploadFile, HTTPException
from config import config
class FileHandler:
"""Handles file operations for the translation API"""
@staticmethod
def validate_file_extension(filename: str) -> str:
"""
Validate that the file extension is supported
Args:
filename: Name of the file
Returns:
File extension (lowercase, with dot)
Raises:
HTTPException: If file extension is not supported
"""
file_extension = Path(filename).suffix.lower()
if file_extension not in config.SUPPORTED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"Unsupported file type. Supported types: {', '.join(config.SUPPORTED_EXTENSIONS)}"
)
return file_extension
@staticmethod
def validate_file_size(file: UploadFile) -> None:
"""
Validate that the file size is within limits
Args:
file: Uploaded file
Raises:
HTTPException: If file is too large
"""
# Get file size
file.file.seek(0, 2) # Move to end of file
file_size = file.file.tell() # Get position (file size)
file.file.seek(0) # Reset to beginning
if file_size > config.MAX_FILE_SIZE_BYTES:
raise HTTPException(
status_code=400,
detail=f"File too large. Maximum size: {config.MAX_FILE_SIZE_MB}MB"
)
@staticmethod
async def save_upload_file(file: UploadFile, destination: Path) -> Path:
"""
Save an uploaded file to disk
Args:
file: Uploaded file
destination: Path to save the file
Returns:
Path to the saved file
"""
destination.parent.mkdir(parents=True, exist_ok=True)
with open(destination, "wb") as buffer:
content = await file.read()
buffer.write(content)
return destination
@staticmethod
def generate_unique_filename(original_filename: str, prefix: str = "") -> str:
"""
Generate a unique filename to avoid collisions
Args:
original_filename: Original filename
prefix: Optional prefix for the filename
Returns:
Unique filename
"""
file_path = Path(original_filename)
unique_id = str(uuid.uuid4())[:8]
if prefix:
return f"{prefix}_{unique_id}_{file_path.stem}{file_path.suffix}"
else:
return f"{unique_id}_{file_path.stem}{file_path.suffix}"
@staticmethod
def cleanup_file(file_path: Path) -> None:
"""
Delete a file if it exists
Args:
file_path: Path to the file to delete
"""
try:
if file_path.exists():
file_path.unlink()
except Exception as e:
print(f"Error deleting file {file_path}: {e}")
@staticmethod
def get_file_info(file_path: Path) -> dict:
"""
Get information about a file
Args:
file_path: Path to the file
Returns:
Dictionary with file information
"""
if not file_path.exists():
return {}
stat = file_path.stat()
return {
"filename": file_path.name,
"size_bytes": stat.st_size,
"size_mb": round(stat.st_size / (1024 * 1024), 2),
"extension": file_path.suffix,
"created": stat.st_ctime,
"modified": stat.st_mtime
}
# Global file handler instance
file_handler = FileHandler()