> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing Long Sessions Effectively

> Guide to building a Flask image optimization web app with drag and drop upload, rate limiting, secure temp storage, image processing, and session notes for managing long sessions.

This lesson walks through building a single-image optimization web app with Flask to demonstrate techniques for managing long, iterative sessions and preserving context. The project is a practical vehicle for exploring how to:

* Design a long-lived context project that’s easy to iterate on.
* Keep notes and session state so context can be recovered or handed off.
* Implement a secure, maintainable Flask app for image optimization.

Core objectives

* Single-image optimization web app with a drag-and-drop UI.
* Support JPEG, PNG, WebP (reject SVG and GIF).
* Rate-limit to 30 requests/min per IP.
* Max 25 MB upload size.
* Store temp files in a secure temporary directory and remove them on exit.
* Show a "Before" preview immediately and an "After" placeholder until optimization completes.
* Use Pillow for I/O and keep OpenCV available for future enhancements.
* Provide Docker/Gunicorn notes for deployment (optional).
* Deliver a complete project: app code, HTML, JS/CSS, and session notes.

Overview and initial prompt
Below is an example prompt that guided development of the project. It’s a concise specification you can use to re-create the task or to brief an assistant or collaborator.

```text theme={null}
Build a single image optimization web app using flask.

Requirements:

- Flask web UI with drag-and-drop upload
- Support JPEG/PNG/WebP only (reject SVG/GIF)
- Rate limiting (30 req/min per IP)
- Max 25MB upload size
- Auto-cleanup temp files
- Show "Before" uploaded image and placeholder "After" pane
- Use Pillow for image I/O, keep OpenCV available for future features

Complete Flask app structure (routes, config, validators, cleanup)
- HTML templates with drag-and-drop UI
- Static files (JS for upload, CSS styling)
- requirements.txt
```

Project structure (high level)

| File | Purpose |
| - | - |
| `app.py` | Flask application: routes, validation, optimization, cleanup |
| `templates/index.html` | Main UI with drag-and-drop and controls |
| `static/js/main.js` | Upload logic, progress UI, optimize calls |
| `static/css/style.css` | Styling for UI and controls |
| `requirements.txt` | Python dependencies |
| `SESSION_NOTES.md` | Session notes to rehydrate context across long sessions |

Key dependencies (requirements.txt)

```text theme={null}
Flask==2.3.3
Flask-Limiter==3.5.0
Pillow==10.1.0
opencv-python==4.8.1.78
```

Quick dependency table

| Package | Role |
| - | - |
| `Flask` | Web framework |
| `Flask-Limiter` | Per-IP rate limiting |
| `Pillow` | Image I/O and processing |
| `opencv-python` | Optional future features (kept installed) |

Core application (app.py)
Below is a concise, corrected, and robust skeleton covering configuration, upload route, optimization handler, cleanup, and error handlers. It integrates the required functionality: upload validation, optimization pipeline, thread-safe temp-file tracking, and error handling.

