54 built-in features. Zero dependencies. One import, everything works.
Documentation • Getting Started • Features • CLI Reference • Parity • tina4.com
# Install the Tina4 CLI
cargo install tina4 # or download binary from https://github.com/tina4stack/tina4/releases
# Create a project
tina4 init python ./my-app
# Run it
cd my-app && tina4 serveOpen http://localhost:7145 — your app is running.
Without the Tina4 CLI
# 1. Create project
mkdir my-app && cd my-app
uv init && uv add tina4-python
# 2. Create entry point
echo 'from tina4_python.core import run; run()' > app.py
# 3. Create .env
echo 'TINA4_DEBUG=true' > .env
echo 'TINA4_LOG_LEVEL=ALL' >> .env
# 4. Create route directory
mkdir -p src/routes
# 5. Run
uv run python app.pyEvery feature is built from scratch -- no pip install, no node_modules, no third-party runtime dependencies in core.
| Category | Features |
|---|---|
| Core HTTP (7) | Router with path params ({id:int}, {p:path}), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
| Database (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird — unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
| ORM (7) | Active Record with typed fields, relationships (has_one/has_many/belongs_to), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
| Auth & Security (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
| Templating (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
| API & Integration (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
| Background (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters — service runner — event system (on/emit/once/off) |
| Data & Storage (4) | Session (File/Redis/Valkey/MongoDB/DB), response cache (LRU, TTL), seeder + 50+ fake data generators, messenger (SMTP/IMAP) |
| Developer Tools (7) | Dev dashboard (11 tabs), dev toolbar, error overlay (Catppuccin Mocha), dev mailbox, hot reload + CSS hot-reload, code metrics (complexity, coupling, maintainability), AI context installer (7 tools) |
| Utilities (7) | DI container (transient + singleton), HtmlElement builder, inline testing (@tests decorator), i18n (6 languages), Swagger/OpenAPI auto-generation, CLI scaffolding (generate model/route/migration/middleware), structured logging |
2,066 tests. Zero dependencies. Full parity across Python, PHP, Ruby, and Node.js.
For full documentation visit tina4.com.
pip install tina4-pythonOr with uv (recommended):
uv add tina4-pythonInstall only what you need:
pip install tina4-python[postgres] # PostgreSQL (psycopg2-binary)
pip install tina4-python[mysql] # MySQL / MariaDB (mysql-connector-python)
pip install tina4-python[mssql] # Microsoft SQL Server (pymssql)
pip install tina4-python[firebird] # Firebird (firebird-driver)
pip install tina4-python[mongo] # MongoDB (pymongo)
pip install tina4-python[odbc] # ODBC (pyodbc)
pip install tina4-python[all-db] # All of the above
pip install tina4-python[dev-reload] # Hot-patching via juriggedtina4python init my-app
cd my-appThis creates:
my-app/
├── app.py # Entry point
├── .env # Configuration
├── src/
│ ├── routes/ # API + page routes (auto-discovered)
│ ├── orm/ # Database models
│ ├── app/ # Service classes and shared helpers
│ ├── templates/ # Frond/Twig templates
│ ├── seeds/ # Database seeders
│ ├── scss/ # SCSS (auto-compiled to public/css/)
│ └── public/ # Static assets served at /
├── migrations/ # SQL migration files
└── tests/ # pytest tests
Create src/routes/hello.py:
from tina4_python.core.router import get, post
@get("/api/hello")
async def hello(request, response):
return response({"message": "Hello from Tina4!"})
@get("/api/hello/{name}")
async def hello_name(request, response):
name = request.param("name")
return response({"message": f"Hello, {name}!"})Visit http://localhost:7145/api/hello -- routes are auto-discovered, no imports needed.
Edit .env:
DATABASE_URL=sqlite:///data/app.dbCreate and run a migration:
tina4python migrate:create "create users table"Edit the generated SQL:
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);tina4python migrateCreate src/orm/User.py:
from tina4_python.orm import ORM, IntegerField, StringField, DateTimeField
class User(ORM):
table_name = "users"
id = IntegerField(primary_key=True, auto_increment=True)
name = StringField(required=True, min_length=1, max_length=100)
email = StringField(regex=r'^[^@]+@[^@]+\.[^@]+$')
created_at = DateTimeField()Create src/routes/users.py:
from tina4_python.core.router import get, post, noauth
@get("/api/users")
async def list_users(request, response):
from src.orm.User import User
return response(User().select(limit=100).to_array())
@get("/api/users/{id:int}")
async def get_user(id, request, response):
from src.orm.User import User
user = User()
if user.load("id = ?", [id]):
return response(user.to_dict())
return response({"error": "Not found"}, 404)
@noauth()
@post("/api/users")
async def create_user(request, response):
from src.orm.User import User
user = User(request.body)
user.save()
return response(user.to_dict(), 201)Create src/templates/base.twig:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="/css/tina4.min.css">
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<script src="/js/frond.js"></script>
{% block javascripts %}{% endblock %}
</body>
</html>Create src/templates/pages/home.twig:
{% extends "base.twig" %}
{% block content %}
<div class="container mt-4">
<h1>{{ title }}</h1>
<ul>
{% for user in users %}
<li>{{ user.name }} -- {{ user.email }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}Render it from a route:
@get("/")
async def home(request, response):
from src.orm.User import User
users = User().select(limit=20).to_array()
return response.render("pages/home.twig", {"title": "Users", "users": users})tina4python seed # Run seeders from src/seeds/
tina4python test # Run test suite
tina4python build # Build distributableFor the complete step-by-step guide, visit tina4.com.
from tina4_python.core.router import get, post, put, delete, noauth, secured, middleware
@get("/api/items") # Public by default
async def list_items(request, response):
return response({"items": []})
@noauth() # Make a write route public
@post("/api/webhook")
async def webhook(request, response):
return response({"ok": True})
@secured() # Protect a GET route
@get("/api/admin/stats")
async def admin_stats(request, response):
return response({"secret": True})Path parameter types: {id} (string), {id:int}, {price:float}, {path:path} (greedy).
Active Record with typed fields, validation, soft delete, relationships, scopes, and multi-database support.
from tina4_python.orm import ORM, IntegerField, StringField, Field, orm_bind
class User(ORM):
table_name = "users"
id = IntegerField(primary_key=True, auto_increment=True)
name = StringField(required=True, min_length=1, max_length=100)
email = StringField(regex=r'^[^@]+@[^@]+\.[^@]+$')
role = StringField(choices=["admin", "user", "guest"], default="user")
age = Field(int, min_value=0, max_value=150)
# CRUD
user = User({"name": "Alice", "email": "alice@example.com"})
user.save()
user.load("email = ?", ["alice@example.com"])
user.delete()
# Relationships
orders = user.has_many("Order", "user_id")
profile = user.has_one("Profile", "user_id")
# Soft delete, scopes, caching
user.soft_delete()
active_admins = User.scope("active").scope("admin").select()
users = User.cached("SELECT * FROM users", ttl=300)
# Multi-database
orm_bind(main_db) # Default
orm_bind(audit_db, name="audit") # Named
class AuditLog(ORM):
_db = "audit" # Uses named connectionUnified interface across 7 engines:
from tina4_python.database.connection import Database
db = Database("sqlite:///data/app.db")
db = Database("postgresql://user:pass@localhost:5432/mydb")
db = Database("mysql://user:pass@localhost:3306/mydb")
db = Database("mssql://sa:pass@localhost:1433/mydb")
db = Database("firebird://SYSDBA:masterkey@localhost:3050//path/to/db")
db = Database("mongodb://localhost:27017/mydb")
db = Database("odbc://DSN=mydsn")
result = db.fetch("SELECT * FROM users WHERE age > ?", [18], limit=20, offset=0)
row = db.fetch_one("SELECT * FROM users WHERE id = ?", [1])
db.insert("users", {"name": "Alice", "email": "alice@test.com"})
db.commit()class AuthCheck:
@staticmethod
def before_auth(request, response):
if "authorization" not in request.headers:
return request, response("Unauthorized", 401)
return request, response
@middleware(AuthCheck)
@get("/protected")
async def protected(request, response):
return response({"secret": True})from tina4_python.auth import Auth
auth = Auth(secret="your-secret")
token = auth.get_token({"user_id": 42})
payload = auth.valid_token(token)POST/PUT/PATCH/DELETE routes require Authorization: Bearer <token> by default. Use @noauth() to make public, @secured() to protect GET routes.
request.session.set("user_id", 42)
user_id = request.session.get("user_id")Backends: file (default), Redis, Valkey, MongoDB, database. Set via TINA4_SESSION_HANDLER in .env.
from tina4_python.queue import Queue, Producer, Consumer
Producer(Queue(topic="emails")).push({"to": "alice@example.com"})
for job in Consumer(Queue(topic="emails")).poll():
send_email(job.data)
job.complete()from tina4_python.graphql import GraphQL
gql = GraphQL()
gql.schema.from_orm(User)
gql.register_route("/graphql") # GET = GraphiQL IDE, POST = queriesfrom tina4_python.websocket import WebSocketManager
ws = WebSocketManager()
@ws.route("/ws/chat")
async def chat(connection, message):
await ws.broadcast("/ws/chat", f"User said: {message}")Auto-generated at /swagger:
@description("Get all users")
@tags(["users"])
@get("/api/users")
async def users(request, response):
return response(User().select().to_array())from tina4_python.core.events import on, emit, once
@on("user.created", priority=10)
def notify_admin(user):
send_notification(f"New user: {user['name']}")
emit("user.created", {"name": "Alice"})Twig-compatible, 35+ filters, macros, inheritance, fragment caching, sandboxing:
{% extends "base.twig" %}
{% block content %}
<h1>{{ title | upper }}</h1>
{% for item in items %}
<p>{{ item.name }} -- {{ item.price | number_format(2) }}</p>
{% endfor %}
{% cache "sidebar" 300 %}
{% include "partials/sidebar.twig" %}
{% endcache %}
{% endblock %}@get("/admin/users")
async def admin_users(request, response):
return response(CRUD.to_crud(request, {
"sql": "SELECT id, name, email FROM users",
"title": "User Management",
"primary_key": "id",
}))from tina4_python.wsdl import WSDL, wsdl_operation
class Calculator(WSDL):
@wsdl_operation({"Result": int})
def Add(self, a: int, b: int):
return {"Result": a + b}from tina4_python.api import Api
api = Api("https://api.example.com", auth_header="Bearer xyz")
result = api.send_request("/users/42")from tina4_python.seeder import FakeData, seed_orm
fake = FakeData()
fake.name() # "Alice Johnson"
fake.email() # "alice.johnson@example.com"
seed_orm(User, count=50)from tina4_python.messenger import create_messenger
mail = create_messenger()
mail.send(to="user@test.com", subject="Welcome", body="<h1>Hi!</h1>", html=True)from tina4_python.core.cache import Cache
cache = Cache()
cache.set("key", "value", ttl=300)
cache.tag("users").flush()- SCSS: Drop
.scssinsrc/scss/-- auto-compiled to CSS. Variables, nesting, mixins,@import,@extend. - i18n: JSON translation files, 6 languages (en, fr, af, zh, ja, es), placeholder interpolation.
- Inline tests:
@tests(assert_equal((5, 3), 8))decorator on any function.
Set TINA4_DEBUG=true in .env to enable:
- Live reload -- browser auto-refreshes on code changes
- CSS hot-reload -- SCSS changes apply without page refresh
- Error overlay -- rich error display in the browser
- Dev admin at
/__dev/with 11 tabs: Routes, Queue, Mailbox, Messages, Database, Requests, Errors, WebSocket, System, Tools, Tina4
tina4python init [dir] # Scaffold a new project
tina4python serve [port] # Start dev server (default: 7145)
tina4python serve --production # Auto-install and use best production server (uvicorn)
tina4python migrate # Run pending migrations
tina4python migrate:create <desc> # Create a migration file
tina4python migrate:rollback # Rollback last batch
tina4python generate model <name> # Generate ORM model scaffold
tina4python generate route <name> # Generate route scaffold
tina4python generate migration <d> # Generate migration file
tina4python generate middleware <n># Generate middleware scaffold
tina4python seed # Run seeders from src/seeds/
tina4python routes # List all registered routes
tina4python test # Run test suite
tina4python build # Build distributable package
tina4python ai [--all] # Detect AI tools and install contexttina4 serve automatically detects and uses the best available production server:
- Python: uvicorn (if installed), otherwise built-in asyncio
- Use
tina4python serve --productionto auto-install the production server
Quickly scaffold new components:
tina4python generate model User # Creates src/orm/User.py with field stubs
tina4python generate route users # Creates src/routes/users.py with CRUD stubs
tina4python generate migration "add age" # Creates migration SQL file
tina4python generate middleware AuthLog # Creates middleware class# Define relationships
orders = user.has_many("Order", "user_id")
profile = user.has_one("Profile", "user_id")
customer = order.belongs_to("Customer", "customer_id")
# Eager loading with include=
users = User().select(include=["orders", "profile"])Enable query caching for up to 4x speedup on read-heavy workloads:
# .env
TINA4_DB_CACHE=true# Check cache stats
from tina4_python.orm import cache_stats, cache_clear
stats = cache_stats() # {"hits": 42, "misses": 7, "size": 15}
cache_clear() # Flush all cached queriesTemplates are pre-compiled for 2.8x faster rendering. Clear the cache when needed:
from tina4_python.frond import Frond
Frond.clear_cache()7 interactive examples with Try It deploy — visit the dev admin at /__dev/ to explore.
SECRET=your-jwt-secret
DATABASE_URL=sqlite:///data/app.db
TINA4_DEBUG=true # Enable dev toolbar, error overlay
TINA4_LOG_LEVEL=ALL # ALL, DEBUG, INFO, WARNING, ERROR
TINA4_LOCALE=en # en, fr, af, zh, ja, es
TINA4_SESSION_HANDLER=SessionFileHandler
SWAGGER_TITLE=My APItina4python ai # Detect and install context
tina4python ai --all # Install for ALL supported toolsSupported: Claude Code, Cursor, GitHub Copilot, Windsurf, Aider, Cline, OpenAI Codex CLI. Generates framework-aware context so AI assistants understand Tina4's conventions.
Benchmarked with wrk — 5,000 requests, 50 concurrent, median of 3 runs:
| Framework | JSON req/s | Deps | Features |
|---|---|---|---|
| Tina4 Python | 6,508 | 0 | 54 |
| FastAPI | 12,652 | 12+ | ~8 |
| Flask | 4,928 | 6+ | ~7 |
| Bottle | 4,355 | 0 | ~5 |
| Django | 4,050 | 20+ | ~22 |
Tina4 Python delivers competitive throughput with zero dependencies and 54 features — frameworks with higher req/s have a fraction of the functionality and require dozens of third-party packages.
Across all 4 Tina4 implementations:
| Python | PHP | Ruby | Node.js | |
|---|---|---|---|---|
| JSON req/s | 6,508 | 29,293 | 10,243 | 84,771 |
| Dependencies | 0 | 0 | 0 | 0 |
| Features | 54 | 54 | 54 | 54 |
Run benchmarks locally: python benchmarks/benchmark.py --python
Tina4 ships identical features across four languages — same architecture, same conventions, same 54 features:
| Python | PHP | Ruby | Node.js | |
|---|---|---|---|---|
| Package | tina4-python |
tina4stack/tina4php |
tina4ruby |
tina4-nodejs |
| Tests | 2,066 | 1,427 | 1,793 | 1,950 |
| Default port | 7145 | 7146 | 7147 | 7148 |
7,236 tests across all 4 frameworks. See tina4.com.
Full guides, API reference, and examples at tina4.com.
MIT (c) 2007-2026 Tina4 Stack https://opensource.org/licenses/MIT
Tina4 -- The framework that keeps out of the way of your coding.
Sponsored with 🩵 by Code Infinity
Supporting open source communities • Innovate • Code • Empower
