Spaces:
Running
Running
| """ | |
| Request validation utilities. | |
| """ | |
| import os | |
| from typing import Tuple, Optional | |
| from werkzeug.utils import secure_filename | |
| from PIL import Image | |
| def validate_image_file(file, max_size: int, allowed_extensions: tuple) -> Tuple[bool, Optional[str], Optional[str]]: | |
| """ | |
| Validate uploaded image file. | |
| Args: | |
| file: FileStorage object from Flask | |
| max_size: Maximum file size in bytes | |
| allowed_extensions: Tuple of allowed extensions (e.g., (".jpg", ".png")) | |
| Returns: | |
| Tuple of (is_valid, error_message, sanitized_filename) | |
| If valid: (True, None, filename) | |
| If invalid: (False, error_message, None) | |
| """ | |
| if not file or not file.filename: | |
| return False, "No file provided", None | |
| # Check filename | |
| filename = secure_filename(file.filename) | |
| if not filename: | |
| return False, "Invalid filename", None | |
| # Check extension | |
| ext = os.path.splitext(filename)[1].lower() | |
| if ext not in allowed_extensions: | |
| return False, f"Unsupported file type. Allowed: {', '.join(allowed_extensions)}", None | |
| # Check file size (if available) | |
| try: | |
| file.seek(0, os.SEEK_END) | |
| file_size = file.tell() | |
| file.seek(0) # Reset to beginning | |
| if file_size > max_size: | |
| max_mb = max_size / (1024 * 1024) | |
| return False, f"File too large. Maximum size: {max_mb:.1f}MB", None | |
| if file_size == 0: | |
| return False, "File is empty", None | |
| except Exception: | |
| # If we can't check size, continue (will be caught by MAX_CONTENT_LENGTH) | |
| pass | |
| # Validate it's actually an image by trying to open it | |
| try: | |
| file.seek(0) | |
| img = Image.open(file) | |
| img.verify() # Verify it's a valid image | |
| file.seek(0) # Reset after verification | |
| except Exception as e: | |
| return False, f"Invalid image file: {str(e)}", None | |
| return True, None, filename | |
| def validate_pagination_params(limit: Optional[str], offset: Optional[str]) -> Tuple[int, int, Optional[str]]: | |
| """ | |
| Validate pagination parameters. | |
| Returns: | |
| Tuple of (limit, offset, error_message) | |
| """ | |
| try: | |
| limit_val = int(limit) if limit else 20 | |
| limit_val = max(1, min(200, limit_val)) | |
| except ValueError: | |
| return 20, 0, "Invalid limit parameter. Must be an integer." | |
| try: | |
| offset_val = int(offset) if offset else 0 | |
| offset_val = max(0, offset_val) | |
| except ValueError: | |
| return limit_val, 0, "Invalid offset parameter. Must be an integer." | |
| return limit_val, offset_val, None | |
| def validate_confidence_range(min_conf: Optional[str], max_conf: Optional[str]) -> Tuple[Optional[float], Optional[float], Optional[str]]: | |
| """ | |
| Validate confidence range parameters. | |
| Returns: | |
| Tuple of (min_confidence, max_confidence, error_message) | |
| """ | |
| min_val = None | |
| max_val = None | |
| if min_conf: | |
| try: | |
| min_val = float(min_conf) | |
| if not 0 <= min_val <= 1: | |
| return None, None, "min_confidence must be between 0 and 1" | |
| except ValueError: | |
| return None, None, "Invalid min_confidence parameter. Must be a number." | |
| if max_conf: | |
| try: | |
| max_val = float(max_conf) | |
| if not 0 <= max_val <= 1: | |
| return None, None, "max_confidence must be between 0 and 1" | |
| except ValueError: | |
| return None, None, "Invalid max_confidence parameter. Must be a number." | |
| if min_val is not None and max_val is not None and min_val > max_val: | |
| return None, None, "min_confidence cannot be greater than max_confidence" | |
| return min_val, max_val, None | |