tina4-python 0.2.201__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 (89) hide show
  1. tina4_python-0.2.201/README.md → tina4_python-0.2.203/PKG-INFO +154 -22
  2. tina4_python-0.2.201/PKG-INFO → tina4_python-0.2.203/README.md +117 -43
  3. {tina4_python-0.2.201 → tina4_python-0.2.203}/pyproject.toml +18 -4
  4. tina4_python-0.2.203/tina4_python/Auth.py +236 -0
  5. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Messages.py +1 -0
  6. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Router.py +269 -76
  7. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/__init__.py +20 -35
  8. tina4_python-0.2.201/tina4_python/Auth.py +0 -354
  9. {tina4_python-0.2.201 → tina4_python-0.2.203}/.gitignore +0 -0
  10. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Api.py +0 -0
  11. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/CLAUDE.md +0 -0
  12. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/CRUD.py +0 -0
  13. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Constant.py +0 -0
  14. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Database.py +0 -0
  15. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/DatabaseResult.py +0 -0
  16. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/DatabaseTypes.py +0 -0
  17. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Debug.py +0 -0
  18. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/DevReload.py +0 -0
  19. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Env.py +0 -0
  20. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/FieldTypes.py +0 -0
  21. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/GraphQL.py +0 -0
  22. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/HtmlElement.py +0 -0
  23. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Localization.py +0 -0
  24. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/MiddleWare.py +0 -0
  25. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Migration.py +0 -0
  26. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/ORM.py +0 -0
  27. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Queue.py +0 -0
  28. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Request.py +0 -0
  29. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Response.py +0 -0
  30. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/SQLToMongo.py +0 -0
  31. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Seeder.py +0 -0
  32. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Session.py +0 -0
  33. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/ShellColors.py +0 -0
  34. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Swagger.py +0 -0
  35. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Template.py +0 -0
  36. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Testing.py +0 -0
  37. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/WSDL.py +0 -0
  38. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Webserver.py +0 -0
  39. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/Websocket.py +0 -0
  40. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/cli.py +0 -0
  41. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/messages.pot +0 -0
  42. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/css/readme.md +0 -0
  43. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/css/tina4.css +0 -0
  44. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/css/tina4.min.css +0 -0
  45. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/favicon.ico +0 -0
  46. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/images/403.png +0 -0
  47. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/images/404.png +0 -0
  48. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/images/500.png +0 -0
  49. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/images/logo.png +0 -0
  50. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/images/readme.md +0 -0
  51. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/js/readme.md +0 -0
  52. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  53. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/js/tina4.js +0 -0
  54. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/js/tina4helper.js +0 -0
  55. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/swagger/index.html +0 -0
  56. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  57. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  58. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_badges.scss +0 -0
  59. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  60. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_cards.scss +0 -0
  61. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_forms.scss +0 -0
  62. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_grid.scss +0 -0
  63. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_modals.scss +0 -0
  64. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_nav.scss +0 -0
  65. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_reset.scss +0 -0
  66. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_tables.scss +0 -0
  67. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_typography.scss +0 -0
  68. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  69. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/_variables.scss +0 -0
  70. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/base.scss +0 -0
  71. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/colors.scss +0 -0
  72. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/scss/tina4css/tina4.scss +0 -0
  73. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/templates/components/crud.twig +0 -0
  74. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/templates/errors/403.twig +0 -0
  75. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/templates/errors/404.twig +0 -0
  76. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/templates/errors/500.twig +0 -0
  77. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/templates/readme.md +0 -0
  78. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  79. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  80. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  81. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  82. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  83. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  84. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  85. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  86. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  87. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  88. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  89. {tina4_python-0.2.201 → tina4_python-0.2.203}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
