FastAPI sizzles, Django dazzles. The best of both worlds in one framework.
Project description
Cotlette 🚀
Cotlette is a modern, Django-inspired web framework built on top of FastAPI. It combines the best of both worlds: the speed and async power of FastAPI with the convenience of Django-like project structure, ORM, templates, and management commands.
Key Features
- FastAPI Under the Hood: High-performance async web framework
- Django-like Project Structure: Familiar and easy to organize
- SQLAlchemy-powered ORM: Simple, Pythonic, and extensible with support for multiple databases
- Alembic Migrations: Powerful database migration system
- Jinja2 Templates: Powerful and flexible HTML rendering
- Admin Panel: Built-in, customizable (inspired by Django admin)
- Management Commands: CLI for project/app creation, server, shell, migrations, and more
- Asynchronous Support: Full async views and endpoints with automatic context detection
- Multi-Database Support: SQLite, PostgreSQL, MySQL, Oracle, and more
- Extensible: Add your own apps, middleware, commands, and more
🎯 URL-Based Async/Sync Mode Detection
Cotlette uses URL-based mode detection to determine whether to use synchronous or asynchronous database operations. This approach provides explicit control and predictable behavior across all frameworks.
How It Works
The mode is determined by the presence of async drivers in your database URL:
# Synchronous mode (default)
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite:///' + str(BASE_DIR / 'db.sqlite3'), # Sync mode
}
}
# Asynchronous mode
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite+aiosqlite:///' + str(BASE_DIR / 'db.sqlite3'), # Async mode
}
}
Supported Database Drivers
Synchronous Drivers:
- SQLite:
sqlite:///db.sqlite3 - PostgreSQL:
postgresql://user:pass@localhost/dbname - MySQL:
mysql://user:pass@localhost/dbname - Oracle:
oracle://user:pass@localhost/dbname
Asynchronous Drivers:
- SQLite:
sqlite+aiosqlite:///db.sqlite3 - PostgreSQL:
postgresql+asyncpg://user:pass@localhost/dbname - MySQL:
mysql+aiomysql://user:pass@localhost/dbname
Benefits
- ✅ Explicit Control: You choose the mode explicitly in settings
- ✅ Predictable Behavior: No dependency on execution context
- ✅ Framework Agnostic: Works with FastAPI, Django, Flask, or any framework
- ✅ Easy Switching: Simply change the URL to switch modes
- ✅ Clear Intent: URL clearly shows sync or async database drivers
Quick Start
1. Install Cotlette
pip install cotlette
2. Create a New Project
cotlette startproject myproject
cd myproject
3. Run the Development Server
cotlette runserver
Open your browser at http://127.0.0.1:8000
Example Projects
Cotlette comes with two complete example projects demonstrating both synchronous and asynchronous modes:
Synchronous Example (example/)
cd example
cotlette runserver
- Uses
sqlite:///db.sqlite3(synchronous mode) - Direct iteration:
for user in users: - Direct template passing:
"users": users
Asynchronous Example (example_async/)
cd example_async
cotlette runserver
- Uses
sqlite+aiosqlite:///db.sqlite3(asynchronous mode) - Async iteration:
async for user in users: - Template execution:
"users": await users.execute()
Project Configuration
Settings Structure
# config/settings.py
import pathlib
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite:///' + str(BASE_DIR / 'db.sqlite3'), # Sync mode
# 'URL': 'sqlite+aiosqlite:///' + str(BASE_DIR / 'db.sqlite3'), # Async mode
}
}
INSTALLED_APPS = [
'apps.home',
'apps.admin',
'apps.users',
'apps.accounts',
'apps.groups',
]
TEMPLATES = [
{
"BACKEND": "cotlette.template.backends.jinja2.Jinja2",
"DIRS": ["templates"],
"APP_DIRS": True
},
]
SECRET_KEY = b'your-secret-key'
ALGORITHM = "HS256"
Screenshots
Home Page:
Login Page:
Admin Panel:
Example: Defining a Model
from cotlette.core.database import Model, CharField, IntegerField, AutoField
from cotlette.core.database.fields.related import ForeignKeyField
class UserModel(Model):
table = "users_usermodel"
id = AutoField() # Primary key
name = CharField(max_length=50)
age = IntegerField()
email = CharField(max_length=100)
password_hash = CharField(max_length=255)
group = ForeignKeyField(to="GroupModel", related_name="users")
organization = CharField(max_length=100)
Universal ORM Usage
Cotlette ORM automatically detects the mode based on your database URL configuration and works accordingly. No need for separate sync/async methods!
Basic CRUD Operations
# Create
article = Article.objects.create(title="Hello", content="World", author_id=1)
# Get single object
user = UserModel.objects.get(id=1)
user = UserModel.objects.get(email="john@example.com")
# Filter
users = UserModel.objects.filter(age__gte=25).execute()
users = UserModel.objects.filter(group_id=1).execute()
# Update
user.name = "Jane Doe"
user.save()
# Delete
user.delete()
# Count
count = UserModel.objects.count()
active_users = UserModel.objects.filter(age__gte=18).count()
# Exists
exists = UserModel.objects.filter(email="john@example.com").exists()
In Async Mode
When using async database URLs, the same methods automatically work asynchronously:
async def async_view():
# Create
article = await Article.objects.create(title="Hello", content="World", author_id=1)
# Get
user = await UserModel.objects.get(id=1)
# Filter
users = await UserModel.objects.filter(age__gte=25).execute()
# Update
user.name = "Jane Doe"
await user.save()
# Delete
await user.delete()
# Count
count = await UserModel.objects.count()
# Exists
exists = await UserModel.objects.filter(email="john@example.com").exists()
Example: Creating Views
Synchronous View (with sync database URL)
from fastapi import APIRouter, Request
from cotlette.shortcuts import render_template
from starlette.authentication import requires
from .models import UserModel
router = APIRouter()
@router.get("/users", response_model=None)
@requires("user_auth")
async def users_view(request: Request):
users = UserModel.objects.all() # Direct iteration works in sync mode
return render_template(request=request, template_name="admin/users.html", context={
"users": users, # Can be passed directly to template
"config": request.app.settings,
})
Asynchronous View (with async database URL)
from fastapi import APIRouter, Request
from cotlette.shortcuts import render_template
from starlette.authentication import requires
from .models import UserModel
router = APIRouter()
@router.get("/users", response_model=None)
@requires("user_auth")
async def users_view(request: Request):
users = UserModel.objects.all()
# Option 1: Async iteration for lazy loading
async for user in users:
print("user", user)
# Option 2: Execute for template context
return render_template(request=request, template_name="admin/users.html", context={
"users": await users.execute(), # Must execute for template
"config": request.app.settings,
})
Note: The same ORM methods work in both contexts! Cotlette automatically detects the mode based on database URL configuration.
Lazy Loading and Iteration
Cotlette supports lazy loading and iteration over QuerySet objects in both synchronous and asynchronous modes.
Synchronous Mode
# Method 1: Execute to get list
users = UserModel.objects.all().execute()
for user in users:
print(user.name)
# Method 2: Direct iteration (lazy loading)
users = UserModel.objects.all()
for user in users:
print(user.name)
# Method 3: Indexing
first_user = UserModel.objects.all().get_item(0)
first_two = UserModel.objects.all().get_item(slice(0, 2))
Asynchronous Mode
# Method 1: Execute to get list
users = await UserModel.objects.all().execute()
for user in users:
print(user.name)
# Method 2: Async iteration (lazy loading)
users = UserModel.objects.all()
async for user in users:
print(user.name)
# Method 3: Indexing
first_user = await UserModel.objects.all().get_item(0)
first_two = await UserModel.objects.all().get_item(slice(0, 2))
Template Usage
Synchronous Mode:
@router.get("/users")
async def users_view(request: Request):
users = UserModel.objects.all()
return render_template(request=request, template_name="users.html", context={
"users": users, # Can be passed directly
})
Asynchronous Mode:
@router.get("/users")
async def users_view(request: Request):
users = UserModel.objects.all()
return render_template(request=request, template_name="users.html", context={
"users": await users.execute(), # Must execute for template
})
Important Notes
- In synchronous mode: Use regular
forloops for iteration - In asynchronous mode: Use
async forloops for iteration - The
execute()method always returns a list that can be iterated normally - Direct iteration provides lazy loading - data is fetched only when needed
- Indexing and slicing work in both modes with appropriate await calls
- For Jinja2 templates in async mode, use
await queryset.execute()to get a regular list
Advanced ORM Features
Query Chaining
# Complex queries with chaining (sync mode)
articles = Article.objects.filter(author_id=1).order_by('-id').execute()
# In async mode
articles = await Article.objects.filter(author_id=1).order_by('-id').execute()
Iteration and Lazy Loading
# Iterate over QuerySet results (sync mode)
for article in Article.objects.all().iter():
print(article.title)
# Get specific items by index or slice (sync mode)
first_article = Article.objects.all().get_item(0)
recent_articles = Article.objects.all().get_item(slice(0, 10))
# In async mode
async for article in Article.objects.all().iter():
print(article.title)
first_article = await Article.objects.all().get_item(0)
recent_articles = await Article.objects.all().get_item(slice(0, 10))
Bulk Operations
# Create multiple objects (sync mode)
articles = [
Article(title="Article 1", content="Content 1", author_id=1),
Article(title="Article 2", content="Content 2", author_id=1),
]
for article in articles:
article.save()
# In async mode
for article in articles:
await article.save()
Database Support
Cotlette supports multiple databases through SQLAlchemy with both sync and async drivers:
# SQLite (sync and async)
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite:///db.sqlite3', # Sync mode
# 'URL': 'sqlite+aiosqlite:///db.sqlite3', # Async mode
}
}
# PostgreSQL (sync and async)
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'postgresql://user:pass@localhost/dbname', # Sync mode
# 'URL': 'postgresql+asyncpg://user:pass@localhost/dbname', # Async mode
}
}
# MySQL (sync and async)
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'mysql://user:pass@localhost/dbname', # Sync mode
# 'URL': 'mysql+aiomysql://user:pass@localhost/dbname', # Async mode
}
}
Async/Sync Mode Configuration
Cotlette determines the database mode (sync/async) based on the URL configuration in settings:
# Synchronous mode (default)
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite:///db.sqlite3', # Sync mode
}
}
# Asynchronous mode
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'sqlite+aiosqlite:///db.sqlite3', # Async mode
}
}
# PostgreSQL examples
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'postgresql://user:pass@localhost/dbname', # Sync mode
}
}
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'postgresql+asyncpg://user:pass@localhost/dbname', # Async mode
}
}
# MySQL examples
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'mysql://user:pass@localhost/dbname', # Sync mode
}
}
DATABASES = {
'default': {
'ENGINE': 'cotlette.core.database.sqlalchemy',
'URL': 'mysql+aiomysql://user:pass@localhost/dbname', # Async mode
}
}
Note: The mode is determined by the presence of async drivers in the URL. No automatic conversion - you explicitly choose sync or async mode in your settings!
Benefits of URL-based Mode Detection
- Explicit Control: You choose the mode explicitly in settings, not based on execution context
- Predictable Behavior: No dependency on whether you're in an async function or not
- Framework Agnostic: Works consistently with FastAPI, Django, Flask, or any other framework
- Easy Switching: Simply change the URL to switch between sync and async modes
- Clear Intent: The URL clearly shows whether you're using sync or async database drivers
Supported Async Drivers
- SQLite:
sqlite+aiosqlite://(requiresaiosqlitepackage) - PostgreSQL:
postgresql+asyncpg://(requiresasyncpgpackage) - MySQL:
mysql+aiomysql://(requiresaiomysqlpackage) - MySQL:
mysql+asyncmy://(requiresasyncmypackage)
Management Commands
Project Management
cotlette startproject <project_name>— Create a new Cotlette project directory structurecotlette startapp <app_name>— Create a new Cotlette app directory structure
Development Server
cotlette runserver [addrport]— Start the development server- Optional arguments:
--ipv6,--reload - Example:
cotlette runserver 0.0.0.0:8000
- Optional arguments:
Interactive Shell
cotlette shell— Interactive Python shell with auto-imports- Options:
--no-startup,--no-imports,--interface,--command - Supports IPython, bpython, and standard Python
- Options:
Database Management
cotlette makemigrations [--message] [--empty]— Create database migrations- Options:
--message,--empty - Example:
cotlette makemigrations --message "Add user model"
- Options:
cotlette migrate [--revision] [--fake]— Apply database migrations- Options:
--revision,--fake - Example:
cotlette migrate --revision head
- Options:
User Management
cotlette createsuperuser— Create a superuser account- Options:
--username,--email,--noinput - Interactive mode for secure password input
- Options:
Documentation
- Technical Documentation
- ORM Reference
- Template Reference
- Command Reference
- Middleware Reference
- Extending Cotlette
- FAQ
License
MIT License. See LICENSE for details.
Contributing
Pull requests and issues are welcome! See GitHub.
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 cotlette-0.0.29.tar.gz.
File metadata
- Download URL: cotlette-0.0.29.tar.gz
- Upload date:
- Size: 36.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a02bf1e9c3934ae7be7de53ba1c5e53540532d8de2da7e71ecb1f321746190ad
|
|
| MD5 |
aaade7bf8fe142fe6397d35196888e42
|
|
| BLAKE2b-256 |
297b10ba5d5c01dfb3188c2f7dd276434504c8fdb2aa8e0a590827bd672751b2
|
File details
Details for the file cotlette-0.0.29-py3-none-any.whl.
File metadata
- Download URL: cotlette-0.0.29-py3-none-any.whl
- Upload date:
- Size: 36.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2892c91971b47cfc5b661ac27f705777dab03d9ab247fa1ca3f96207f2b53947
|
|
| MD5 |
239226ce5bc679598d88ae36a9d09423
|
|
| BLAKE2b-256 |
54ae0c544dfb3d74256e74d7b197fd9a25c65f10a788f05bbd74dda09ca385cd
|