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.
Files changed (88) hide show
  1. {tina4_python-0.2.202 → tina4_python-0.2.203}/PKG-INFO +118 -23
  2. {tina4_python-0.2.202 → tina4_python-0.2.203}/README.md +117 -22
  3. {tina4_python-0.2.202 → tina4_python-0.2.203}/pyproject.toml +1 -1
  4. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Router.py +269 -76
  5. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/__init__.py +20 -35
  6. {tina4_python-0.2.202 → tina4_python-0.2.203}/.gitignore +0 -0
  7. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Api.py +0 -0
  8. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Auth.py +0 -0
  9. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/CLAUDE.md +0 -0
  10. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/CRUD.py +0 -0
  11. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Constant.py +0 -0
  12. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Database.py +0 -0
  13. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DatabaseResult.py +0 -0
  14. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DatabaseTypes.py +0 -0
  15. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Debug.py +0 -0
  16. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/DevReload.py +0 -0
  17. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Env.py +0 -0
  18. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/FieldTypes.py +0 -0
  19. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/GraphQL.py +0 -0
  20. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/HtmlElement.py +0 -0
  21. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Localization.py +0 -0
  22. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Messages.py +0 -0
  23. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/MiddleWare.py +0 -0
  24. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Migration.py +0 -0
  25. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/ORM.py +0 -0
  26. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Queue.py +0 -0
  27. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Request.py +0 -0
  28. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Response.py +0 -0
  29. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/SQLToMongo.py +0 -0
  30. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Seeder.py +0 -0
  31. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Session.py +0 -0
  32. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/ShellColors.py +0 -0
  33. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Swagger.py +0 -0
  34. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Template.py +0 -0
  35. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Testing.py +0 -0
  36. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/WSDL.py +0 -0
  37. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Webserver.py +0 -0
  38. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/Websocket.py +0 -0
  39. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/cli.py +0 -0
  40. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/messages.pot +0 -0
  41. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/readme.md +0 -0
  42. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/tina4.css +0 -0
  43. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/css/tina4.min.css +0 -0
  44. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/favicon.ico +0 -0
  45. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/403.png +0 -0
  46. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/404.png +0 -0
  47. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/500.png +0 -0
  48. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/logo.png +0 -0
  49. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/images/readme.md +0 -0
  50. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/readme.md +0 -0
  51. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  52. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/tina4.js +0 -0
  53. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/js/tina4helper.js +0 -0
  54. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/swagger/index.html +0 -0
  55. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  56. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  57. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_badges.scss +0 -0
  58. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  59. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_cards.scss +0 -0
  60. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_forms.scss +0 -0
  61. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_grid.scss +0 -0
  62. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_modals.scss +0 -0
  63. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_nav.scss +0 -0
  64. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_reset.scss +0 -0
  65. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_tables.scss +0 -0
  66. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_typography.scss +0 -0
  67. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  68. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_variables.scss +0 -0
  69. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/base.scss +0 -0
  70. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/colors.scss +0 -0
  71. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/scss/tina4css/tina4.scss +0 -0
  72. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/components/crud.twig +0 -0
  73. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/403.twig +0 -0
  74. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/404.twig +0 -0
  75. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/errors/500.twig +0 -0
  76. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/templates/readme.md +0 -0
  77. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  78. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  79. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  80. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  81. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  82. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  83. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  84. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  85. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  86. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  87. {tina4_python-0.2.202 → tina4_python-0.2.203}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  88. {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.202
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** — auto-generated RSA keys, bearer tokens, form tokens
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 for dependency management):
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("name", "Joe")
165
- name = request.session.get("name")
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
- print(msg.data)
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.get("/users/42")
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
- SERVICE_URL = "http://localhost:7145/calculator"
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 --verbose`.
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** — auto-generated RSA keys, bearer tokens, form tokens
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 for dependency management):
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("name", "Joe")
128
- name = request.session.get("name")
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
- print(msg.data)
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.get("/users/42")
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
- SERVICE_URL = "http://localhost:7145/calculator"
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 --verbose`.
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "0.2.202"
3
+ version = "0.2.203"
4
4
  description = "Tina4Python - This is not another framework for Python"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}