ghmk's picture
Initial deployment of Character Forge
5b6e956

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:

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 validate
  • min_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 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:

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:

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

  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