@@ -1,3 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: tina4-python
3
+ Version: 0.2.203
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: hypercorn>=0.18.0
10
+ Requires-Dist: jinja2<4.0.0,>=3.1.5
11
+ Requires-Dist: libsass<0.24.0,>=0.23.0
12
+ Requires-Dist: litequeue<0.10,>=0.9
13
+ Requires-Dist: pyjwt<3.0.0,>=2.10.1
14
+ Requires-Dist: python-dotenv<2.0.0,>=1.0.1
15
+ Requires-Dist: requests>=2.32.5
16
+ Requires-Dist: simple-websocket<2.0.0,>=1.1.0
17
+ Requires-Dist: watchdog<7.0.0,>=6.0.0
18
+ Provides-Extra: all-db
19
+ Requires-Dist: firebird-driver>=1.10.0; extra == 'all-db'
20
+ Requires-Dist: mysql-connector-python>=9.3.0; extra == 'all-db'
21
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == 'all-db'
22
+ Requires-Dist: pymongo>=4.0.0; extra == 'all-db'
23
+ Requires-Dist: pymssql>=2.3.0; extra == 'all-db'
24
+ Provides-Extra: dev-reload
25
+ Requires-Dist: jurigged>=0.6.0; extra == 'dev-reload'
26
+ Provides-Extra: firebird
27
+ Requires-Dist: firebird-driver>=1.10.0; extra == 'firebird'
28
+ Provides-Extra: mongo
29
+ Requires-Dist: pymongo>=4.0.0; extra == 'mongo'
30
+ Provides-Extra: mssql
31
+ Requires-Dist: pymssql>=2.3.0; extra == 'mssql'
32
+ Provides-Extra: mysql
33
+ Requires-Dist: mysql-connector-python>=9.3.0; extra == 'mysql'
34
+ Provides-Extra: postgres
35
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == 'postgres'
36
+ Description-Content-Type: text/markdown
37
+
1
38
  # Tina4 Python — This is not a framework
2
39
 
3
40
  Laravel joy. Python speed. 10x less code.
@@ -21,13 +58,16 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
21
58
  - **Full async** — every route handler is `async` by default
22
59
  - **Routing** — decorator-based with path parameters, type hints, and auto-discovery
23
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
24
62
  - **ORM** — define models with typed fields, save/load/select/delete with one line
25
- - **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
63
+ - **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird, MongoDB
26
64
  - **Migrations** — versioned SQL files, CLI scaffolding
65
+ - **Data seeder** — zero-dependency fake data generation with ORM and table support
27
66
  - **Sessions** — file, Redis, Valkey, or MongoDB backends
28
- - **JWT authentication** — auto-generated RSA keys, bearer tokens, form tokens
67
+ - **JWT authentication** — HS256 tokens signed with your `SECRET` env var, form tokens
29
68
  - **Swagger/OpenAPI** — auto-generated docs at `/swagger`
30
- - **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
31
71
  - **Middleware** — before/after hooks per route or globally
32
72
  - **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
33
73
  - **WebSockets** — built-in support via `simple-websocket`
@@ -36,7 +76,7 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
36
76
  - **SCSS compilation** — auto-compiled to CSS on save
37
77
  - **Live reload** — browser auto-refreshes during development
38
78
  - **Inline testing** — decorator-based test cases with `@tests`
39
- - **Localization** — i18n via gettext (English, French, Afrikaans)
79
+ - **Localization** — i18n via gettext (English, French, Afrikaans, Chinese, Japanese, Spanish)
40
80
 
41
81
  ## Install
42
82
 
@@ -44,12 +84,26 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
44
84
  pip install tina4-python
45
85
  ```
46
86
 
47
- Or with [uv](https://docs.astral.sh/uv/) (recommended for dependency management):
87
+ Or with [uv](https://docs.astral.sh/uv/) (recommended):
48
88
 
49
89
  ```bash
50
90
  uv add tina4-python
51
91
  ```
52
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
+
53
107
  ## Routing
54
108
 
55
109
  Routes live in `src/routes/` and are auto-discovered on startup.
@@ -64,7 +118,7 @@ async def get_hello(request, response):
64
118
 
65
119
  @get("/hello/{name}")
66
120
  async def get_hello_name(name, request, response):
67
- return response(f"Hello, {name}")
121
+ return response(f"Hello, {name}!")
68
122
 
69
123
  @get("/hello/json")
70
124
  async def get_hello_json(request, response):
@@ -73,10 +127,6 @@ async def get_hello_json(request, response):
73
127
  @get("/hello/template")
74
128
  async def get_hello_template(request, response):
75
129
  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
130
  ```
