smartbiz_ai / backend /utils /validators.py
Muhammad7865253's picture
SmartBiz AI — clean release (guardrails, UI polish, repo cleanup)
8ce1429
Raw
History Blame Contribute Delete
3.18 kB
"""
Validation utilities for file upload pipeline.
"""
import os
from fastapi import UploadFile, HTTPException
STRUCTURED_EXTENSIONS = {"csv", "xlsx"}
UNSTRUCTURED_EXTENSIONS = {"pdf", "txt", "doc", "docx", "ppt", "pptx", "jpg", "jpeg", "png"}
ALLOWED_EXTENSIONS = STRUCTURED_EXTENSIONS | UNSTRUCTURED_EXTENSIONS
MIME_TYPE_MAP = {
"csv": ["text/csv", "application/csv", "text/plain"],
"xlsx": ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
"pdf": ["application/pdf"],
"txt": ["text/plain"],
"doc": ["application/msword"],
"docx": ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
"ppt": ["application/vnd.ms-powerpoint"],
"pptx": ["application/vnd.openxmlformats-officedocument.presentationml.presentation"],
"jpg": ["image/jpeg"],
"jpeg": ["image/jpeg"],
"png": ["image/png"],
}
def get_file_extension(filename: str) -> str:
"""Extract and normalize file extension."""
if not filename or "." not in filename:
return ""
return filename.rsplit(".", 1)[-1].lower()
def validate_file_extension(filename: str) -> str:
"""
Validate that file has an allowed extension.
Returns the extension if valid, raises HTTPException otherwise.
"""
ext = get_file_extension(filename)
if not ext:
raise HTTPException(
status_code=400,
detail="File must have a valid extension."
)
if ext not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"Unsupported file type: .{ext}. Allowed types: {', '.join(sorted(ALLOWED_EXTENSIONS))}"
)
return ext
def validate_file_size(file: UploadFile) -> None:
"""
Validate that file is not empty.
Raises HTTPException if file size is 0.
"""
file.file.seek(0, os.SEEK_END)
size = file.file.tell()
file.file.seek(0)
if size == 0:
raise HTTPException(
status_code=400,
detail="File is empty. Please upload a valid file."
)
def validate_mime_type(file: UploadFile, extension: str) -> None:
"""
Light MIME type sanity check.
Raises HTTPException if MIME type doesn't match extension.
"""
content_type = file.content_type or ""
expected_mimes = MIME_TYPE_MAP.get(extension, [])
if not expected_mimes:
return
if content_type and content_type not in expected_mimes:
if content_type != "application/octet-stream":
raise HTTPException(
status_code=400,
detail=f"MIME type mismatch. Expected one of {expected_mimes} for .{extension}, got {content_type}"
)
async def validate_upload_file(file: UploadFile) -> str:
"""
Run all validations on uploaded file.
Returns the validated file extension.
"""
if not file or not file.filename:
raise HTTPException(
status_code=400,
detail="No file provided or file has no name."
)
extension = validate_file_extension(file.filename)
validate_file_size(file)
validate_mime_type(file, extension)
return extension