Concepts 🚀 Project 🧮 Labs
📚 Learn / Mini Projects / File Upload + Save + List

File Upload + Save + List

Upload files via multipart form, save to disk with a UUID name, list all uploaded files. The pattern behind OpenBenchML's model upload.

🚀 Mini Project · 2 files

Project files

📄 main.py python
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import HTMLResponse, FileResponse
from pathlib import Path
import uuid, uvicorn

app = FastAPI()
UPLOAD_DIR = Path('uploads')
UPLOAD_DIR.mkdir(exist_ok=True)

@app.get('/')
async def form():
    return HTMLResponse('''
      <form method='post' action='/upload' enctype='multipart/form-data'>
        <input type='file' name='file' required>
        <button>Upload</button>
      </form>
      <a href='/files'>View uploaded files</a>
    ''')

@app.post('/upload')
async def upload(file: UploadFile = File(...)):
    # Validate extension
    if not file.filename.endswith(('.pkl', '.csv', '.png', '.txt')):
        raise HTTPException(400, 'unsupported file type')

    # Generate a unique name to avoid collisions
    ext = Path(file.filename).suffix
    safe_name = f'{uuid.uuid4().hex}{ext}'
    dest = UPLOAD_DIR / safe_name

    # Stream to disk (don't load into memory — large files)
    with open(dest, 'wb') as f:
        while chunk := await file.read(1024 * 1024):  # 1MB chunks
            f.write(chunk)

    return {'saved_as': safe_name, 'size_bytes': dest.stat().st_size}

@app.get('/files')
async def list_files():
    files = [
        {'name': f.name, 'size': f.stat().st_size}
        for f in UPLOAD_DIR.iterdir() if f.is_file()
    ]
    return files

@app.get('/download/{name}')
async def download(name: str):
    f = UPLOAD_DIR / name
    if not f.exists():
        raise HTTPException(404)
    return FileResponse(f)

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)
📄 run.sh bash
pip install fastapi uvicorn python-multipart
python main.py

# Open http://localhost:8000 in a browser
# Pick a .pkl, .csv, .png, or .txt file → Upload
# Click 'View uploaded files' to see the saved list
# Each file gets a UUID name to prevent collisions

How it works

File upload has 3 critical details: (1) enctype='multipart/form-data' on the form — without it, the file isn't sent. (2) UploadFile = File(...) on the server side — FastAPI streams the file, it doesn't load it all into RAM. (3) Stream to disk in chunks — `while chunk := await file.read(1MB): f.write(chunk)`. Reading the whole file into memory would crash the server on a 2GB upload. Other good practices: validate the extension (defends against malicious uploads), generate a UUID filename (prevents collisions and path traversal attacks — never trust the user's filename), and store size for display. The download endpoint uses FileResponse which streams the file back to the browser.

⚡ Open in Notebook
🔍 Where this is used in OpenBenchML app/routes/models.py — model .pkl upload. app/routes/datasets.py — dataset .csv upload. Both use this exact streaming pattern.