Tina4 Python – Quick Reference
🔥 Hot Tips
- Routes go in
src/routes/, templates insrc/templates/, static files insrc/public/ - GET routes are public by default; POST/PUT/PATCH/DELETE require a token
- Return a
dictfromresponse()and the framework auto-setsapplication/json - Use
uv run tina4 startto launch the dev server on port 7145
Installation
pip install tina4-python
tina4 init my-project
cd my-project
tina4 startMore details around project setup and some customizations.
Static Websites
Put .twig files in ./src/templates • assets in ./src/public
<!-- src/templates/index.twig -->
<h1>Hello Static World</h1>More details on static website routing.
Basic Routing
from tina4_python.Router import get, post
@get("/")
async def get_home(request, response):
return response("<h1>Hello Tina4 Python</h1>")
# post requires a formToken in body or Bearer auth
@post("/api")
async def post_api(request, response):
return response({"data": request.params})
# redirect after post
@post("/register")
async def post_register(request, response):
return response.redirect("/welcome")Follow the links for basic routing and dynamic routing with variables.
Middleware
class RunSomething:
@staticmethod
def before_something(request, response):
response.content += "Before"
return request, response
@staticmethod
def after_something(request, response):
response.content += "After"
return request, response
@staticmethod
def before_and_after_something(request, response):
response.content += "[Before / After Something]"
return request, response
@middleware(RunSomething)
@get("/middleware")
async def get_middleware(request, response):
return response("Route") # Before[Before / After Something]Route[Before / After Something]AfterFollow the links for more on Middleware Declaration and Linking to Routes.
Template Rendering
Put .twig files in ./src/templates • assets in ./src/public
<!-- src/templates/index.twig -->
<h1>Hello {{name}}</h1>from tina4_python.Router import get
@get("/")
async def get_home(request, response):
return response.render("index.twig", {"name": "World!"})Sessions
The default session handling is SessionFileHandler, override TINA4_SESSION_HANDLER in .env
| Handler | Backend | Required package |
|---|---|---|
SessionFileHandler (default) | File system | — |
SessionRedisHandler | Redis | redis |
SessionValkeyHandler | Valkey | valkey |
SessionMongoHandler | MongoDB | pymongo |
TINA4_SESSION_HANDLER=SessionMongoHandler
TINA4_SESSION_MONGO_HOST=localhost
TINA4_SESSION_MONGO_PORT=27017
TINA4_SESSION_MONGO_URI=
TINA4_SESSION_MONGO_USERNAME=
TINA4_SESSION_MONGO_PASSWORD=
TINA4_SESSION_MONGO_DB=tina4_sessions
TINA4_SESSION_MONGO_COLLECTION=sessions@get("/session/set")
async def get_session_set(request, response):
request.session.set("name", "Joe")
request.session.set("info", {"info": ["one", "two", "three"]})
return response("Session Set!")
@get("/session/get")
async def get_session_set(request, response):
name = request.session.get("name")
info = request.session.get("info")
return response({"name": name, "info": info})SCSS Stylesheets
Drop in ./src/scss → auto-compiled to ./src/public/css
// src/scss/main.scss
$primary: #2c3e50;
body {
background: $primary;
color: white;
}More details on css and scss.
Environments
Default development environment can be found in .env
PROJECT_NAME="My Project"
VERSION=1.0.0
TINA4_LANGUAGE=en
TINA4_DEBUG_LEVEL=ALL
API_KEY=ABC1234
TINA4_TOKEN_LIMIT=1
DATABASE_NAME=sqlite3:test.dbimport os
api_key = os.getenv("API_KEY", "ABC1234")Authentication
Pass Authorization: Bearer API_KEY to secured routes in requests. See .env for default API_KEY.
from tina4_python.Router import get, post, noauth, secured
@post("/login")
@noauth()
async def login(request, response):
return response("Logged in")
@get("/protected")
@secured()
async def secret(request, response):
return response("Welcome!")HTML Forms and Tokens
<form method="POST" action="/register">
{{ ("Register" ~ RANDOM()) | form_token }}
<input name="email">
<button>Save</button>
</form>More details on posting form data, basic form handling, how to generate form tokens, dealing with file uploads, returning errors, disabling route auth and a full login example.
AJAX and tina4helper.js
Tina4 ships with a small javascript library, in the bin folder, to assist with the heavy lifting of ajax calls.
More details on available features.
OpenAPI and Swagger UI
Visit http://localhost:7145/swagger
from tina4_python.Router import get
from tina4_python import description
@get("/users")
@description("Get all users")
async def users(request, response):
return response(User().select("*"))Follow the links for more on Configuration, Usage and Decorators.
Databases
from tina4_python.Database import Database
# dba = Database("<driver>:<hostname>/<port>:database_name", username, password)
dba = Database("sqlite3:data.db")Follow the links for more on Available Connections, Core Methods, Usage and Full transaction control.
Database Results
result = dba.fetch("select * from test_record order by id", limit=3, skip=1)
array = result.to_array()
paginated = result.to_paginate()
csv_data = result.to_csv()
json_data = result.to_json()Looking at detailed Usage will improve deeper understanding.
Migrations
tina4 migrate:create create_users_table-- migrations/00001_create_users_table.sql
CREATE TABLE users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);tina4 migrateMigrations do have some limitations and considerations when used extensively.
ORM
from tina4_python.ORM import ORM, IntegerField, StringField
class User(ORM):
id = IntegerField(primary_key=True, auto_increment=True)
name = StringField()
User({"name": "Alice"}).save()
user = User()
user.load("id = ?", [1])ORM functionality is quite extensive and needs more study of the Advanced Detail to get the full value from ORM.
CRUD
@get("/users/dashboard")
async def dashboard(request, response):
users = User().select("id, name, email")
return response.render("users/dashboard.twig", {"crud": users.to_crud(request)}){{ crud }}More details on how CRUD works, where it puts the generated files is worth some investigation.
Consuming REST APIs
from tina4_python import Api
api = Api("https://api.example.com", auth_header="Bearer xyz")
result = api.get("/users/42")
print(result["body"])More details are available on sending a post data body, authorizations and other finer controls of sending api requests.
Inline Testing
from tina4_python import tests
@tests(
assert_equal((7, 7), 1),
assert_equal((-1, 1), -1),
assert_raises(ZeroDivisionError, (5, 0)),
)
def divide(a: int, b: int) -> float:
if b == 0:
raise ZeroDivisionError("division by zero")
return a / bRun: tina4 test
Services
Due to the nature of python, services are not necessary.
Websockets
Requires simple-websocket, add with uv add simple-websocket
from tina4_python.Websocket import Websocket
@get("/ws/chat")
async def chat_ws(request, response):
ws = await Websocket(request).connection()
try:
while True:
data = await ws.receive()
await ws.send(f"Echo: {data}")
finally:
await ws.close()
return response("")Have a look at out PubSub example under Websockets
Threads
Due to the nature of python, threads are not necessary.
Queues
Supports litequeue (default/SQLite), RabbitMQ, Kafka, and MongoDB backends.
from tina4_python.Queue import Queue, Producer, Consumer
# Produce a message
queue = Queue(topic="emails")
Producer(queue).produce({"to": "alice@example.com", "subject": "Welcome"})
# Consume messages
consumer = Consumer(queue)
for msg in consumer.messages():
print(msg.data)Full details on backend configuration, batching, multi-queue consumers, and error handling.
WSDL
from tina4_python.WSDL import WSDL, wsdl_operation
from typing import List
class Calculator(WSDL):
SERVICE_URL = "http://localhost:7145/calculator"
def Add(self, a: int, b: int):
return {"Result": a + b}
def SumList(self, Numbers: List[int]):
return {
"Numbers": Numbers,
"Total": sum(Numbers),
"Error": None
}
@wsdl("/calculator")
async def wsdl_cis(request, response):
return response.wsdl(Calculator(request))More Details are available for WSDL
Localization (i18n)
Set TINA4_LANGUAGE in .env to change framework language. Supported: en, fr, af.
from tina4_python.Localization import localize
_ = localize()
print(_("Server stopped.")) # "Bediener gestop." (af)Translations use Python's gettext module. Falls back to English for unsupported languages.
from tina4_python.Localization import AVAILABLE_LANGUAGES
# ['en', 'fr', 'af']