Spaces:
Sleeping
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 operationsPIL.Image- Image validationconfig.settings.Settings- Validation constraintsutils.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:
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:
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:
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 validatemin_length: Minimum length (default: 1)max_length: Maximum length (default: 5000)
Example:
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:
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:
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:
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:
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 promptbackend: Backend nameaspect_ratio: Aspect ratiotemperature: Temperature valueinput_images: Optional list of input images
Validates:
- All individual parameters
- Input images (if provided, max 3)
Example:
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 nameinitial_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 typebackend: Backend name
Validates:
- Character name
- Backend
- Correct images for selected mode
Example:
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:
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:
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:
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
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
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
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
Before submission (UI layer)
- Validate on button click
- Show errors immediately
- Prevent submission if invalid
In service layer (redundant validation)
- Validate again for safety
- Raise exceptions if invalid
- Protects against programmatic calls
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 constraintsutils/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