Tina4Python is a light-weight routing and twig based templating system based on the Tina4 stack which allows you to write websites and API applications very quickly. .
- Install Poetry:
curl -sSL https://install.python-poetry.org | python3 -
- Install Jurigged (Enables Hot Reloading):
pip install jurigged
After installing poetry you can do the following:
poetry new project-name
cd project-name
poetry add tina4_python
poetry add jurigged
Create an entry point for Tina4 called app.py
and add the following to the file
from tina4_python import *
The basic tina4 project uses an autoloader methodology from the src folder All the source folders you should need are created there and they are run from init.py
If you are developing on Tina4, make sure you copy the public folder from tina4_python into src
Virtual environment
Linux / Mac
python3 -m venv venv
source ./venv/bin/activate
pip install poetry
Windows
python -m venv venv
.\venv\Scripts\activate
pip install poetry
1.) Install Poetry:
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
2.) Add the following path to the system PATH:
%APPDATA%\pypoetry\venv\Scripts
3.) Install Tina4Python and Jurigged:
poetry add tina4_python
poetry add jurigged
or
pip install tina4_python
pip install jurigged
After defining templates and routes, run the Tina4Python server:
- Normal Server:
poetry run python app.py
- Server with Hot Reloading:
poetry run jurigged app.py
- Server on a Specific Port:
poetry run python app.py 7777
- **Server to not autostart **:
poetry run python app.py manual
- Server with alternate language (for example fr = French):
poetry run python app.py fr
Add more translations by going here
Tina4 uses Jinja2 (Twig) templating to provide a simple and efficient way to create web pages.
1.) Twig Files: Add your Twig files within the src/templates
folder. For instance, you might create files like index.twig
, base.twig
, etc., containing your HTML structure with Twig templating syntax for dynamic content.
2.) Using Twig: In these Twig files, you can use Twig syntax to embed dynamic content, control structures, variables, and more. For example:
<!-- index.twig -->
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
The routing in Tina4Python can be defined in the __init__.py
file or any file used as an entry point to your application. Tina4Python provides decorators to define routes easily.
1.) Creating Routes: Define routes using decorators from Tina4Python to handle HTTP requests.
Example:
from tina4_python.Router import get
from tina4_python.Response import Response
@get("/hello")
async def hello(request, response):
return response("Hello, World!")
This code creates a route for a GET request to /hello
, responding with "Hello, World!".
2.) Route Parameters: You can define route parameters by enclosing variables with curly braces { }. The parameters are passed to the function as arguments.
Example:
from tina4_python.Router import get
from tina4_python.Response import Response
@get("/hello/{name}")
async def greet(**params): #(request, response)
name = params['request'].params['name']
return params['response'](f"Hello, {name}!") # return response()
This code creates a route for a GET request to /hello/{name}
, where name
is a parameter in the URL. The function greet
accepts this parameter and responds with a personalized greeting.
Example:
- Visiting
/hello/John
will respond with "Hello, John!" - Visiting
/hello/Alice
will respond with "Hello, Alice!"
3.) POST routes now require jwt token or API key to validate requests with an Authorization header
Authorization: Bearer <token>
You can generate tokens using tina4_python.tina4_auth which takes in a payload parameter which is a dictionary:
You need the following twig file in the src/templates
folder called something.twig
<form method="post">
<input name="email" type="text" placeholder="Email">
<button type="submit">Send</button>
<input type="hidden" name="formToken" value="{{ token }}" >
</form>
You can add the following code to src/routes/example.py
# get router which renders the twig form html
@get("/capture")
async def capture_get(request, response):
# get a token to add to the form
token = tina4_python.tina4_auth.get_token({"data": {"formName": "capture"}})
html = Template.render_twig_template("somefile.twig", {"token": token})
return response(html)
# returns back to the user the form data that has been posted
@post("/capture")
async def capture_post(request, response):
return response(request.body)
@get("/simple")
async def simple_get(request, response):
print("<h1>Hello World!</h1>")
@get("/redirect")
async def simple_get(request, response):
return response.redirect("/test")
In your src/__init__.py
add the following code
from .routes.example import *
Generate tokens with the following code as per the example above, tokens carry a payload and we add an additional expires
value to the token based on the env variable TINA4_TOKEN_LIMIT
import tina4_python
tina4_python.tina4_auth.get_token({"data": {"something":"more"}})
OR
For ease of use you can supply an API_KEY
param to your .env with a secret of your choice to use:
API_KEY=somehash
You may want to use your Tina4 project as a library in another project so suppressing the default webservice is probably needed.
TINA4_DEFAULT_WEBSERVER="False"
or in your __init__.py
file
import os
os.environ["TINA4_DEFAULT_WEBSERVER"] = "False"
Additionally, it will require you to rename src
to the name of your package
Completed | To Do |
---|---|
Python pip package | |
Basic environment handling | |
Routing with Swagger | |
Enhanced Routing | |
CSS Support | |
Image Support | |
Localization | |
Error Pages | |
Template handling | |
Form posting | |
Migrations | |
Colored Debugging | |
Database Abstraction |
Various databases initialised:
dba = Database("sqlite3:test.db", "username", "password")
dba = Database("mysql:localhost/3306:myschema", "username", "password")
dba = Database("postgres:localhost/5432:myschema", "username", "password")
dba = Database("firebird:localhost/3050:/home/database/FIREBIRD.FDB", "username", "password")
NoSQL support (Still to be developed):
dba = Database("mongodb:localhost/27017:mycollection", "username", "password")
dba = Database("firebase:https://your_storage.firebaseio.com", "username", "password")
Fetching records and passing data as parameters, limit and skip specified:
records = dba.fetch("select * from table where something = ? and something2 = ?", params=["something", "something2"], limit=10, skip=5)
print (records)
print (records.to_json())
{
id : 1
something: "something",
something2: "something2"
}
print(records[0].id)
1
record = dba.fetch_one("select * from table where something = ? and something2 = ?", params=["something", "something2"])
print(record.id)
print (records.to_json())
1
Executing sql queries:
dba.execute ("update table set something = ? and something2 = ? where id = ?", params=["something", "something2", 1])
dba.execute ("delete from table where id = ?", params=[1])
Starting a transaction:
dba.start_transaction()
Rollback a transaction:
dba.roll_back()
Commit a transaction:
dba.commit()
Select method (Still in development):
dba.select(["id", "something", "something2"], "table_name", filter={"id": 1}, limit=10, skip=0)
dba.select(["id", "something", "something2"], "table_name", filter=[{"id": 1}, {"id": 2}], limit=10, skip=0)
dba.select(["id", "something", "sum(id)"])
.from(["table"])
.join(["tabel2"])
.on([""])
.and([{"id": 1}])
.where({"id" : 2}, "id = ?", [{"id": 2}])
.having()
.group_by()
.order_by(["id"])
Updating records - records will be found by primary key specified and then updated:
dba.update(table_name="table_name", records.fromJSON(json_string))
dba.update(table_name="table_name", records, primary_key="id")
dba.update(table_name="table_name", record, primary_key="id") # primary key implied by first key value pair
Inserting records - pass a dictionary for a single record and a list of dictionaries for multiple records:
dba.insert(table_name="table_name", dba.from_json(json_string))
dba.insert(table_name="table_name", {"id": 1, "something": "hello", "something2": "world"})
dba.insert(table_name="table_name", [{"id": 1, "something": "hello", "something2": "world"},
{"id": 2, "something": "hello2", "something2": "world2"}])
Deleting records - specify table name. Records can either be found by primary key or filter:
dba.delete("table_name", record, primary_key="id")
dba.delete("table_name", filter={"id": 1})
dba.delete("table_name", filter=[{"id": 1}, {"id": 2}])
Migration updates:
- record count added
- Database result object updated
Building the package without poetry - you will need to request an API key:
python3 -m pip install --upgrade build
python3 -m build
python3 -m pip install --upgrade twine
python3 -m twine upload dist/*
Building the package:
poetry build
Publishing the package:
poetry publish
PyTests
poetry run pytest ./tests
Flake8 Code tests
poetry run flake8 ./tina4_python