Laravel joy. Python speed. 10x less code.
pip install tina4-python
tina4 init my_project
cd my_project
python app.pyYou've just built your first Tina4 app — zero configuration, zero classes, zero boilerplate!
Prefer uv? Replace
pip install tina4-pythonwithuv add tina4-python, then useuv run tina4 startto launch the dev server.
- ASGI compliant — works with any ASGI server (uvicorn, hypercorn, daphne)
- Full async — every route handler is
asyncby default - Routing — decorator-based with path parameters, type hints, and auto-discovery
- Twig/Jinja2 templates — with inheritance, partials, custom filters, and globals
- tina4-css — lightweight CSS framework (~24 KB) ships built-in, Bootstrap-compatible class names
- ORM — define models with typed fields, save/load/select/delete with one line
- Database — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird, MongoDB
- Migrations — versioned SQL files, CLI scaffolding
- Data seeder — zero-dependency fake data generation with ORM and table support
- Sessions — file, Redis, Valkey, or MongoDB backends
- JWT authentication — HS256 tokens signed with your
SECRETenv var, form tokens - Swagger/OpenAPI — auto-generated docs at
/swagger - CRUD scaffolding — instant searchable admin UI with one line of code
- GraphQL — zero-dependency engine with ORM auto-schema and GraphiQL IDE
- Middleware — before/after hooks per route or globally
- Queues — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
- WebSockets — built-in support via
simple-websocket - WSDL/SOAP — auto-generated WSDL from Python classes
- REST client — built-in
Apiclass for external HTTP calls - SCSS compilation — auto-compiled to CSS on save
- Live reload — browser auto-refreshes during development
- Inline testing — decorator-based test cases with
@tests - Localization — i18n via gettext (English, French, Afrikaans, Chinese, Japanese, Spanish)
pip install tina4-pythonOr with uv (recommended):
uv add tina4-pythonInstall only the database driver 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[all-db] # all of the above
pip install tina4-python[dev-reload] # hot-patching via juriggedRoutes live in src/routes/ and are auto-discovered on startup.
# src/routes/hello.py
from tina4_python import get
@get("/hello")
async def get_hello(request, response):
return response("Hello, Tina4 Python!")
@get("/hello/{name}")
async def get_hello_name(name, request, response):
return response(f"Hello, {name}!")
@get("/hello/json")
async def get_hello_json(request, response):
return response([{"brand": "BMW"}, {"brand": "Toyota"}])
@get("/hello/template")
async def get_hello_template(request, response):
return response.render("index.twig", {"data": request.params})# src/orm/User.py
from tina4_python import ORM, IntegerField, StringField
class User(ORM):
id = IntegerField(primary_key=True, auto_increment=True)
name = StringField()
email = StringField()
User({"name": "Alice", "email": "alice@example.com"}).save()from tina4_python.Database import Database
db = Database("sqlite3:app.db")
db = Database("psycopg2:localhost/5432:mydb", "user", "password") # PostgreSQL
db = Database("mysql.connector:localhost/3306:mydb", "user", "password") # MySQL
db = Database("pymongo:localhost/27017:mydb") # MongoDB
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])tina4 migrate:create "create users table"
# Edit the generated SQL file in migrations/
tina4 migrateGenerate fake data for development and testing:
from tina4_python.Seeder import FakeData, seed_orm, seed_table
fake = FakeData()
fake.name() # "Alice Johnson"
fake.email() # "alice.johnson@example.com"
fake.phone() # "+27 82 123 4567"
# Seed an ORM model
seed_orm(User, count=50)
# Seed a raw table
seed_table(db, "products", columns, count=100)CLI:
tina4 seed # run all files in src/seeds/
tina4 seed:create "initial users" # scaffold a new seed fileGenerate a searchable, paginated admin UI with one call:
from tina4_python.CRUD import CRUD
@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",
}))Built-in session management with pluggable backends:
| Handler | Backend | Package |
|---|---|---|
SessionFileHandler (default) |
File system | — |
SessionRedisHandler |
Redis | redis |
SessionValkeyHandler |
Valkey | valkey |
SessionMongoHandler |
MongoDB | pymongo |
request.session.set("user_id", 42)
user_id = request.session.get("user_id")Tokens are signed with HS256 using your SECRET env var. Set it in .env:
SECRET=your-strong-random-secret-32-chars-minfrom tina4_python import tina4_auth
token = tina4_auth.get_token({"user_id": 42})
is_valid = tina4_auth.valid(token)
payload = tina4_auth.get_payload(token)
hashed = tina4_auth.hash_password("mypassword")
ok = tina4_auth.check_password(hashed, "mypassword")Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
from tina4_python.Queue import Queue, Producer, Consumer
# Enqueue from a route
Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
# Process in a worker
for msg in Consumer(Queue(topic="emails")).messages():
send_email(msg.data)Zero-dependency GraphQL engine with ORM auto-schema:
from tina4_python.GraphQL import GraphQL
gql = GraphQL()
gql.schema.from_orm(User) # auto-generates type, queries, and mutations
gql.schema.from_orm(Product)
gql.register_route("/graphql") # GET = GraphiQL IDE, POST = queriesfrom tina4_python.Router import get, middleware
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})Auto-generated at /swagger. Add metadata with decorators:
from tina4_python import description, tags
@get("/users")
@description("Get all users")
@tags(["users"])
async def users(request, response):
return response(User().select("*"))from tina4_python import Api
api = Api("https://api.example.com", auth_header="Bearer xyz")
result = api.send_request("/users/42")from tina4_python.WSDL import WSDL, wsdl_operation
from tina4_python import get, post
class Calculator(WSDL):
@wsdl_operation({"Result": int})
def Add(self, a: int, b: int):
return {"Result": a + b}
@get("/calculator")
@post("/calculator")
async def calculator(request, response):
return response(Calculator(request).handle())from tina4_python import tests, assert_equal, assert_raises
@tests(
assert_equal((7, 7), 1),
assert_raises(ZeroDivisionError, (5, 0)),
)
def divide(a: int, b: int) -> float:
if b == 0:
raise ZeroDivisionError("division by zero")
return a / bRun with tina4 test or uv run pytest.
Key .env settings:
SECRET=your-jwt-secret-32-chars-min
API_KEY=your-api-key
DATABASE_NAME=sqlite3:app.db
TINA4_DEBUG_LEVEL=ALL
TINA4_LANGUAGE=en
TINA4_SESSION_HANDLER=SessionFileHandler
SWAGGER_TITLE=My APIMIT (c) 2007-2025 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