81
131
 
82
132
  ## ORM
@@ -99,11 +149,13 @@ User({"name": "Alice", "email": "alice@example.com"}).save()
99
149
  from tina4_python.Database import Database
100
150
 
101
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
+
102
156
  result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
103
157
  ```
104
158
 
105
- Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
106
-
107
159
  ## Migrations
108
160
 
109
161
  ```bash
@@ -112,6 +164,48 @@ tina4 migrate:create "create users table"
112
164
  tina4 migrate
113
165
  ```
114
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
+
115
209
  ## Sessions
116
210
 
117
211
  Built-in session management with pluggable backends:
@@ -124,8 +218,26 @@ Built-in session management with pluggable backends:
124
218
  | `SessionMongoHandler` | MongoDB | `pymongo` |
125
219
 
126
220
  ```python
127
- request.session.set("name", "Joe")
128
- 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")
129
241
  ```
130
242
 
131
243
  ## Queues
@@ -135,10 +247,25 @@ Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
135
247
  ```python
136
248
  from tina4_python.Queue import Queue, Producer, Consumer
137
249
 
250
+ # Enqueue from a route
138
251
  Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
139
252
 
253
+ # Process in a worker
140
254
  for msg in Consumer(Queue(topic="emails")).messages():
141
- 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
142
269
  ```
143
270
 
144
271
  ## Middleware
@@ -179,25 +306,30 @@ async def users(request, response):
179
306
  from tina4_python import Api
180
307
 
181
308
  api = Api("https://api.example.com", auth_header="Bearer xyz")
182
- result = api.get("/users/42")
309
+ result = api.send_request("/users/42")
183
310
  ```
184
311
 
185
312
  ## WSDL / SOAP
186
313
 
187
314
  ```python
188
315
  from tina4_python.WSDL import WSDL, wsdl_operation
316
+ from tina4_python import get, post
189
317
 
190
318
  class Calculator(WSDL):
191
- SERVICE_URL = "http://localhost:7145/calculator"
192
-
319
+ @wsdl_operation({"Result": int})
193
320
  def Add(self, a: int, b: int):
194
321
  return {"Result": a + b}
322
+
323
+ @get("/calculator")
324
+ @post("/calculator")
325
+ async def calculator(request, response):
326
+ return response(Calculator(request).handle())
195
327
  ```
196
328
 
197
- ## Testing
329
+ ## Inline Testing
198
330
 
199
331
  ```python
200
- from tina4_python import tests
332
+ from tina4_python import tests, assert_equal, assert_raises
201
333
 
202
334
  @tests(
203
335
  assert_equal((7, 7), 1),
@@ -209,14 +341,14 @@ def divide(a: int, b: int) -> float:
209
341
  return a / b
210
342
  ```
211
343
 
212
- Run with `tina4 test` or `uv run pytest --verbose`.
344
+ Run with `tina4 test` or `uv run pytest`.
213
345
 
214
346
  ## Environment
215
347
 
216
348
  Key `.env` settings:
217
349
 
