tina4-python 0.2.188__tar.gz → 0.2.190__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.190/PKG-INFO +264 -0
- tina4_python-0.2.190/README.md +243 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/pyproject.toml +1 -1
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Auth.py +2 -1
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/CLAUDE.md +300 -10
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/CRUD.py +8 -4
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Database.py +10 -8
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Localization.py +26 -24
- tina4_python-0.2.190/tina4_python/Messages.py +103 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Migration.py +7 -8
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Response.py +8 -5
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Router.py +3 -3
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Session.py +123 -2
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Template.py +5 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Webserver.py +4 -2
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Websocket.py +4 -2
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/cli.py +29 -29
- tina4_python-0.2.190/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/af/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.190/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/en/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.190/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/es/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.190/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/fr/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.190/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/ja/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.190/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.190/tina4_python/translations/zh/LC_MESSAGES/messages.po +233 -0
- tina4_python-0.2.188/PKG-INFO +0 -117
- tina4_python-0.2.188/README.md +0 -96
- tina4_python-0.2.188/tina4_python/Messages.py +0 -30
- tina4_python-0.2.188/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.188/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -80
- tina4_python-0.2.188/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- tina4_python-0.2.188/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -84
- {tina4_python-0.2.188 → tina4_python-0.2.190}/.gitignore +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Api.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/DatabaseResult.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/DatabaseTypes.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/DevReload.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/FieldTypes.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/ORM.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Queue.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Request.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/Testing.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/WSDL.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/__init__.py +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/js/reconnecting-websocket.js +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.188 → tina4_python-0.2.190}/tina4_python/templates/readme.md +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tina4-python
|
|
3
|
+
Version: 0.2.190
|
|
4
|
+
Summary: Tina4Python - This is not another framework for Python
|
|
5
|
+
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
|
+
Requires-Python: <4.0,>=3.12
|
|
7
|
+
Requires-Dist: asyncer>=0.0.8
|
|
8
|
+
Requires-Dist: bcrypt<5.0.0,>=4.2.1
|
|
9
|
+
Requires-Dist: cryptography<45.0.0,>=44.0.0
|
|
10
|
+
Requires-Dist: hypercorn>=0.18.0
|
|
11
|
+
Requires-Dist: jinja2<4.0.0,>=3.1.5
|
|
12
|
+
Requires-Dist: jurigged>=0.6.0
|
|
13
|
+
Requires-Dist: libsass<0.24.0,>=0.23.0
|
|
14
|
+
Requires-Dist: litequeue<0.10,>=0.9
|
|
15
|
+
Requires-Dist: pyjwt<3.0.0,>=2.10.1
|
|
16
|
+
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
17
|
+
Requires-Dist: requests>=2.32.5
|
|
18
|
+
Requires-Dist: simple-websocket<2.0.0,>=1.1.0
|
|
19
|
+
Requires-Dist: watchdog<7.0.0,>=6.0.0
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Tina4 Python — This is not a framework
|
|
23
|
+
|
|
24
|
+
Laravel joy. Python speed. 10x less code.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install tina4-python
|
|
30
|
+
tina4 init my_project
|
|
31
|
+
cd my_project
|
|
32
|
+
python app.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You've just built your first Tina4 app — zero configuration, zero classes, zero boilerplate!
|
|
36
|
+
|
|
37
|
+
> **Prefer uv?** Replace `pip install tina4-python` with `uv add tina4-python`, then use `uv run tina4 start` to launch the dev server.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **ASGI compliant** — works with any ASGI server (uvicorn, hypercorn, daphne)
|
|
42
|
+
- **Full async** — every route handler is `async` by default
|
|
43
|
+
- **Routing** — decorator-based with path parameters, type hints, and auto-discovery
|
|
44
|
+
- **Twig/Jinja2 templates** — with inheritance, partials, custom filters, and globals
|
|
45
|
+
- **ORM** — define models with typed fields, save/load/select/delete with one line
|
|
46
|
+
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
|
|
47
|
+
- **Migrations** — versioned SQL files, CLI scaffolding
|
|
48
|
+
- **Sessions** — file, Redis, Valkey, or MongoDB backends
|
|
49
|
+
- **JWT authentication** — auto-generated RSA keys, bearer tokens, form tokens
|
|
50
|
+
- **Swagger/OpenAPI** — auto-generated docs at `/swagger`
|
|
51
|
+
- **CRUD scaffolding** — instant admin UI with one line of code
|
|
52
|
+
- **Middleware** — before/after hooks per route or globally
|
|
53
|
+
- **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
|
|
54
|
+
- **WebSockets** — built-in support via `simple-websocket`
|
|
55
|
+
- **WSDL/SOAP** — auto-generated WSDL from Python classes
|
|
56
|
+
- **REST client** — built-in `Api` class for external HTTP calls
|
|
57
|
+
- **SCSS compilation** — auto-compiled to CSS on save
|
|
58
|
+
- **Live reload** — browser auto-refreshes during development
|
|
59
|
+
- **Inline testing** — decorator-based test cases with `@tests`
|
|
60
|
+
- **Localization** — i18n via gettext (English, French, Afrikaans)
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install tina4-python
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or with [uv](https://docs.astral.sh/uv/) (recommended for dependency management):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv add tina4-python
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Routing
|
|
75
|
+
|
|
76
|
+
Routes live in `src/routes/` and are auto-discovered on startup.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# src/routes/hello.py
|
|
80
|
+
from tina4_python import get
|
|
81
|
+
|
|
82
|
+
@get("/hello")
|
|
83
|
+
async def get_hello(request, response):
|
|
84
|
+
return response("Hello, Tina4 Python!")
|
|
85
|
+
|
|
86
|
+
@get("/hello/{name}")
|
|
87
|
+
async def get_hello_name(name, request, response):
|
|
88
|
+
return response(f"Hello, {name}")
|
|
89
|
+
|
|
90
|
+
@get("/hello/json")
|
|
91
|
+
async def get_hello_json(request, response):
|
|
92
|
+
return response([{"brand": "BMW"}, {"brand": "Toyota"}])
|
|
93
|
+
|
|
94
|
+
@get("/hello/template")
|
|
95
|
+
async def get_hello_template(request, response):
|
|
96
|
+
return response.render("index.twig", {"data": request.params})
|
|
97
|
+
|
|
98
|
+
@get("/hello/redirect")
|
|
99
|
+
async def get_hello_redirect(request, response):
|
|
100
|
+
return response.redirect("/hello/world")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## ORM
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# src/orm/User.py
|
|
107
|
+
from tina4_python import ORM, IntegerField, StringField
|
|
108
|
+
|
|
109
|
+
class User(ORM):
|
|
110
|
+
id = IntegerField(primary_key=True, auto_increment=True)
|
|
111
|
+
name = StringField()
|
|
112
|
+
email = StringField()
|
|
113
|
+
|
|
114
|
+
User({"name": "Alice", "email": "alice@example.com"}).save()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Database
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from tina4_python.Database import Database
|
|
121
|
+
|
|
122
|
+
db = Database("sqlite3:app.db")
|
|
123
|
+
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
|
|
127
|
+
|
|
128
|
+
## Migrations
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
tina4 migrate:create "create users table"
|
|
132
|
+
# Edit the generated SQL file in migrations/
|
|
133
|
+
tina4 migrate
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Sessions
|
|
137
|
+
|
|
138
|
+
Built-in session management with pluggable backends:
|
|
139
|
+
|
|
140
|
+
| Handler | Backend | Package |
|
|
141
|
+
|---------|---------|---------|
|
|
142
|
+
| `SessionFileHandler` (default) | File system | — |
|
|
143
|
+
| `SessionRedisHandler` | Redis | `redis` |
|
|
144
|
+
| `SessionValkeyHandler` | Valkey | `valkey` |
|
|
145
|
+
| `SessionMongoHandler` | MongoDB | `pymongo` |
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
request.session.set("name", "Joe")
|
|
149
|
+
name = request.session.get("name")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Queues
|
|
153
|
+
|
|
154
|
+
Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from tina4_python.Queue import Queue, Producer, Consumer
|
|
158
|
+
|
|
159
|
+
Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
|
|
160
|
+
|
|
161
|
+
for msg in Consumer(Queue(topic="emails")).messages():
|
|
162
|
+
print(msg.data)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Middleware
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from tina4_python.Router import get, middleware
|
|
169
|
+
|
|
170
|
+
class AuthCheck:
|
|
171
|
+
@staticmethod
|
|
172
|
+
def before_auth(request, response):
|
|
173
|
+
if "authorization" not in request.headers:
|
|
174
|
+
return request, response("Unauthorized", 401)
|
|
175
|
+
return request, response
|
|
176
|
+
|
|
177
|
+
@middleware(AuthCheck)
|
|
178
|
+
@get("/protected")
|
|
179
|
+
async def protected(request, response):
|
|
180
|
+
return response({"secret": True})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Swagger / OpenAPI
|
|
184
|
+
|
|
185
|
+
Auto-generated at `/swagger`. Add metadata with decorators:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from tina4_python import description, tags
|
|
189
|
+
|
|
190
|
+
@get("/users")
|
|
191
|
+
@description("Get all users")
|
|
192
|
+
@tags(["users"])
|
|
193
|
+
async def users(request, response):
|
|
194
|
+
return response(User().select("*"))
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## REST Client
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from tina4_python import Api
|
|
201
|
+
|
|
202
|
+
api = Api("https://api.example.com", auth_header="Bearer xyz")
|
|
203
|
+
result = api.get("/users/42")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## WSDL / SOAP
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from tina4_python.WSDL import WSDL, wsdl_operation
|
|
210
|
+
|
|
211
|
+
class Calculator(WSDL):
|
|
212
|
+
SERVICE_URL = "http://localhost:7145/calculator"
|
|
213
|
+
|
|
214
|
+
def Add(self, a: int, b: int):
|
|
215
|
+
return {"Result": a + b}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Testing
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from tina4_python import tests
|
|
222
|
+
|
|
223
|
+
@tests(
|
|
224
|
+
assert_equal((7, 7), 1),
|
|
225
|
+
assert_raises(ZeroDivisionError, (5, 0)),
|
|
226
|
+
)
|
|
227
|
+
def divide(a: int, b: int) -> float:
|
|
228
|
+
if b == 0:
|
|
229
|
+
raise ZeroDivisionError("division by zero")
|
|
230
|
+
return a / b
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Run with `tina4 test` or `uv run pytest --verbose`.
|
|
234
|
+
|
|
235
|
+
## Environment
|
|
236
|
+
|
|
237
|
+
Key `.env` settings:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
SECRET=your-jwt-secret
|
|
241
|
+
API_KEY=your-api-key
|
|
242
|
+
DATABASE_NAME=sqlite3:app.db
|
|
243
|
+
TINA4_DEBUG_LEVEL=ALL
|
|
244
|
+
TINA4_LANGUAGE=en
|
|
245
|
+
TINA4_SESSION_HANDLER=SessionFileHandler
|
|
246
|
+
SWAGGER_TITLE=My API
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Further Documentation
|
|
250
|
+
|
|
251
|
+
https://tina4.com/python
|
|
252
|
+
|
|
253
|
+
## Community
|
|
254
|
+
|
|
255
|
+
- GitHub: https://github.com/tina4stack/tina4-python
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT (c) 2007-2025 Tina4 Stack
|
|
260
|
+
https://opensource.org/licenses/MIT
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
**Tina4** — The framework that keeps out of the way of your coding.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Tina4 Python — This is not a framework
|
|
2
|
+
|
|
3
|
+
Laravel joy. Python speed. 10x less code.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install tina4-python
|
|
9
|
+
tina4 init my_project
|
|
10
|
+
cd my_project
|
|
11
|
+
python app.py
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You've just built your first Tina4 app — zero configuration, zero classes, zero boilerplate!
|
|
15
|
+
|
|
16
|
+
> **Prefer uv?** Replace `pip install tina4-python` with `uv add tina4-python`, then use `uv run tina4 start` to launch the dev server.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **ASGI compliant** — works with any ASGI server (uvicorn, hypercorn, daphne)
|
|
21
|
+
- **Full async** — every route handler is `async` by default
|
|
22
|
+
- **Routing** — decorator-based with path parameters, type hints, and auto-discovery
|
|
23
|
+
- **Twig/Jinja2 templates** — with inheritance, partials, custom filters, and globals
|
|
24
|
+
- **ORM** — define models with typed fields, save/load/select/delete with one line
|
|
25
|
+
- **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
|
|
26
|
+
- **Migrations** — versioned SQL files, CLI scaffolding
|
|
27
|
+
- **Sessions** — file, Redis, Valkey, or MongoDB backends
|
|
28
|
+
- **JWT authentication** — auto-generated RSA keys, bearer tokens, form tokens
|
|
29
|
+
- **Swagger/OpenAPI** — auto-generated docs at `/swagger`
|
|
30
|
+
- **CRUD scaffolding** — instant admin UI with one line of code
|
|
31
|
+
- **Middleware** — before/after hooks per route or globally
|
|
32
|
+
- **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
|
|
33
|
+
- **WebSockets** — built-in support via `simple-websocket`
|
|
34
|
+
- **WSDL/SOAP** — auto-generated WSDL from Python classes
|
|
35
|
+
- **REST client** — built-in `Api` class for external HTTP calls
|
|
36
|
+
- **SCSS compilation** — auto-compiled to CSS on save
|
|
37
|
+
- **Live reload** — browser auto-refreshes during development
|
|
38
|
+
- **Inline testing** — decorator-based test cases with `@tests`
|
|
39
|
+
- **Localization** — i18n via gettext (English, French, Afrikaans)
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install tina4-python
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or with [uv](https://docs.astral.sh/uv/) (recommended for dependency management):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv add tina4-python
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Routing
|
|
54
|
+
|
|
55
|
+
Routes live in `src/routes/` and are auto-discovered on startup.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# src/routes/hello.py
|
|
59
|
+
from tina4_python import get
|
|
60
|
+
|
|
61
|
+
@get("/hello")
|
|
62
|
+
async def get_hello(request, response):
|
|
63
|
+
return response("Hello, Tina4 Python!")
|
|
64
|
+
|
|
65
|
+
@get("/hello/{name}")
|
|
66
|
+
async def get_hello_name(name, request, response):
|
|
67
|
+
return response(f"Hello, {name}")
|
|
68
|
+
|
|
69
|
+
@get("/hello/json")
|
|
70
|
+
async def get_hello_json(request, response):
|
|
71
|
+
return response([{"brand": "BMW"}, {"brand": "Toyota"}])
|
|
72
|
+
|
|
73
|
+
@get("/hello/template")
|
|
74
|
+
async def get_hello_template(request, response):
|
|
75
|
+
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
|
+
```
|
|
81
|
+
|
|
82
|
+
## ORM
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# src/orm/User.py
|
|
86
|
+
from tina4_python import ORM, IntegerField, StringField
|
|
87
|
+
|
|
88
|
+
class User(ORM):
|
|
89
|
+
id = IntegerField(primary_key=True, auto_increment=True)
|
|
90
|
+
name = StringField()
|
|
91
|
+
email = StringField()
|
|
92
|
+
|
|
93
|
+
User({"name": "Alice", "email": "alice@example.com"}).save()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Database
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from tina4_python.Database import Database
|
|
100
|
+
|
|
101
|
+
db = Database("sqlite3:app.db")
|
|
102
|
+
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
|
|
106
|
+
|
|
107
|
+
## Migrations
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
tina4 migrate:create "create users table"
|
|
111
|
+
# Edit the generated SQL file in migrations/
|
|
112
|
+
tina4 migrate
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Sessions
|
|
116
|
+
|
|
117
|
+
Built-in session management with pluggable backends:
|
|
118
|
+
|
|
119
|
+
| Handler | Backend | Package |
|
|
120
|
+
|---------|---------|---------|
|
|
121
|
+
| `SessionFileHandler` (default) | File system | — |
|
|
122
|
+
| `SessionRedisHandler` | Redis | `redis` |
|
|
123
|
+
| `SessionValkeyHandler` | Valkey | `valkey` |
|
|
124
|
+
| `SessionMongoHandler` | MongoDB | `pymongo` |
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
request.session.set("name", "Joe")
|
|
128
|
+
name = request.session.get("name")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Queues
|
|
132
|
+
|
|
133
|
+
Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from tina4_python.Queue import Queue, Producer, Consumer
|
|
137
|
+
|
|
138
|
+
Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
|
|
139
|
+
|
|
140
|
+
for msg in Consumer(Queue(topic="emails")).messages():
|
|
141
|
+
print(msg.data)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Middleware
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from tina4_python.Router import get, middleware
|
|
148
|
+
|
|
149
|
+
class AuthCheck:
|
|
150
|
+
@staticmethod
|
|
151
|
+
def before_auth(request, response):
|
|
152
|
+
if "authorization" not in request.headers:
|
|
153
|
+
return request, response("Unauthorized", 401)
|
|
154
|
+
return request, response
|
|
155
|
+
|
|
156
|
+
@middleware(AuthCheck)
|
|
157
|
+
@get("/protected")
|
|
158
|
+
async def protected(request, response):
|
|
159
|
+
return response({"secret": True})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Swagger / OpenAPI
|
|
163
|
+
|
|
164
|
+
Auto-generated at `/swagger`. Add metadata with decorators:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from tina4_python import description, tags
|
|
168
|
+
|
|
169
|
+
@get("/users")
|
|
170
|
+
@description("Get all users")
|
|
171
|
+
@tags(["users"])
|
|
172
|
+
async def users(request, response):
|
|
173
|
+
return response(User().select("*"))
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## REST Client
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from tina4_python import Api
|
|
180
|
+
|
|
181
|
+
api = Api("https://api.example.com", auth_header="Bearer xyz")
|
|
182
|
+
result = api.get("/users/42")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## WSDL / SOAP
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from tina4_python.WSDL import WSDL, wsdl_operation
|
|
189
|
+
|
|
190
|
+
class Calculator(WSDL):
|
|
191
|
+
SERVICE_URL = "http://localhost:7145/calculator"
|
|
192
|
+
|
|
193
|
+
def Add(self, a: int, b: int):
|
|
194
|
+
return {"Result": a + b}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Testing
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from tina4_python import tests
|
|
201
|
+
|
|
202
|
+
@tests(
|
|
203
|
+
assert_equal((7, 7), 1),
|
|
204
|
+
assert_raises(ZeroDivisionError, (5, 0)),
|
|
205
|
+
)
|
|
206
|
+
def divide(a: int, b: int) -> float:
|
|
207
|
+
if b == 0:
|
|
208
|
+
raise ZeroDivisionError("division by zero")
|
|
209
|
+
return a / b
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Run with `tina4 test` or `uv run pytest --verbose`.
|
|
213
|
+
|
|
214
|
+
## Environment
|
|
215
|
+
|
|
216
|
+
Key `.env` settings:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
SECRET=your-jwt-secret
|
|
220
|
+
API_KEY=your-api-key
|
|
221
|
+
DATABASE_NAME=sqlite3:app.db
|
|
222
|
+
TINA4_DEBUG_LEVEL=ALL
|
|
223
|
+
TINA4_LANGUAGE=en
|
|
224
|
+
TINA4_SESSION_HANDLER=SessionFileHandler
|
|
225
|
+
SWAGGER_TITLE=My API
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Further Documentation
|
|
229
|
+
|
|
230
|
+
https://tina4.com/python
|
|
231
|
+
|
|
232
|
+
## Community
|
|
233
|
+
|
|
234
|
+
- GitHub: https://github.com/tina4stack/tina4-python
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT (c) 2007-2025 Tina4 Stack
|
|
239
|
+
https://opensource.org/licenses/MIT
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
**Tina4** — The framework that keeps out of the way of your coding.
|
|
@@ -181,7 +181,8 @@ class Auth:
|
|
|
181
181
|
self.secret = os.environ.get("SECRET", "{self.secret}")
|
|
182
182
|
if self.secret == "{self.secret}":
|
|
183
183
|
from tina4_python.Debug import Debug
|
|
184
|
-
|
|
184
|
+
from tina4_python import Messages
|
|
185
|
+
Debug.warning(Messages.MSG_AUTH_NO_SECRET)
|
|
185
186
|
self.private_key = os.path.join(root_path, "secrets", "private.key")
|
|
186
187
|
self.public_key = os.path.join(root_path, "secrets", "public.key")
|
|
187
188
|
self.self_signed = os.path.join(root_path, "secrets", "domain.cert")
|