55 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:7146. Your app is running.
Two CLIs:
tina4is the cross-language Rust CLI that scaffolds projects, runs the dev server, and watches files.tina4pythonis the Python package's own CLI for project tasks (migrate,seed,generate,test). This guide usestina4to scaffold and run, andtina4pythonfor those tasks.
Without the Tina4 CLI (Docker / CI only)
The framework normally refuses to start without the tina4 Rust CLI (it owns file watching and SCSS compilation). To bypass (e.g. inside a Docker image where you've already built the assets), set TINA4_OVERRIDE_CLIENT=true in .env:
# 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 (note the override)
echo 'TINA4_DEBUG=true' > .env
echo 'TINA4_LOG_LEVEL=ALL' >> .env
echo 'TINA4_OVERRIDE_CLIENT=true' >> .env
# 4. Create route directory
mkdir -p src/routes
# 5. Run (no file watching, no hot reload in this mode)
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,281 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 juriggedtina4 init python 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:7146/api/hello -- routes are auto-discovered, no imports needed.
Edit .env:
TINA4_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": "[email protected]"})
user.save()
user.load("email = ?", ["[email protected]"])
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": "[email protected]"})
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_BACKEND in .env.
from tina4_python.queue import Queue, Producer, Consumer
Producer(Queue(topic="emails")).push({"to": "[email protected]"})
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() # "[email protected]"
seed_orm(User, count=50)from tina4_python.messenger import create_messenger
mail = create_messenger()
mail.send(to="[email protected]", 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 -- the
tina4Rust CLI watchessrc/,migrations/,.envand POSTs/__dev/api/reloadto the running server; the framework broadcasts to the browser via WebSocket (/__dev_reload) with a polling fallback (GET /__dev/api/mtime) - CSS hot-reload -- SCSS changes apply without a full page refresh
- Error overlay -- rich error display in the browser
- Dev admin at
/__dev/with tabs: Routes, Queue, Mailbox, Messages, Database, Requests, Errors, WebSocket, System, Tools, Tina4
tina4python init [dir] # Scaffold a new project
tina4python serve [--port P] [--no-browser] [--no-reload] # Dev server (default: 0.0.0.0:7146)
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.
TINA4_SECRET=your-jwt-secret
TINA4_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_BACKEND=file # file (default), redis, valkey, mongodb, database
TINA4_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 | 55 |
| 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 55 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 | 55 | 55 | 55 | 55 |
Run benchmarks locally: python benchmarks/benchmark.py --python
Tina4 ships identical features across four languages: same architecture, same conventions, same 55 features:
| Python | PHP | Ruby | Node.js | |
|---|---|---|---|---|
| Package | tina4-python |
tina4stack/tina4php |
tina4ruby |
tina4-nodejs |
| Tests (v3.11.12) | 2,281 | 2,073 | 2,508 | 2,897 |
| Default port | 7146 | 7145 | 7147 | 7148 |
~9,700 tests across all 4 frameworks. See tina4.com.
A complete e-commerce app lives in example/. It demonstrates every framework feature through a real-world use case.
cd example
bash setup.sh # macOS/Linux
# or: setup.bat # Windows
.venv/bin/python app.py| Role | Password | |
|---|---|---|
| Admin | [email protected] | admin123 |
| Customer | [email protected] | customer123 |
See example/README.md for full details.
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