```python theme={null}
# app.py
import os
import uuid
import tempfile
import atexit
import shutil
import traceback
from datetime import datetime, timedelta
from threading import Lock
from flask import Flask, request, render_template, jsonify, send_file, url_for, abort
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from PIL import Image, ImageFilter, ImageEnhance, ImageOps

# Optional import kept for future features
import cv2  # noqa: F401

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
app.config['UPLOAD_FOLDER'] = tempfile.mkdtemp(prefix="img_opt_")
app.config['ALLOWED_EXTENSIONS'] = {'jpg', 'jpeg', 'png', 'webp'}

# Rate limiting: 30 requests per minute per IP (in-memory store by default)
limiter = Limiter(app, key_func=get_remote_address, default_limits=["30 per minute"])

# Thread-safety for temp file tracking
upload_lock = Lock()
uploaded_files = {}  # uuid -> original filepath

atexit.register(lambda: shutil.rmtree(app.config['UPLOAD_FOLDER'], ignore_errors=True))


def allowed_file(filename: str) -> bool:
    ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
    return ext in app.config['ALLOWED_EXTENSIONS']


@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')


@app.route('/upload', methods=['POST'])
@limiter.limit("30/minute")
def upload():
    file = request.files.get('file')
    if not file:
        return jsonify({'error': 'No file provided'}), 400

    filename = file.filename or ''
    if not allowed_file(filename):
        return jsonify({'error': 'Unsupported file type'}), 400

    uid = str(uuid.uuid4())
    ext = filename.rsplit('.', 1)[-1].lower()
    saved_name = f"{uid}.{ext}"
    saved_path = os.path.join(app.config['UPLOAD_FOLDER'], saved_name)

    with upload_lock:
        file.save(saved_path)
        uploaded_files[uid] = {'original': saved_path, 'created': datetime.utcnow()}

    return jsonify({'success': True, 'id': uid, 'before_url': url_for('serve_file', uid=uid, name='original')})


@app.route('/file/<uid>/<name>', methods=['GET'])
def serve_file(uid, name):
    info = uploaded_files.get(uid)
    if not info:
        abort(404)
    path = info.get('original') if name == 'original' else info.get('optimized')
    if not path or not os.path.exists(path):
        abort(404)
    return send_file(path, as_attachment=False)


def cleanup_temp_file(path):
    try:
        if os.path.exists(path):
            os.remove(path)
    except Exception:
        app.logger.exception("Failed to cleanup temp file: %s", path)


@app.route('/optimize/<uid>', methods=['POST'])
@limiter.limit("30/minute")
def optimize(uid):
    info = uploaded_files.get(uid)
    if not info:
        return jsonify({'error': 'Upload not found'}), 404

    try:
        # Read parameters with safe types and defaults
        quality = int(float(request.form.get('quality', 85)))
        resize_percent = int(float(request.form.get('resize_percent', 100)))
        strip_metadata = request.form.get('strip_metadata', 'true').lower() == 'true'
        auto_orient = request.form.get('auto_orient', 'true').lower() == 'true'
        preset = request.form.get('preset', 'balanced')
        format_type = request.form.get('format', 'JPEG').upper()

        filepath = info['original']
        optimized_filename = f"optimized_{uid}.{format_type.lower() if format_type != 'JPEG' else 'jpg'}"
        optimized_path = os.path.join(app.config['UPLOAD_FOLDER'], optimized_filename)

        # Ensure previous optimized removed
        if os.path.exists(optimized_path):
            cleanup_temp_file(optimized_path)

        with Image.open(filepath) as img:
            # Auto-orient based on EXIF
            if auto_orient:
                img = ImageOps.exif_transpose(img)

            # Resize
            if resize_percent != 100 and resize_percent > 0:
                new_w = max(1, int(img.width * resize_percent / 100))
                new_h = max(1, int(img.height * resize_percent / 100))
                img = img.resize((new_w, new_h), Image.LANCZOS)

            # Convert RGBA -> RGB for JPEG
            if format_type == 'JPEG' and img.mode in ('RGBA', 'LA', 'P'):
                background = Image.new('RGB', img.size, (255, 255, 255))
                alpha = img.convert('RGBA').split()[-1]
                background.paste(img.convert('RGBA'), mask=alpha)
                img = background

            # Optional enhancements: parse sliders (sharpen, contrast, brightness)
            sharpen_val = float(request.form.get('sharpen', 0.0))  # negative -> blur, positive -> sharpen
            if sharpen_val != 0:
                if sharpen_val > 0:
                    # UnsharpMask expects integer percent; round and clamp
                    percent = max(1, min(500, int(round(sharpen_val * 100))))
                    img = img.filter(ImageFilter.UnsharpMask(radius=2, percent=percent, threshold=3))
                else:
                    # blur amount
                    radius = max(0.1, abs(sharpen_val))
                    img = img.filter(ImageFilter.GaussianBlur(radius=radius))

            contrast_val = float(request.form.get('contrast', 1.0))
            if contrast_val != 1.0:
                img = ImageEnhance.Contrast(img).enhance(contrast_val)

            brightness_val = float(request.form.get('brightness', 1.0))
            if brightness_val != 1.0:
                img = ImageEnhance.Brightness(img).enhance(brightness_val)

            # Strip metadata if requested
            save_kwargs = {}
            if strip_metadata:
                img.info.pop('exif', None)
            else:
                if 'exif' in img.info:
                    save_kwargs['exif'] = img.info['exif']

            # Format specific saving options
            if format_type == 'JPEG':
                save_kwargs.update({'quality': max(1, min(95, quality)), 'optimize': True})
                save_format = 'JPEG'
            elif format_type == 'WEBP':
                save_kwargs.update({'quality': max(1, min(95, quality)), 'method': 6})
                save_format = 'WEBP'
            else:
                save_format = 'PNG'

            img.save(optimized_path, save_format, **save_kwargs)

        # Track optimized path
        with upload_lock:
            uploaded_files[uid]['optimized'] = optimized_path

        # Optionally compute compression ratio etc.
        before_size = os.path.getsize(filepath)
        after_size = os.path.getsize(optimized_path)
        ratio = 100.0 * (before_size - after_size) / before_size if before_size else 0.0

        return jsonify({
            'success': True,
            'optimized_url': url_for('serve_file', uid=uid, name='optimized'),
            'before_size': before_size,
            'after_size': after_size,
            'reduction_percent': round(ratio, 2)
        })
    except Exception as e:
        app.logger.error("Optimization error: %s", str(e))
        traceback.print_exc()
        return jsonify({'error': f'Failed to optimize image: {str(e)}'}), 500


@app.errorhandler(413)
def too_large(e):
    return jsonify({'error': 'File too large. Maximum size is 25MB'}), 413


@app.errorhandler(429)
def ratelimit_handler(e):
    return jsonify({'error': 'Rate limit exceeded. Please try again later.'}), 429


if __name__ == '__main__':
    # Run on an alternate port if 5000 is in use
    app.run(debug=True, port=int(os.environ.get('PORT', 5001)))
```