218
350
  ```bash
219
- SECRET=your-jwt-secret
351
+ SECRET=your-jwt-secret-32-chars-min
220
352
  API_KEY=your-api-key
221
353
  DATABASE_NAME=sqlite3:app.db
222
354
  TINA4_DEBUG_LEVEL=ALL
@@ -1,24 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tina4-python
3
- Version: 0.2.201
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
1
  # Tina4 Python — This is not a framework
23
2
 
24
3
  Laravel joy. Python speed. 10x less code.
@@ -42,13 +21,16 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
42
21
  - **Full async** — every route handler is `async` by default
43
22
  - **Routing** — decorator-based with path parameters, type hints, and auto-discovery
44
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
45
25
  - **ORM** — define models with typed fields, save/load/select/delete with one line
46
- - **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird
26
+ - **Database** — SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Firebird, MongoDB
47
27
  - **Migrations** — versioned SQL files, CLI scaffolding
28
+ - **Data seeder** — zero-dependency fake data generation with ORM and table support
48
29
  - **Sessions** — file, Redis, Valkey, or MongoDB backends
49
- - **JWT authentication** — auto-generated RSA keys, bearer tokens, form tokens
30
+ - **JWT authentication** — HS256 tokens signed with your `SECRET` env var, form tokens
50
31
  - **Swagger/OpenAPI** — auto-generated docs at `/swagger`
51
- - **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
52
34
  - **Middleware** — before/after hooks per route or globally
53
35
  - **Queues** — background processing with litequeue, RabbitMQ, Kafka, or MongoDB
54
36
  - **WebSockets** — built-in support via `simple-websocket`
@@ -57,7 +39,7 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
57
39
  - **SCSS compilation** — auto-compiled to CSS on save
58
40
  - **Live reload** — browser auto-refreshes during development
59
41
  - **Inline testing** — decorator-based test cases with `@tests`
60
- - **Localization** — i18n via gettext (English, French, Afrikaans)
42
+ - **Localization** — i18n via gettext (English, French, Afrikaans, Chinese, Japanese, Spanish)
61
43
 
62
44
  ## Install
63
45
 
@@ -65,12 +47,26 @@ You've just built your first Tina4 app — zero configuration, zero classes, zer
65
47
  pip install tina4-python
66
48
  ```
67
49
 
