tina4-python 0.2.202__tar.gz → 0.2.203__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-0.2.202 → tina4_python-0.2.203}/PKG-INFO +118 -23
- {tina4_python-0.2.202 → tina4_python-0.2.203}/README.md +117 -22
- {tina4_python-0.2.202 → tina4_python-0.2.203}/pyproject.toml +1 -1
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Router.py +269 -76
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/__init__.py +20 -35
- {tina4_python-0.2.202 → tina4_python-0.2.203}/.gitignore +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Api.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Auth.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/CRUD.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Database.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DatabaseResult.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DatabaseTypes.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DevReload.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/FieldTypes.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/GraphQL.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Localization.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Messages.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Migration.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/ORM.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Queue.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Request.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Response.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/SQLToMongo.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Seeder.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Session.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Template.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Testing.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/WSDL.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Webserver.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Websocket.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/cli.py +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/reconnecting-websocket.js +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/tina4.js +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/readme.md +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.203
|
|
4
4
|
Summary: Tina4Python - This is not another framework for Python
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
Requires-Python: <4.0,>=3.12
|
|
@@ -58,13 +58,16 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
58
58
|
- **Full async** — every route handler is `async` by default
|
|
59
59
|
- **Routing** — decorator-based with path parameters, type hints, and auto-discovery
|
|
60
60
|
- **Twig/Jinja2 templates** — with inheritance, partials, custom filters, and globals
|
|
61
|
+
- **tina4-css** — lightweight CSS framework (~24 KB) ships built-in, Bootstrap-compatible class names
|
|
61
62
|
- **ORM** — define models with typed fields, save/load/select/delete with one line
|
|
62
|
-
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
|
|
63
|
+
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird, MongoDB
|
|
63
64
|
- **Migrations** — versioned SQL files, CLI scaffolding
|
|
65
|
+
- **Data seeder** — zero-dependency fake data generation with ORM and table support
|
|
64
66
|
- **Sessions** — file, Redis, Valkey, or MongoDB backends
|
|
65
|
-
- **JWT authentication** —
|
|
67
|
+
- **JWT authentication** — HS256 tokens signed with your `SECRET` env var, form tokens
|
|
66
68
|
- **Swagger/OpenAPI** — auto-generated docs at `/swagger`
|
|
67
|
-
- **CRUD scaffolding** — instant admin UI with one line of code
|
|
69
|
+
- **CRUD scaffolding** — instant searchable admin UI with one line of code
|
|
70
|
+
- **GraphQL** — zero-dependency engine with ORM auto-schema and GraphiQL IDE
|
|
68
71
|
- **Middleware** — before/after hooks per route or globally
|
|
69
72
|
- **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
|
|
70
73
|
- **WebSockets** — built-in support via `simple-websocket`
|
|
@@ -73,7 +76,7 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
73
76
|
- **SCSS compilation** — auto-compiled to CSS on save
|
|
74
77
|
- **Live reload** — browser auto-refreshes during development
|
|
75
78
|
- **Inline testing** — decorator-based test cases with `@tests`
|
|
76
|
-
- **Localization** — i18n via gettext (English, French, Afrikaans)
|
|
79
|
+
- **Localization** — i18n via gettext (English, French, Afrikaans, Chinese, Japanese, Spanish)
|
|
77
80
|
|
|
78
81
|
## Install
|
|
79
82
|
|
|
@@ -81,12 +84,26 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
81
84
|
pip install tina4-python
|
|
82
85
|
```
|
|
83
86
|
|
|
84
|
-
Or with [uv](https://docs.astral.sh/uv/) (recommended
|
|
87
|
+
Or with [uv](https://docs.astral.sh/uv/) (recommended):
|
|
85
88
|
|
|
86
89
|
```bash
|
|
87
90
|
uv add tina4-python
|
|
88
91
|
```
|
|
89
92
|
|
|
93
|
+
### Optional extras
|
|
94
|
+
|
|
95
|
+
Install only the database driver you need:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install tina4-python[postgres] # PostgreSQL (psycopg2-binary)
|
|
99
|
+
pip install tina4-python[mysql] # MySQL / MariaDB (mysql-connector-python)
|
|
100
|
+
pip install tina4-python[mssql] # Microsoft SQL Server (pymssql)
|
|
101
|
+
pip install tina4-python[firebird] # Firebird (firebird-driver)
|
|
102
|
+
pip install tina4-python[mongo] # MongoDB (pymongo)
|
|
103
|
+
pip install tina4-python[all-db] # all of the above
|
|
104
|
+
pip install tina4-python[dev-reload] # hot-patching via jurigged
|
|
105
|
+
```
|
|
106
|
+
|
|
90
107
|
## Routing
|
|
91
108
|
|
|
92
109
|
Routes live in `src/routes/` and are auto-discovered on startup.
|
|
@@ -101,7 +118,7 @@ async def get_hello(request, response):
|
|
|
101
118
|
|
|
102
119
|
@get("/hello/{name}")
|
|
103
120
|
async def get_hello_name(name, request, response):
|
|
104
|
-
return response(f"Hello, {name}")
|
|
121
|
+
return response(f"Hello, {name}!")
|
|
105
122
|
|
|
106
123
|
@get("/hello/json")
|
|
107
124
|
async def get_hello_json(request, response):
|
|
@@ -110,10 +127,6 @@ async def get_hello_json(request, response):
|
|
|
110
127
|
@get("/hello/template")
|
|
111
128
|
async def get_hello_template(request, response):
|
|
112
129
|
return response.render("index.twig", {"data": request.params})
|
|
113
|
-
|
|
114
|
-
@get("/hello/redirect")
|
|
115
|
-
async def get_hello_redirect(request, response):
|
|
116
|
-
return response.redirect("/hello/world")
|
|
117
130
|
```
|
|
118
131
|
|
|
119
132
|
## ORM
|
|
@@ -136,11 +149,13 @@ User({"name": "Alice", "email": "alice@example.com"}).save()
|
|
|
136
149
|
from tina4_python.Database import Database
|
|
137
150
|
|
|
138
151
|
db = Database("sqlite3:app.db")
|
|
152
|
+
db = Database("psycopg2:localhost/5432:mydb", "user", "password") # PostgreSQL
|
|
153
|
+
db = Database("mysql.connector:localhost/3306:mydb", "user", "password") # MySQL
|
|
154
|
+
db = Database("pymongo:localhost/27017:mydb") # MongoDB
|
|
155
|
+
|
|
139
156
|
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
|
|
140
157
|
```
|
|
141
158
|
|
|
142
|
-
Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
|
|
143
|
-
|
|
144
159
|
## Migrations
|
|
145
160
|
|
|
146
161
|
```bash
|
|
@@ -149,6 +164,48 @@ tina4 migrate:create "create users table"
|
|
|
149
164
|
tina4 migrate
|
|
150
165
|
```
|
|
151
166
|
|
|
167
|
+
## Data Seeder
|
|
168
|
+
|
|
169
|
+
Generate fake data for development and testing:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from tina4_python.Seeder import FakeData, seed_orm, seed_table
|
|
173
|
+
|
|
174
|
+
fake = FakeData()
|
|
175
|
+
fake.name() # "Alice Johnson"
|
|
176
|
+
fake.email() # "alice.johnson@example.com"
|
|
177
|
+
fake.phone() # "+27 82 123 4567"
|
|
178
|
+
|
|
179
|
+
# Seed an ORM model
|
|
180
|
+
seed_orm(User, count=50)
|
|
181
|
+
|
|
182
|
+
# Seed a raw table
|
|
183
|
+
seed_table(db, "products", columns, count=100)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
CLI:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
tina4 seed # run all files in src/seeds/
|
|
190
|
+
tina4 seed:create "initial users" # scaffold a new seed file
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## CRUD Scaffolding
|
|
194
|
+
|
|
195
|
+
Generate a searchable, paginated admin UI with one call:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from tina4_python.CRUD import CRUD
|
|
199
|
+
|
|
200
|
+
@get("/admin/users")
|
|
201
|
+
async def admin_users(request, response):
|
|
202
|
+
return response(CRUD.to_crud(request, {
|
|
203
|
+
"sql": "SELECT id, name, email FROM users",
|
|
204
|
+
"title": "User Management",
|
|
205
|
+
"primary_key": "id",
|
|
206
|
+
}))
|
|
207
|
+
```
|
|
208
|
+
|
|
152
209
|
## Sessions
|
|
153
210
|
|
|
154
211
|
Built-in session management with pluggable backends:
|
|
@@ -161,8 +218,26 @@ Built-in session management with pluggable backends:
|
|
|
161
218
|
| `SessionMongoHandler` | MongoDB | `pymongo` |
|
|
162
219
|
|
|
163
220
|
```python
|
|
164
|
-
request.session.set("
|
|
165
|
-
|
|
221
|
+
request.session.set("user_id", 42)
|
|
222
|
+
user_id = request.session.get("user_id")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## JWT Authentication
|
|
226
|
+
|
|
227
|
+
Tokens are signed with HS256 using your `SECRET` env var. Set it in `.env`:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
SECRET=your-strong-random-secret-32-chars-min
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from tina4_python import tina4_auth
|
|
235
|
+
|
|
236
|
+
token = tina4_auth.get_token({"user_id": 42})
|
|
237
|
+
is_valid = tina4_auth.valid(token)
|
|
238
|
+
payload = tina4_auth.get_payload(token)
|
|
239
|
+
hashed = tina4_auth.hash_password("mypassword")
|
|
240
|
+
ok = tina4_auth.check_password(hashed, "mypassword")
|
|
166
241
|
```
|
|
167
242
|
|
|
168
243
|
## Queues
|
|
@@ -172,10 +247,25 @@ Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
|
|
|
172
247
|
```python
|
|
173
248
|
from tina4_python.Queue import Queue, Producer, Consumer
|
|
174
249
|
|
|
250
|
+
# Enqueue from a route
|
|
175
251
|
Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
|
|
176
252
|
|
|
253
|
+
# Process in a worker
|
|
177
254
|
for msg in Consumer(Queue(topic="emails")).messages():
|
|
178
|
-
|
|
255
|
+
send_email(msg.data)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## GraphQL
|
|
259
|
+
|
|
260
|
+
Zero-dependency GraphQL engine with ORM auto-schema:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from tina4_python.GraphQL import GraphQL
|
|
264
|
+
|
|
265
|
+
gql = GraphQL()
|
|
266
|
+
gql.schema.from_orm(User) # auto-generates type, queries, and mutations
|
|
267
|
+
gql.schema.from_orm(Product)
|
|
268
|
+
gql.register_route("/graphql") # GET = GraphiQL IDE, POST = queries
|
|
179
269
|
```
|
|
180
270
|
|
|
181
271
|
## Middleware
|
|
@@ -216,25 +306,30 @@ async def users(request, response):
|
|
|
216
306
|
from tina4_python import Api
|
|
217
307
|
|
|
218
308
|
api = Api("https://api.example.com", auth_header="Bearer xyz")
|
|
219
|
-
result = api.
|
|
309
|
+
result = api.send_request("/users/42")
|
|
220
310
|
```
|
|
221
311
|
|
|
222
312
|
## WSDL / SOAP
|
|
223
313
|
|
|
224
314
|
```python
|
|
225
315
|
from tina4_python.WSDL import WSDL, wsdl_operation
|
|
316
|
+
from tina4_python import get, post
|
|
226
317
|
|
|
227
318
|
class Calculator(WSDL):
|
|
228
|
-
|
|
229
|
-
|
|
319
|
+
@wsdl_operation({"Result": int})
|
|
230
320
|
def Add(self, a: int, b: int):
|
|
231
321
|
return {"Result": a + b}
|
|
322
|
+
|
|
323
|
+
@get("/calculator")
|
|
324
|
+
@post("/calculator")
|
|
325
|
+
async def calculator(request, response):
|
|
326
|
+
return response(Calculator(request).handle())
|
|
232
327
|
```
|
|
233
328
|
|
|
234
|
-
## Testing
|
|
329
|
+
## Inline Testing
|
|
235
330
|
|
|
236
331
|
```python
|
|
237
|
-
from tina4_python import tests
|
|
332
|
+
from tina4_python import tests, assert_equal, assert_raises
|
|
238
333
|
|
|
239
334
|
@tests(
|
|
240
335
|
assert_equal((7, 7), 1),
|
|
@@ -246,14 +341,14 @@ def divide(a: int, b: int) -> float:
|
|
|
246
341
|
return a / b
|
|
247
342
|
```
|
|
248
343
|
|
|
249
|
-
Run with `tina4 test` or `uv run pytest
|
|
344
|
+
Run with `tina4 test` or `uv run pytest`.
|
|
250
345
|
|
|
251
346
|
## Environment
|
|
252
347
|
|
|
253
348
|
Key `.env` settings:
|
|
254
349
|
|
|
255
350
|
```bash
|
|
256
|
-
SECRET=your-jwt-secret
|
|
351
|
+
SECRET=your-jwt-secret-32-chars-min
|
|
257
352
|
API_KEY=your-api-key
|
|
258
353
|
DATABASE_NAME=sqlite3:app.db
|
|
259
354
|
TINA4_DEBUG_LEVEL=ALL
|
|
@@ -21,13 +21,16 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
21
21
|
- **Full async** — every route handler is `async` by default
|
|
22
22
|
- **Routing** — decorator-based with path parameters, type hints, and auto-discovery
|
|
23
23
|
- **Twig/Jinja2 templates** — with inheritance, partials, custom filters, and globals
|
|
24
|
+
- **tina4-css** — lightweight CSS framework (~24 KB) ships built-in, Bootstrap-compatible class names
|
|
24
25
|
- **ORM** — define models with typed fields, save/load/select/delete with one line
|
|
25
|
-
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
|
|
26
|
+
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird, MongoDB
|
|
26
27
|
- **Migrations** — versioned SQL files, CLI scaffolding
|
|
28
|
+
- **Data seeder** — zero-dependency fake data generation with ORM and table support
|
|
27
29
|
- **Sessions** — file, Redis, Valkey, or MongoDB backends
|
|
28
|
-
- **JWT authentication** —
|
|
30
|
+
- **JWT authentication** — HS256 tokens signed with your `SECRET` env var, form tokens
|
|
29
31
|
- **Swagger/OpenAPI** — auto-generated docs at `/swagger`
|
|
30
|
-
- **CRUD scaffolding** — instant admin UI with one line of code
|
|
32
|
+
- **CRUD scaffolding** — instant searchable admin UI with one line of code
|
|
33
|
+
- **GraphQL** — zero-dependency engine with ORM auto-schema and GraphiQL IDE
|
|
31
34
|
- **Middleware** — before/after hooks per route or globally
|
|
32
35
|
- **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
|
|
33
36
|
- **WebSockets** — built-in support via `simple-websocket`
|
|
@@ -36,7 +39,7 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
36
39
|
- **SCSS compilation** — auto-compiled to CSS on save
|
|
37
40
|
- **Live reload** — browser auto-refreshes during development
|
|
38
41
|
- **Inline testing** — decorator-based test cases with `@tests`
|
|
39
|
-
- **Localization** — i18n via gettext (English, French, Afrikaans)
|
|
42
|
+
- **Localization** — i18n via gettext (English, French, Afrikaans, Chinese, Japanese, Spanish)
|
|
40
43
|
|
|
41
44
|
## Install
|
|
42
45
|
|
|
@@ -44,12 +47,26 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
|
|
|
44
47
|
pip install tina4-python
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
Or with [uv](https://docs.astral.sh/uv/) (recommended
|
|
50
|
+
Or with [uv](https://docs.astral.sh/uv/) (recommended):
|
|
48
51
|
|
|
49
52
|
```bash
|
|
50
53
|
uv add tina4-python
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
### Optional extras
|
|
57
|
+
|
|
58
|
+
Install only the database driver you need:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install tina4-python[postgres] # PostgreSQL (psycopg2-binary)
|
|
62
|
+
pip install tina4-python[mysql] # MySQL / MariaDB (mysql-connector-python)
|
|
63
|
+
pip install tina4-python[mssql] # Microsoft SQL Server (pymssql)
|
|
64
|
+
pip install tina4-python[firebird] # Firebird (firebird-driver)
|
|
65
|
+
pip install tina4-python[mongo] # MongoDB (pymongo)
|
|
66
|
+
pip install tina4-python[all-db] # all of the above
|
|
67
|
+
pip install tina4-python[dev-reload] # hot-patching via jurigged
|
|
68
|
+
```
|
|
69
|
+
|
|
53
70
|
## Routing
|
|
54
71
|
|
|
55
72
|
Routes live in `src/routes/` and are auto-discovered on startup.
|
|
@@ -64,7 +81,7 @@ async def get_hello(request, response):
|
|
|
64
81
|
|
|
65
82
|
@get("/hello/{name}")
|
|
66
83
|
async def get_hello_name(name, request, response):
|
|
67
|
-
return response(f"Hello, {name}")
|
|
84
|
+
return response(f"Hello, {name}!")
|
|
68
85
|
|
|
69
86
|
@get("/hello/json")
|
|
70
87
|
async def get_hello_json(request, response):
|
|
@@ -73,10 +90,6 @@ async def get_hello_json(request, response):
|
|
|
73
90
|
@get("/hello/template")
|
|
74
91
|
async def get_hello_template(request, response):
|
|
75
92
|
return response.render("index.twig", {"data": request.params})
|
|
76
|
-
|
|
77
|
-
@get("/hello/redirect")
|
|
78
|
-
async def get_hello_redirect(request, response):
|
|
79
|
-
return response.redirect("/hello/world")
|
|
80
93
|
```
|
|
81
94
|
|
|
82
95
|
## ORM
|
|
@@ -99,11 +112,13 @@ User({"name": "Alice", "email": "alice@example.com"}).save()
|
|
|
99
112
|
from tina4_python.Database import Database
|
|
100
113
|
|
|
101
114
|
db = Database("sqlite3:app.db")
|
|
115
|
+
db = Database("psycopg2:localhost/5432:mydb", "user", "password") # PostgreSQL
|
|
116
|
+
db = Database("mysql.connector:localhost/3306:mydb", "user", "password") # MySQL
|
|
117
|
+
db = Database("pymongo:localhost/27017:mydb") # MongoDB
|
|
118
|
+
|
|
102
119
|
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
|
|
103
120
|
```
|
|
104
121
|
|
|
105
|
-
Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
|
|
106
|
-
|
|
107
122
|
## Migrations
|
|
108
123
|
|
|
109
124
|
```bash
|
|
@@ -112,6 +127,48 @@ tina4 migrate:create "create users table"
|
|
|
112
127
|
tina4 migrate
|
|
113
128
|
```
|
|
114
129
|
|
|
130
|
+
## Data Seeder
|
|
131
|
+
|
|
132
|
+
Generate fake data for development and testing:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from tina4_python.Seeder import FakeData, seed_orm, seed_table
|
|
136
|
+
|
|
137
|
+
fake = FakeData()
|
|
138
|
+
fake.name() # "Alice Johnson"
|
|
139
|
+
fake.email() # "alice.johnson@example.com"
|
|
140
|
+
fake.phone() # "+27 82 123 4567"
|
|
141
|
+
|
|
142
|
+
# Seed an ORM model
|
|
143
|
+
seed_orm(User, count=50)
|
|
144
|
+
|
|
145
|
+
# Seed a raw table
|
|
146
|
+
seed_table(db, "products", columns, count=100)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
CLI:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
tina4 seed # run all files in src/seeds/
|
|
153
|
+
tina4 seed:create "initial users" # scaffold a new seed file
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## CRUD Scaffolding
|
|
157
|
+
|
|
158
|
+
Generate a searchable, paginated admin UI with one call:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from tina4_python.CRUD import CRUD
|
|
162
|
+
|
|
163
|
+
@get("/admin/users")
|
|
164
|
+
async def admin_users(request, response):
|
|
165
|
+
return response(CRUD.to_crud(request, {
|
|
166
|
+
"sql": "SELECT id, name, email FROM users",
|
|
167
|
+
"title": "User Management",
|
|
168
|
+
"primary_key": "id",
|
|
169
|
+
}))
|
|
170
|
+
```
|
|
171
|
+
|
|
115
172
|
## Sessions
|
|
116
173
|
|
|
117
174
|
Built-in session management with pluggable backends:
|
|
@@ -124,8 +181,26 @@ Built-in session management with pluggable backends:
|
|
|
124
181
|
| `SessionMongoHandler` | MongoDB | `pymongo` |
|
|
125
182
|
|
|
126
183
|
```python
|
|
127
|
-
request.session.set("
|
|
128
|
-
|
|
184
|
+
request.session.set("user_id", 42)
|
|
185
|
+
user_id = request.session.get("user_id")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## JWT Authentication
|
|
189
|
+
|
|
190
|
+
Tokens are signed with HS256 using your `SECRET` env var. Set it in `.env`:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
SECRET=your-strong-random-secret-32-chars-min
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from tina4_python import tina4_auth
|
|
198
|
+
|
|
199
|
+
token = tina4_auth.get_token({"user_id": 42})
|
|
200
|
+
is_valid = tina4_auth.valid(token)
|
|
201
|
+
payload = tina4_auth.get_payload(token)
|
|
202
|
+
hashed = tina4_auth.hash_password("mypassword")
|
|
203
|
+
ok = tina4_auth.check_password(hashed, "mypassword")
|
|
129
204
|
```
|
|
130
205
|
|
|
131
206
|
## Queues
|
|
@@ -135,10 +210,25 @@ Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
|
|
|
135
210
|
```python
|
|
136
211
|
from tina4_python.Queue import Queue, Producer, Consumer
|
|
137
212
|
|
|
213
|
+
# Enqueue from a route
|
|
138
214
|
Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
|
|
139
215
|
|
|
216
|
+
# Process in a worker
|
|
140
217
|
for msg in Consumer(Queue(topic="emails")).messages():
|
|
141
|
-
|
|
218
|
+
send_email(msg.data)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## GraphQL
|
|
222
|
+
|
|
223
|
+
Zero-dependency GraphQL engine with ORM auto-schema:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from tina4_python.GraphQL import GraphQL
|
|
227
|
+
|
|
228
|
+
gql = GraphQL()
|
|
229
|
+
gql.schema.from_orm(User) # auto-generates type, queries, and mutations
|
|
230
|
+
gql.schema.from_orm(Product)
|
|
231
|
+
gql.register_route("/graphql") # GET = GraphiQL IDE, POST = queries
|
|
142
232
|
```
|
|
143
233
|
|
|
144
234
|
## Middleware
|
|
@@ -179,25 +269,30 @@ async def users(request, response):
|
|
|
179
269
|
from tina4_python import Api
|
|
180
270
|
|
|
181
271
|
api = Api("https://api.example.com", auth_header="Bearer xyz")
|
|
182
|
-
result = api.
|
|
272
|
+
result = api.send_request("/users/42")
|
|
183
273
|
```
|
|
184
274
|
|
|
185
275
|
## WSDL / SOAP
|
|
186
276
|
|
|
187
277
|
```python
|
|
188
278
|
from tina4_python.WSDL import WSDL, wsdl_operation
|
|
279
|
+
from tina4_python import get, post
|
|
189
280
|
|
|
190
281
|
class Calculator(WSDL):
|
|
191
|
-
|
|
192
|
-
|
|
282
|
+
@wsdl_operation({"Result": int})
|
|
193
283
|
def Add(self, a: int, b: int):
|
|
194
284
|
return {"Result": a + b}
|
|
285
|
+
|
|
286
|
+
@get("/calculator")
|
|
287
|
+
@post("/calculator")
|
|
288
|
+
async def calculator(request, response):
|
|
289
|
+
return response(Calculator(request).handle())
|
|
195
290
|
```
|
|
196
291
|
|
|
197
|
-
## Testing
|
|
292
|
+
## Inline Testing
|
|
198
293
|
|
|
199
294
|
```python
|
|
200
|
-
from tina4_python import tests
|
|
295
|
+
from tina4_python import tests, assert_equal, assert_raises
|
|
201
296
|
|
|
202
297
|
@tests(
|
|
203
298
|
assert_equal((7, 7), 1),
|
|
@@ -209,14 +304,14 @@ def divide(a: int, b: int) -> float:
|
|
|
209
304
|
return a / b
|
|
210
305
|
```
|
|
211
306
|
|
|
212
|
-
Run with `tina4 test` or `uv run pytest
|
|
307
|
+
Run with `tina4 test` or `uv run pytest`.
|
|
213
308
|
|
|
214
309
|
## Environment
|
|
215
310
|
|
|
216
311
|
Key `.env` settings:
|
|
217
312
|
|
|
218
313
|
```bash
|
|
219
|
-
SECRET=your-jwt-secret
|
|
314
|
+
SECRET=your-jwt-secret-32-chars-min
|
|
220
315
|
API_KEY=your-api-key
|
|
221
316
|
DATABASE_NAME=sqlite3:app.db
|
|
222
317
|
TINA4_DEBUG_LEVEL=ALL
|