Skip to content

tina4stack/tina4-python

Repository files navigation

Tina4Python - This is not a framework for Python

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

System Requirements

  • Install Poetry:
curl -sSL https://install.python-poetry.org | python3 - 
  • Install Jurigged (Enables Hot Reloading):
pip install jurigged

Quick Start

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 *

Overview

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

Installation

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

Windows

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

Usage

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

Templating

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>

Defining Routes

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:

Example of a post with a form, assume the route is /capture

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

Suppressing the default webservice

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

Features

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

Database

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 and Deployment

Using Python

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/*

Using Poetry

Building the package:

poetry build

Publishing the package:

poetry publish

Running tests & checks

PyTests

poetry run pytest ./tests

Flake8 Code tests

poetry run flake8 ./tina4_python