68
- Or with [uv](https://docs.astral.sh/uv/) (recommended for dependency management):
50
+ Or with [uv](https://docs.astral.sh/uv/) (recommended):
69
51
 
70
52
  ```bash
71
53
  uv add tina4-python
72
54
  ```
73
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
+
74
70
  ## Routing
75
71
 
76
72
  Routes live in `src/routes/` and are auto-discovered on startup.
@@ -85,7 +81,7 @@ async def get_hello(request, response):
85
81
 
86
82
  @get("/hello/{name}")
87
83
  async def get_hello_name(name, request, response):
88
- return response(f"Hello, {name}")
84
+ return response(f"Hello, {name}!")
89
85
 
90
86
  @get("/hello/json")
91
87
  async def get_hello_json(request, response):
@@ -94,10 +90,6 @@ async def get_hello_json(request, response):
94
90
  @get("/hello/template")
95
91
  async def get_hello_template(request, response):
96
92
  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
93
  ```
102
94
 
103
95
  ## ORM
@@ -120,11 +112,13 @@ User({"name": "Alice", "email": "alice@example.com"}).save()
120
112
  from tina4_python.Database import Database
121
113
 
122
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
+
123
119
  result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
124
120
  ```
125
121
 
126
- Works with SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, and Firebird.
127
-
128
122
  ## Migrations
129
123
 
130
124
  ```bash
@@ -133,6 +127,48 @@ tina4 migrate:create "create users table"
133
127
  tina4 migrate
134
128
  ```
135
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
+
136
172
  ## Sessions
137
173
 
138
174
  Built-in session management with pluggable backends:
@@ -145,8 +181,26 @@ Built-in session management with pluggable backends:
145
181
  | `SessionMongoHandler` | MongoDB | `pymongo` |
146
182
 
147
183
  ```python
148
- request.session.set("name", "Joe")
149
- 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")
150
204
  ```
151
205
 
152
206
  ## Queues
@@ -156,10 +210,25 @@ Background processing with litequeue (default), RabbitMQ, Kafka, or MongoDB.
156
210
  ```python
157
211
  from tina4_python.Queue import Queue, Producer, Consumer
158
212
 
213
+ # Enqueue from a route
159
214
  Producer(Queue(topic="emails")).produce({"to": "alice@example.com"})
160
215
 
216
+ # Process in a worker
161
217
  for msg in Consumer(Queue(topic="emails")).messages():
162
- 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
163
232
  ```
164
233
 
165
234
  ## Middleware
@@ -200,25 +269,30 @@ async def users(request, response):
200
269
  from tina4_python import Api
201
270
 
202
271
  api = Api("https://api.example.com", auth_header="Bearer xyz")
203
- result = api.get("/users/42")
272
+ result = api.send_request("/users/42")
204
273
  ```
205
274
 
206
275
  ## WSDL / SOAP
207
276
 
208
277
  ```python
209
278
  from tina4_python.WSDL import WSDL, wsdl_operation
279
+ from tina4_python import get, post
210
280
 
211
281
  class Calculator(WSDL):
212
- SERVICE_URL = "http://localhost:7145/calculator"
213
-
282
+ @wsdl_operation({"Result": int})
214
283
  def Add(self, a: int, b: int):
215
284
  return {"Result": a + b}
285
+
286
+ @get("/calculator")
287
+ @post("/calculator")
288
+ async def calculator(request, response):
289
+ return response(Calculator(request).handle())
216
290
  ```
217
291
 
218
- ## Testing
292
+ ## Inline Testing
219
293
 
220
294
  ```python
221
- from tina4_python import tests
295
+ from tina4_python import tests, assert_equal, assert_raises
222
296
 
223
297
  @tests(
224
298
  assert_equal((7, 7), 1),
@@ -230,14 +304,14 @@ def divide(a: int, b: int) -> float:
230
304
  return a / b
231
305
  ```
232
306
 
233
- Run with `tina4 test` or `uv run pytest --verbose`.
307
+ Run with `tina4 test` or `uv run pytest`.
234
308
 
235
309
  ## Environment
236
310
 
237
311
  Key `.env` settings:
238
312
 
239
313
  ```bash
240
- SECRET=your-jwt-secret
314
+ SECRET=your-jwt-secret-32-chars-min
241
315
  API_KEY=your-api-key
242
316
  DATABASE_NAME=sqlite3:app.db
243
317
  TINA4_DEBUG_LEVEL=ALL
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "0.2.201"
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"}
@@ -11,8 +11,7 @@ dependencies = [
11
11
  "jinja2>=3.1.5,<4.0.0",
12
12
  "libsass (>=0.23.0,<0.24.0)",
13
13
  "python-dotenv (>=1.0.1,<2.0.0)",
14
- "pyjwt (>=2.10.1,<3.0.0)",
15
- "cryptography (>=44.0.0,<45.0.0)",
14
+ "pyjwt>=2.10.1,<3.0.0",
16
15
  "watchdog (>=6.0.0,<7.0.0)",
17
16
  "bcrypt (>=4.2.1,<5.0.0)",
18
17
  "litequeue (>=0.9,<0.10)",
@@ -20,11 +19,26 @@ dependencies = [
20
19
  "asyncer>=0.0.8",
21
20
  "hypercorn>=0.18.0",
22
21
  "requests>=2.32.5",
23
- "jurigged>=0.6.0",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev-reload = ["jurigged>=0.6.0"]
26
+ postgres = ["psycopg2-binary>=2.9.10"]
27
+ mysql = ["mysql-connector-python>=9.3.0"]
28
+ mssql = ["pymssql>=2.3.0"]
29
+ firebird = ["firebird-driver>=1.10.0"]
30
+ mongo = ["pymongo>=4.0.0"]
31
+ all-db = [
32
+ "psycopg2-binary>=2.9.10",
33
+ "mysql-connector-python>=9.3.0",
34
+ "pymssql>=2.3.0",
35
+ "firebird-driver>=1.10.0",
36
+ "pymongo>=4.0.0",
24
37
  ]
25
38
 
26
39
  [dependency-groups]
27
40
  dev = [
41
+ "jurigged>=0.6.0",
28
42
  "flake8>=7.2.0",
29
43
  "mkdocs>=1.6.1",
30
44
  "mysql-connector-python>=9.3.0",