Implementation notes and highlights

* Secure temporary directory created via `tempfile.mkdtemp()` and cleaned up on exit.
* UUID-based filenames avoid collisions and make file tracking straightforward.
* The original upload is preserved so users can iterate optimizations without re-uploading.
* RGBA images are converted to RGB when saving to JPEG to avoid format errors.
* UnsharpMask percent is converted to integer; blur uses GaussianBlur for negative sharpen values.
* Clear JSON responses include `before` and `optimized` URLs so the front end can fetch previews.

Running locally

```bash theme={null}
# Create and activate venv
python3 -m venv venv
source venv/bin/activate

# Install
python3 -m pip install -r requirements.txt

# Run
python3 app.py
# or set a different port if 5000 is occupied:
PORT=5001 python3 app.py
```

UI and user flow

* Drag-and-drop upload with file browser fallback.
* Immediately show a "Before" preview once the upload completes.
* Display an "After" placeholder until optimization completes.
* Provide sliders and toggles for:
  * Quality (1–95)
  * Resize percentage (10–200)
  * Strip metadata
  * Auto-orient (EXIF)
  * Sharpen/Blur
  * Contrast and Brightness
  * Format selection: JPEG / PNG / WebP
  * Presets (speed, balanced, max)
* Users can upload once and apply multiple optimization iterations; the app keeps the original in temporary storage.

Troubleshooting (common issues)

