Skip to content

Tina4 Python – Quick Reference

🔥 Hot Tips

  • Routes go in src/routes/, templates in src/templates/, static files in src/public/
  • GET routes are public by default; POST/PUT/PATCH/DELETE require a token
  • Return a dict from response() and the framework auto-sets application/json
  • Use uv run tina4 start to launch the dev server on port 7145

Installation

bash
pip install tina4-python
tina4 init my-project
cd my-project
tina4 start

More details around project setup and some customizations.

Static Websites

Put .twig files in ./src/templates • assets in ./src/public

twig
<!-- src/templates/index.twig -->
<h1>Hello Static World</h1>

More details on static website routing.

Basic Routing

python
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

python
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]After

Follow the links for more on Middleware Declaration and Linking to Routes.

Template Rendering

Put .twig files in ./src/templates • assets in ./src/public

twig
<!-- src/templates/index.twig -->
<h1>Hello {{name}}</h1>
python
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

HandlerBackendRequired package
SessionFileHandler (default)File system
SessionRedisHandlerRedisredis
SessionValkeyHandlerValkeyvalkey
SessionMongoHandlerMongoDBpymongo
env
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
python
@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

scss
// 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.db
python
import 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.

python
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

twig
<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

python
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

python
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

python
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

bash
tina4 migrate:create create_users_table
sql
-- migrations/00001_create_users_table.sql
CREATE TABLE users
(
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT
);
bash
tina4 migrate

Migrations do have some limitations and considerations when used extensively.

ORM

python
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

python
@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)})
twig
{{ crud }}

More details on how CRUD works, where it puts the generated files is worth some investigation.

Consuming REST APIs

python
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

python
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 / b

Run: tina4 test

Services

Due to the nature of python, services are not necessary.

Websockets

Requires simple-websocket, add with uv add simple-websocket

python
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.

python
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

python
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.

python
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.

python
from tina4_python.Localization import AVAILABLE_LANGUAGES
# ['en', 'fr', 'af']