Auto-generate beautiful HTML forms from Pydantic models for FastAPI
Project description
🌊 Cuyutlán - Auto-Generate FastAPI Forms from Pydantic Models
Never write HTML forms again. Define your data model, get beautiful forms automatically.
Cuyutlán (pronounced: koo-yoot-LAHN) - Named after the famous beach town in Colima, Mexico, known for its powerful waves. Like those waves, Cuyutlán streamlines your development flow! 🌊
🎯 The Problem
When building FastAPI apps, you face repetitive work:
- Define Pydantic model (your data structure)
- Write HTML form (duplicate the structure)
- Add client-side validation (duplicate the rules)
- Add server-side validation (already in Pydantic!)
- Style the form (tedious CSS)
Result: 90% of form code is boilerplate.
✨ The Solution: Cuyutlán
Write your Pydantic model once. Get everything automatically:
- ✅ Beautiful HTML forms
- ✅ Client-side validation (from Pydantic rules)
- ✅ Server-side validation (already there!)
- ✅ Responsive design (Bootstrap 5)
- ✅ Type-specific widgets (date pickers, file uploads, etc.)
- ✅ Custom templates (override what you need)
🚀 Quick Start
Installation
pip install cuyutlan
Basic Example (3 lines!)
from fastapi import FastAPI, Request
from pydantic import BaseModel, EmailStr, Field
from cuyutlan import render_form
app = FastAPI()
# 1. Define your data model (you're doing this anyway!)
class UserRegistration(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
email: EmailStr
age: int = Field(..., ge=18, le=120)
newsletter: bool = False
# 2. Auto-generate form (ONE LINE!)
@app.get("/register")
async def register_form(request: Request):
return render_form(UserRegistration, submit_url="/register", request=request)
# 3. Handle submission (standard FastAPI - no changes!)
@app.post("/register")
async def register_submit(user: UserRegistration):
# Validation already done by FastAPI/Pydantic!
return {"message": f"Welcome {user.username}!"}
That's it! You now have:
- ✅ Beautiful form at
/register - ✅ Client-side validation (min_length, email format, age range)
- ✅ Server-side validation (automatic)
- ✅ Error messages
- ✅ Mobile responsive
🎨 What You Get
Type-Specific Widgets
Cuyutlán automatically selects the right HTML input based on type:
from pydantic import BaseModel, EmailStr, HttpUrl
from datetime import date, datetime
from typing import Literal
class SmartForm(BaseModel):
# Text input
name: str
# Email input (with validation)
email: EmailStr
# Number input
age: int
# Date picker
birth_date: date
# Datetime picker
appointment: datetime
# Select dropdown
role: Literal["admin", "user", "guest"]
# Textarea
bio: str = Field(..., max_length=500)
# Checkbox
agree_terms: bool
# URL input
website: HttpUrl
Validation from Pydantic
All Pydantic validators become HTML5 constraints:
class Product(BaseModel):
name: str = Field(..., min_length=3, max_length=100)
# → <input minlength="3" maxlength="100" required>
price: float = Field(..., ge=0, le=10000)
# → <input type="number" min="0" max="10000" required>
sku: str = Field(..., pattern=r"^[A-Z]{3}-\d{4}$")
# → <input pattern="^[A-Z]{3}-\d{4}$" required>
email: EmailStr
# → <input type="email" required>
🔥 Features
1. Type-to-Widget Mapping
| Pydantic Type | HTML Widget |
|---|---|
str |
<input type="text"> |
int/float |
<input type="number"> |
EmailStr |
<input type="email"> |
bool |
<input type="checkbox"> |
date |
<input type="date"> |
Literal["a", "b"] |
<select> dropdown |
2. Validation Mapping
| Pydantic Constraint | HTML5 Attribute |
|---|---|
min_length=3 |
minlength="3" |
max_length=20 |
maxlength="20" |
ge=0 |
min="0" |
pattern=r"..." |
pattern="..." |
3. Custom Widgets
from cuyutlan import render_form
config = FormConfig(
theme="dark", # or "light"
framework="bootstrap5",
)
return render_form(MyModel, config=config, request=request)
📊 Business Impact
Before Cuyutlán:
- 2-3 hours per form
- 100+ lines of HTML/JS
- Frequent sync bugs
After Cuyutlán:
- 5 minutes per form
- 5 lines of code
- Zero sync bugs
Savings: 90% time, 95% code, 100% bugs eliminated
🎯 Real-World Examples
Example 1: User Registration
class UserSignup(BaseModel):
username: str = Field(..., min_length=3)
email: EmailStr
password: str = Field(..., min_length=8, widget="password")
age: int = Field(..., ge=18)
terms: bool = Field(..., description="I agree to terms")
@app.get("/signup")
async def signup_page(request: Request):
return render_form(UserSignup, submit_url="/signup", request=request)
Example 2: Product Management
class Product(BaseModel):
name: str = Field(..., max_length=200)
description: str = Field(..., widget="textarea")
price: float = Field(..., ge=0)
category: Literal["electronics", "clothing", "food"]
in_stock: bool = True
@app.get("/products/new")
async def new_product(request: Request):
return render_form(Product, submit_url="/products", request=request)
🏗️ Use Cases
Perfect For:
- ✅ Admin panels
- ✅ Internal tools
- ✅ CRUD applications
- ✅ Rapid prototyping
- ✅ MVP development
- ✅ Form-heavy apps (surveys, registrations)
📖 Documentation
- Quick Start: See above
- Full Docs: (coming soon)
- Examples: See
examples/folder - API Reference: (coming soon)
🤝 Contributing
Cuyutlán is open-source (MIT). Contributions welcome!
git clone https://github.com/carlos-robles/cuyutlan
cd cuyutlan
pip install -e ".[dev]"
pytest
📄 License
MIT License - See LICENSE file
🙏 Credits
Created by: Carlos Andres Robles Hernandez
Named after: Cuyutlán, Colima, Mexico 🇲🇽 - A beautiful beach town known for its powerful waves and laid-back vibe. This library aims to bring that same smooth, powerful flow to your FastAPI development!
Inspired by:
- Django Forms (but for FastAPI)
- Flask-WTF (but modern)
- The FastAPI community
Built with:
- FastAPI
- Pydantic
- Jinja2
- Bootstrap 5
🔗 Links
- PyPI: https://pypi.org/project/cuyutlan
- GitHub: https://github.com/carlos-robles/cuyutlan
- Issues: https://github.com/carlos-robles/cuyutlan/issues
Stop writing forms. Start building features. 🌊
¡Hecho en México con ❤️!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cuyutlan-0.1.1.tar.gz.
File metadata
- Download URL: cuyutlan-0.1.1.tar.gz
- Upload date:
- Size: 12.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
050c960bd98d4a51e4579e39e57d30da44044cc248ac33bb50f85cdad5ed0d94
|
|
| MD5 |
3e3f9906096f2cc07886368e7dd69bb0
|
|
| BLAKE2b-256 |
5712faa6fbac8e78eb774429349fe227569ebfa13cf173dcb9f8461b0f8f79c3
|
File details
Details for the file cuyutlan-0.1.1-py3-none-any.whl.
File metadata
- Download URL: cuyutlan-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3c779144b22c1b91e6f0fcc8552cab2300aa1a432a63f5d7e183ee1bb98e1ba
|
|
| MD5 |
a81bb7a70bdf1dab95d2e0b483c0d64b
|
|
| BLAKE2b-256 |
b0bb5738d35d5da197cb04aa7a675b771b72805389faaec80a2f0148c9123bee
|