DEV Community

Beltrán Offerrall
Beltrán Offerrall

Posted on

Generates Webs from Type Hints

The Problem I Was Solving

I run a laser cutting business. Every day I need to:

  • Process images for engraving
  • Generate reports from production data
  • Share tools with my team (who don't code)

I wanted something insane: Just type hints → complete web UI

So I built it.


Introducing FuncToWeb

After a month, I shipped FuncToWeb - a framework that auto-generates production-ready web interfaces from Python type hints.

Zero HTML. Zero CSS. Zero JavaScript.

The 30-Second Demo

from typing import Annotated
from func_to_web import run
from func_to_web.types import ImageFile
from pydantic import Field

def process_image(
    image: ImageFile,
    brightness: Annotated[int, Field(ge=0, le=100)] = 50,
    mode: Literal["grayscale", "sepia", "blur"] = "grayscale"
):
    # Your image processing logic here
    processed = apply_filter(image, brightness, mode)
    return processed  # PIL Image auto-displayed

run(process_image)
Enter fullscreen mode Exit fullscreen mode

What you get:

  • ✅ Professional file upload with drag-and-drop
  • ✅ Slider for brightness (0-100, validated client + server)
  • ✅ Dropdown for mode selection
  • ✅ Real-time validation errors
  • ✅ Result image displayed beautifully
  • ✅ Dark mode toggle (preference saved)

3 lines of setup. 0 lines of UI code.


Every Type Becomes The Right UI Component

The magic is in the type hints:

from func_to_web import run
from func_to_web.types import Color, Email, DocumentFile, ImageFile
from datetime import date, time

def create_project(
    # Text types
    name: str,                    # Text input
    email: Email,                 # Email input with validation

    # Numbers
    budget: float,                # Number input (decimals)
    team_size: int,               # Number input (integers)

    # Choices
    active: bool,                 # Checkbox
    priority: Literal["low", "medium", "high"],  # Dropdown

    # Dates & Times
    deadline: date,               # Date picker
    meeting: time,                # Time picker

    # Visual
    brand_color: Color,           # Color picker

    # Files
    logo: ImageFile,              # Image upload (jpg, png, webp, etc.)
    contract: DocumentFile,       # Document upload (pdf, docx, etc.)
):
    return f"Project '{name}' created!"

run(create_project)
Enter fullscreen mode Exit fullscreen mode

All of this becomes a complete form automatically.


Validation That Actually Works

Pydantic Integration

from typing import Annotated
from func_to_web import run
from pydantic import Field

def register_user(
    username: Annotated[str, Field(min_length=3, max_length=20)],
    age: Annotated[int, Field(ge=18, le=120)],
    email: Annotated[str, Field(pattern=r'^[^@]+@[^@]+\.[^@]+$')],
    password: Annotated[str, Field(min_length=8)],
):
    return f"Welcome {username}!"

run(register_user)
Enter fullscreen mode Exit fullscreen mode

Error messages appear in real-time:

  • "Username must be at least 3 characters"
  • "Age must be between 18 and 120"
  • "Please enter a valid email"
  • "Password must be at least 8 characters"

Both client-side (instant) AND server-side (secure).


Lists With Two-Level Validation

This is where it gets crazy. You can validate both the list size AND each individual item:

from typing import Annotated
from func_to_web import run
from pydantic import Field

def rate_products(
    # List must have 3-10 items
    # Each item must be between 1-5
    ratings: Annotated[
        list[Annotated[int, Field(ge=1, le=5)]], 
        Field(min_length=3, max_length=10)
    ],
):
    avg = sum(ratings) / len(ratings)
    return f"Average rating: {avg:.2f}"

run(rate_products)
Enter fullscreen mode Exit fullscreen mode

The UI shows:

  • Dynamic add/remove buttons
  • Validation error if you have < 3 or > 10 ratings
  • Validation error if any rating is not 1-5
  • Pre-filled defaults if you provide them

Works with ALL types: list[str], list[Color], list[date], etc.


Dynamic Dropdowns (Runtime-Populated)

Want dropdowns populated from a database or API? Use Literal[function]:

from func_to_web import run
from typing import Literal

def get_users_from_database():
    # This runs when the form loads
    return ["Alice", "Bob", "Charlie", "Diana"]

def assign_task(
    user: Literal[get_users_from_database],  # Dropdown calls function!
    priority: Literal["low", "medium", "high"],
    task: str
):
    return f"Task '{task}' assigned to {user}"

run(assign_task)
Enter fullscreen mode Exit fullscreen mode

The dropdown is populated at runtime by calling your function. Perfect for database queries, API calls, or dynamic options.


Optional Fields With State Control

Standard Python optional syntax works, but you can also control the initial state:

from func_to_web import run
from func_to_web.types import OptionalEnabled, OptionalDisabled, Email

def create_user(
    username: str,  # Required (no toggle)

    # Standard Python: Auto-detected state
    age: int | None = None,         # Disabled (no default)
    city: str | None = "Madrid",    # Enabled (has default)

    # Explicit control: You decide initial state
    email: Email | OptionalEnabled,     # Always starts enabled
    phone: str | OptionalDisabled,      # Always starts disabled
    bio: str | OptionalDisabled = "Dev",  # Disabled despite having default
):
    return f"User {username} created"

run(create_user)
Enter fullscreen mode Exit fullscreen mode

Each optional field gets a toggle switch. Disabled fields send None automatically.


Return Images and Plots (Auto-Displayed)

from func_to_web import run
from PIL import Image, ImageEnhance
from func_to_web.types import ImageFile

def enhance_image(
    image: ImageFile,
    brightness: float = 1.0,
    contrast: float = 1.0,
):
    img = Image.open(image)

    # Enhance
    if brightness != 1.0:
        enhancer = ImageEnhance.Brightness(img)
        img = enhancer.enhance(brightness)

    if contrast != 1.0:
        enhancer = ImageEnhance.Contrast(img)
        img = enhancer.enhance(contrast)

    return img  # PIL Image auto-displayed in UI!

run(enhance_image)
Enter fullscreen mode Exit fullscreen mode

Works with:

  • PIL/Pillow Images (any format)
  • Matplotlib figures
  • Multiple images (returns list)

File Downloads

from func_to_web import run
from func_to_web.types import FileResponse
import pandas as pd
from io import BytesIO

def generate_report(
    name: str,
    items: int,
):
    # Create Excel file
    df = pd.DataFrame({"Name": [name] * items, "ID": range(items)})
    buffer = BytesIO()
    df.to_excel(buffer, index=False)

    # Return as downloadable file
    return FileResponse(
        data=buffer.getvalue(),
        filename=f"report_{name}.xlsx"
    )

run(generate_report)
Enter fullscreen mode Exit fullscreen mode

Users get a download button. Works with:

  • Excel, CSV, JSON
  • PDFs, Word documents
  • ZIP files, images
  • Any binary data

Return a list for multiple files: [FileResponse(...), FileResponse(...)]


Multiple Functions in One App

from func_to_web import run

def calculate_bmi(weight_kg: float, height_m: float):
    """Calculate Body Mass Index"""
    bmi = weight_kg / (height_m ** 2)
    return f"BMI: {bmi:.2f}"

def celsius_to_fahrenheit(celsius: float):
    """Convert Celsius to Fahrenheit"""
    return f"{celsius}°C = {(celsius * 9/5) + 32}°F"

def reverse_text(text: str):
    """Reverse a string"""
    return text[::-1]

# Pass a list - gets auto-generated index page
run([calculate_bmi, celsius_to_fahrenheit, reverse_text])
Enter fullscreen mode Exit fullscreen mode

FuncToWeb creates a responsive index page where users pick which tool to use.


How It Works (Architecture)

  1. Type Introspection - Python's inspect module extracts function signature
  2. Pydantic Integration - Validates constraints from Field annotations
  3. Form Generation - Jinja2 templates convert types → HTML inputs
  4. FastAPI Backend - Handles requests, validation, file uploads
  5. Result Processing - Auto-detects return type (text, image, file) and displays accordingly

Tech Stack:

  • FastAPI (async backend)
  • Pydantic (validation)
  • Vanilla JS/CSS (no React/Vue needed!)
  • Jinja2 (templating)
  • Uvicorn (ASGI server)

Total size: ~2000 lines of Python, 454 automated tests.


Real-World Use Cases

I use FuncToWeb daily in my laser cutting business:

  1. Image Processing - Brightness/contrast tools for engraving prep
  2. Production Reports - Generate Excel reports from laser job data
  3. Team Tools - Share scripts with non-technical operators
  4. Client Demos - Quick prototypes without deployment hassle
  5. Admin Panels - Manage products, orders, settings

Other users:

  • Data scientists - Share analysis tools with stakeholders
  • Backend devs - Internal tools without learning frontend
  • Researchers - Share computational tools with colleagues
  • DevOps - Quick dashboards for infrastructure tasks

Comparison: FuncToWeb vs Alternatives

Feature FuncToWeb Streamlit Gradio
Syntax Pure type hints st. API gr.Interface()
Learning curve Zero (if you know typing) Medium Low-Medium
Wrap existing functions Works as-is Need modifications Need wrapper
Two-level list validation ✅ Yes ❌ No ❌ No
Dynamic Literal[func] ✅ Yes ❌ No ❌ No
Optional state control ✅ Yes Manual Manual
Pydantic Field integration ✅ Full ❌ No ❌ Limited
File type hints ImageFile, DocumentFile Manual handling gr.File()
Multiple functions Auto index page Multi-page app Tabs interface
Dependencies Minimal Heavy Medium

Getting Started (60 Seconds)

pip install func-to-web
Enter fullscreen mode Exit fullscreen mode
from func_to_web import run
from typing import Annotated
from pydantic import Field

def greet(
    name: Annotated[str, Field(min_length=2)],
    age: Annotated[int, Field(ge=0, le=150)],
    formal: bool = False
):
    greeting = "Good day" if formal else "Hey"
    return f"{greeting} {name}! You are {age} years old."

run(greet)
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8000Instant web UI! 🚀


Documentation

Currently at 208 GitHub stars and growing fast.

Drop a comment with your use case!

And if you like the project, a ⭐ on GitHub would mean the world.


Resources

Top comments (0)