# validation.py ## Purpose Input validation utilities for Nano Banana Streamlit. Validates user inputs, parameters, and system state to ensure data integrity and provide clear error messages. ## Responsibilities - Validate generation parameters (temperature, aspect ratio, backend) - Validate text inputs (prompts, character names) - Validate images (format, dimensions, file size) - Validate complete generation requests - Check backend availability - Provide clear, user-friendly error messages ## Dependencies ### Imports - `pathlib.Path` - File path operations - `PIL.Image` - Image validation - `config.settings.Settings` - Validation constraints - `utils.logging_utils.get_logger` - Logging validation errors ### Used By - All UI pages - Validate user inputs before submission - All services - Validate parameters before generation - Backend clients - Validate configuration - `models/generation_request.py` - Validate request objects ## Public Interface All validation functions return `Tuple[bool, Optional[str]]`: - `(True, None)` if valid - `(False, error_message)` if invalid ### Parameter Validation #### `validate_temperature(temperature: float) -> Tuple[bool, Optional[str]]` Validates temperature is in valid range [0.0, 1.0]. **Example:** ```python valid, error = validate_temperature(0.5) if not valid: st.error(error) ``` #### `validate_aspect_ratio(aspect_ratio: str) -> Tuple[bool, Optional[str]]` Validates aspect ratio is in Settings.ASPECT_RATIOS. Accepts both display names ("16:9 (1344x768)") and values ("16:9"). **Example:** ```python valid, error = validate_aspect_ratio("16:9") if valid: # Use aspect ratio pass ``` #### `validate_backend(backend: str) -> Tuple[bool, Optional[str]]` Validates backend is in Settings.AVAILABLE_BACKENDS. **Example:** ```python valid, error = validate_backend("Gemini API (Cloud)") ``` #### `validate_prompt(prompt: str, min_length: int = 1, max_length: int = 5000) -> Tuple[bool, Optional[str]]` Validates text prompt length. **Parameters:** - `prompt`: Text to validate - `min_length`: Minimum length (default: 1) - `max_length`: Maximum length (default: 5000) **Example:** ```python valid, error = validate_prompt(user_input, min_length=10) if not valid: st.warning(error) ``` #### `validate_character_name(name: str) -> Tuple[bool, Optional[str]]` Validates character name (1-100 characters). **Example:** ```python valid, error = validate_character_name(character_name) if not valid: st.error(error) return ``` ### Image Validation #### `validate_image(image: Image.Image) -> Tuple[bool, Optional[str]]` Validates PIL Image object. **Checks:** - Is valid Image instance - Dimensions > 0 - Dimensions < 8192x8192 (reasonable limit) - Mode is supported (RGB, RGBA, L, P) **Example:** ```python valid, error = validate_image(uploaded_image) if not valid: st.error(f"Invalid image: {error}") ``` #### `validate_image_file(file_path: Path) -> Tuple[bool, Optional[str]]` Validates image file on disk. **Checks:** - File exists - Is a file (not directory) - Has valid extension (.png, .jpg, .jpeg, .webp, .bmp) - Can be opened as image - Passes validate_image() checks **Example:** ```python valid, error = validate_image_file(Path("character.png")) if valid: image = Image.open("character.png") ``` #### `validate_image_upload_size(file_size_bytes: int) -> Tuple[bool, Optional[str]]` Validates uploaded file size against Settings.MAX_IMAGE_UPLOAD_SIZE. **Example:** ```python if uploaded_file: valid, error = validate_image_upload_size(uploaded_file.size) if not valid: st.error(error) ``` ### Request Validation #### `validate_generation_request(...) -> Tuple[bool, Optional[str]]` Validates complete generation request. **Parameters:** - `prompt`: Text prompt - `backend`: Backend name - `aspect_ratio`: Aspect ratio - `temperature`: Temperature value - `input_images`: Optional list of input images **Validates:** - All individual parameters - Input images (if provided, max 3) **Example:** ```python valid, error = validate_generation_request( prompt=prompt, backend=backend, aspect_ratio=aspect_ratio, temperature=temperature, input_images=[img1, img2] ) if not valid: st.error(error) return # Proceed with generation result = generate(...) ``` #### `validate_character_forge_request(...) -> Tuple[bool, Optional[str]]` Validates Character Forge-specific request. **Parameters:** - `character_name`: Character name - `initial_image`: Initial image (Face Only / Full Body modes) - `face_image`: Face image (Face+Body Separate) - `body_image`: Body image (Face+Body Separate) - `image_type`: Input mode type - `backend`: Backend name **Validates:** - Character name - Backend - Correct images for selected mode **Example:** ```python valid, error = validate_character_forge_request( character_name="Hero", initial_image=None, face_image=face_img, body_image=body_img, image_type="Face + Body (Separate)", backend="Gemini API (Cloud)" ) if not valid: st.error(error) return ``` ### Backend Availability #### `validate_backend_available(backend: str, api_key: Optional[str] = None) -> Tuple[bool, Optional[str]]` Check if backend is available and configured. **For Gemini:** - Checks if API key is provided **For OmniGen2:** - Makes HTTP request to /health endpoint - Checks server is responding and healthy **Example:** ```python valid, error = validate_backend_available( backend=st.session_state.backend, api_key=st.session_state.gemini_api_key ) if not valid: st.warning(error) st.stop() ``` ### Helper Functions #### `raise_if_invalid(is_valid: bool, error_message: Optional[str], exception_type=ValueError)` Convert validation result to exception. **Example:** ```python valid, error = validate_temperature(temp) raise_if_invalid(valid, error, ValueError) # Raises ValueError if invalid ``` #### `log_validation_error(validation_result: Tuple[bool, Optional[str]], context: str = "")` Log validation error if validation failed. **Example:** ```python result = validate_prompt(prompt) log_validation_error(result, context="user_input") # Logs: "Validation failed [user_input]: Prompt must be at least 1 character(s)" ``` ## Usage Examples ### Page Input Validation ```python import streamlit as st from utils.validation import ( validate_prompt, validate_backend_available, validate_generation_request ) # Get user inputs prompt = st.text_area("Prompt") backend = st.session_state.backend if st.button("Generate"): # Validate prompt valid, error = validate_prompt(prompt, min_length=5) if not valid: st.error(error) st.stop() # Check backend available valid, error = validate_backend_available(backend, api_key) if not valid: st.warning(error) st.stop() # Validate complete request valid, error = validate_generation_request( prompt=prompt, backend=backend, aspect_ratio=aspect_ratio, temperature=temperature ) if not valid: st.error(error) st.stop() # All valid - proceed result = generate_image(...) ``` ### Service Parameter Validation ```python from utils.validation import validate_generation_request, raise_if_invalid class GenerationService: def generate(self, prompt, backend, aspect_ratio, temperature, ...): # Validate inputs valid, error = validate_generation_request( prompt, backend, aspect_ratio, temperature ) raise_if_invalid(valid, error, ValueError) # Proceed with generation ... ``` ### Backend Status Check ```python import streamlit as st from utils.validation import validate_backend_available def render_backend_status(backend, api_key): valid, error = validate_backend_available(backend, api_key) if valid: st.success(f"✅ {backend}: Ready") else: st.error(f"❌ {backend}: {error}") ``` ## Error Messages All error messages are user-friendly and actionable: **Good Examples:** - ❌ "Prompt must be at least 5 character(s)" (specific, clear) - ❌ "File too large: 25.3MB (max: 20MB)" (includes values) - ❌ "OmniGen2 server not responding. Start it with: omnigen2_plugin/server.bat start" (includes solution) **Not Used:** - ❌ "Invalid input" (too vague) - ❌ "Error" (no information) - ❌ "NoneType has no attribute..." (technical, not user-friendly) ## Validation Strategy ### When to Validate 1. **Before submission** (UI layer) - Validate on button click - Show errors immediately - Prevent submission if invalid 2. **In service layer** (redundant validation) - Validate again for safety - Raise exceptions if invalid - Protects against programmatic calls 3. **Backend availability** (startup + on demand) - Check on app startup - Check when user switches backend - Check before expensive operations ### What NOT to Validate - Don't validate Streamlit widget outputs (they enforce types) - Don't validate internal function calls between modules - Don't validate data from trusted sources (Settings constants) ## Known Limitations - Backend availability check makes network request (slow) - Image validation loads entire image into memory - No async validation support - No batch validation support - No custom validation rules (extension mechanism) ## Future Improvements - Add async validation for slow checks - Add batch validation functions - Add validation caching (avoid redundant checks) - Add custom validation rule registration - Add validation result serialization - Add more granular image checks (color space, DPI, etc.) - Add prompt content validation (detect harmful content) ## Testing - Test all parameter validators with valid/invalid inputs - Test boundary conditions (min/max values) - Test image validators with various formats - Test backend availability with server running/stopped - Test request validators with complete/incomplete data - Test error message clarity and helpfulness ## Related Files - `config/settings.py` - Validation constraints - `utils/logging_utils.py` - Error logging - All UI pages - Input validation - All services - Parameter validation - `models/generation_request.py` - Request validation ## Performance Considerations - validate_backend_available() makes network request (~100ms) - validate_image() loads image into memory - validate_image_file() opens file (I/O) - All other validators are fast (<1ms) ## Change History - 2025-10-23: Initial creation for Streamlit migration - Comprehensive parameter validation - Image validation utilities - Request validation functions - Backend availability checks - User-friendly error messages - Helper functions for error handling