* Flask-Limiter in-memory store warning — fine for local testing. For production, configure a persistent store like Redis. See [Flask-Limiter docs](https://flask-limiter.readthedocs.io/en/stable/).
* Port conflicts on macOS (port 5000) — AirPlay Receiver may bind that port. Either disable it (System Settings → Sharing → AirPlay Receiver) or run on a different port.
* If `pip` fails, use `python3 -m pip install ...`.
* To find what's using a port:
  ```bash theme={null}
  lsof -i :5000
  kill -9 <PID>
  ```

Production considerations

<Callout icon="warning" color="#FF6B6B">
  Important: The provided app is suitable for local testing and prototyping. For production, ensure you:

  * Configure Flask-Limiter with a persistent backend (e.g., Redis) to avoid losing rate-limit state on restarts.
  * Add scheduled cleanup for orphaned temp files (e.g., a background job that removes files older than N hours).
  * Run behind a WSGI server like Gunicorn and place the app behind a reverse proxy (e.g., Nginx) for TLS, buffering, and static file serving.
  * Set a secure `SECRET_KEY` via environment variables — do not use the development secret in production.
</Callout>

Session notes (SESSION\_NOTES.md)
Keeping structured session notes helps long sessions remain recoverable and lets collaborators rehydrate important context without re-processing the entire chat history. Below is a sample excerpt you can include in the repository.

```markdown theme={null}
## SESSION 1 Completed:
### Project Overview
Built a Flask web application for image optimization with drag-and-drop upload, support for JPEG/PNG/WebP, rate limiting, 25MB file size limit, and auto cleanup of temp files. Uses Pillow for image I/O and keeps OpenCV available for future features.

### Core Functionality
- Drag-and-drop image upload with visual feedback
- File type validation (JPEG/PNG/WebP only)
- Rate limiting (30 req/min per IP using Flask-Limiter)
- File size limits (25MB)
- Temporary files are removed on process exit; the app uses thread-safe tracking for uploaded files. Consider adding scheduled cleanup for production.
- Before/After image comparison with side-by-side display

### Advanced Controls Implemented
- Quality slider (1–95)
- Resize by percentage (10–200%)
- Strip metadata toggle
- Auto-orient using EXIF
- Sharpen/Blur control (UnsharpMask / GaussianBlur)
- Contrast and Brightness sliders
- Format conversion (JPEG, PNG, WebP)

### Troubleshooting Logs
- Port conflicts resolved by using port 5001 (AirPlay Receiver sometimes binds 5000 on macOS)
- Flask-Limiter in-memory storage warning noted; recommend Redis for production

## SESSION 2 Focus:
- Improve sharpening/blur UX and ensure parameters are type-safe
- Batch processing support
- Persistent storage for presets and analytics (DB integration)
- Docker + Gunicorn production build

## Performance Metrics (example)
- Upload validation: <100ms
- Speed preset: ~200ms
- Balanced preset: ~400ms
- Max Quality preset: ~800ms
```

Best practices for long sessions

<Callout icon="lightbulb" color="#1CB2FE">
  Keep work in small, well-documented chunks and maintain a session notes file (like `SESSION_NOTES.md`). When a session becomes large or the assistant's responses degrade, start a fresh session and load the session notes plus the minimal necessary files to rehydrate context. This reduces hallucinations and improves reproducibility.
</Callout>

Example UI screenshot

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/Xs8Ruv7b0QCZf7iM/images/Claude-Code-for-Beginners/Working-with-Claude-Code/Managing-Long-Sessions-Effectively/image-optimizer-before-after-vintage-portrait.jpg?fit=max&auto=format&n=Xs8Ruv7b0QCZf7iM&q=85&s=b26d88def6277975261b57e402e34001" alt="A screenshot of an image optimizer web interface showing side-by-side &#x22;Before&#x22; and &#x22;After&#x22; versions of a vintage family portrait, with sliders and settings for quality, resize, and metadata below. The right side shows a browser DevTools panel." width="1920" height="1080" data-path="images/Claude-Code-for-Beginners/Working-with-Claude-Code/Managing-Long-Sessions-Effectively/image-optimizer-before-after-vintage-portrait.jpg" />
</Frame>

Closing notes

* This guide demonstrated building a Flask image optimizer while modeling patterns for managing long-running sessions and maintaining context.
* Use `SESSION_NOTES.md` to summarize decisions, TODOs, and next steps so team members or future assistant sessions can quickly pick up where you left off.

Links and references

* [Flask documentation](https://flask.palletsprojects.com/)
* [Flask-Limiter docs](https://flask-limiter.readthedocs.io/en/stable/)
* [Pillow documentation](https://pillow.readthedocs.io/)
* [Redis](https://redis.io/)
* [Gunicorn](https://gunicorn.org/)
* [Docker docs](https://docs.docker.com/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/3e896e50-3c07-4fdc-8603-bf125255d0a9/lesson/1f4ed59a-414b-47c8-acb4-1d5683a136c1" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.