tina4-python 0.2.190__tar.gz → 0.2.191__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 (66) hide show
  1. {tina4_python-0.2.190 → tina4_python-0.2.191}/PKG-INFO +1 -1
  2. {tina4_python-0.2.190 → tina4_python-0.2.191}/pyproject.toml +1 -1
  3. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/CLAUDE.md +37 -0
  4. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Database.py +36 -21
  5. {tina4_python-0.2.190 → tina4_python-0.2.191}/.gitignore +0 -0
  6. {tina4_python-0.2.190 → tina4_python-0.2.191}/README.md +0 -0
  7. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Api.py +0 -0
  8. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Auth.py +0 -0
  9. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/CRUD.py +0 -0
  10. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Constant.py +0 -0
  11. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/DatabaseResult.py +0 -0
  12. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/DatabaseTypes.py +0 -0
  13. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Debug.py +0 -0
  14. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/DevReload.py +0 -0
  15. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Env.py +0 -0
  16. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/FieldTypes.py +0 -0
  17. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/HtmlElement.py +0 -0
  18. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Localization.py +0 -0
  19. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Messages.py +0 -0
  20. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/MiddleWare.py +0 -0
  21. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Migration.py +0 -0
  22. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/ORM.py +0 -0
  23. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Queue.py +0 -0
  24. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Request.py +0 -0
  25. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Response.py +0 -0
  26. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Router.py +0 -0
  27. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Session.py +0 -0
  28. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/ShellColors.py +0 -0
  29. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Swagger.py +0 -0
  30. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Template.py +0 -0
  31. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Testing.py +0 -0
  32. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/WSDL.py +0 -0
  33. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Webserver.py +0 -0
  34. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/Websocket.py +0 -0
  35. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/__init__.py +0 -0
  36. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/cli.py +0 -0
  37. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/messages.pot +0 -0
  38. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/css/readme.md +0 -0
  39. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/favicon.ico +0 -0
  40. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/images/403.png +0 -0
  41. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/images/404.png +0 -0
  42. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/images/500.png +0 -0
  43. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/images/logo.png +0 -0
  44. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/images/readme.md +0 -0
  45. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/js/readme.md +0 -0
  46. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  47. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/js/tina4helper.js +0 -0
  48. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/swagger/index.html +0 -0
  49. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  50. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/templates/components/crud.twig +0 -0
  51. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/templates/errors/403.twig +0 -0
  52. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/templates/errors/404.twig +0 -0
  53. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/templates/errors/500.twig +0 -0
  54. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/templates/readme.md +0 -0
  55. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  56. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  57. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  58. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  59. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  60. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  61. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  62. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  63. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  64. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  65. {tina4_python-0.2.190 → tina4_python-0.2.191}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  66. {tina4_python-0.2.190 → tina4_python-0.2.191}/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.190
3
+ Version: 0.2.191
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "0.2.190"
3
+ version = "0.2.191"
4
4
  description = "Tina4Python - This is not another framework for Python"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}
@@ -251,6 +251,38 @@ from tina4_python.Queue import Queue, Producer
251
251
  Producer(Queue(topic="tasks")).produce({"action": "send_email"})
252
252
  ```
253
253
 
254
+ ### 11. Key tina4_python Gotchas
255
+
256
+ 1. **Database import**: Use `from tina4_python.Database import Database` (NOT `from tina4_python import Database`)
257
+ 2. **noauth/secured import**: Use `from tina4_python import noauth` or `from tina4_python.Router import noauth` (there is NO `tina4_python.Decorators` module)
258
+ 3. **Jinja2 template syntax** (common mistakes):
259
+ - **No ternary operator**: Use `{% if x %}...{% endif %}` NOT `{{ x ? 'a' : 'b' }}`
260
+ - **elif not elseif**: Use `{% elif %}` NOT `{% elseif %}`
261
+ - **safe not raw**: Use `{{ var | safe }}` NOT `{{ var | raw }}` for unescaped output
262
+ - **format filter**: Use `{{ "%.2f" | format(value) }}` for number formatting
263
+ - **e() filter has NO arguments**: Use `{{ var|e }}` NOT `{{ var|e('js') }}` — Jinja2's `|e` is HTML-only with no mode parameter (that's PHP Twig syntax)
264
+ - **JS string escaping**: Use `{{ var|replace("'", "\\'") }}` to escape single quotes for inline JS onclick handlers
265
+ - **No `|escape('js')` or `|e('js')`**: These will throw `escape() takes 1 positional argument but 2 were given`
266
+ - **Ternary inline**: Use `{{ 's' if count != 1 else '' }}` NOT `{{ count != 1 ? 's' : '' }}`
267
+ - **Default values**: Use `{{ var|default('fallback') }}` — works on undefined and None
268
+ - **Chaining filters**: `{{ var|default('')|replace("'", "\\'") }}` — left to right
269
+ - **Loop variables**: `loop.index` (1-based), `loop.index0` (0-based), `loop.first`, `loop.last`, `loop.length`
270
+ - **Whitespace control**: `{%- -%}` and `{{- -}}` trim surrounding whitespace
271
+ - **String concatenation**: Use `~` operator: `{{ "hello " ~ name }}` NOT `{{ "hello " + name }}`
272
+ - **include with context**: `{% include "partial.twig" %}` inherits parent context automatically
273
+ - **Macro imports**: `{% from "macros/forms.twig" import field_group %}` — macros do NOT inherit parent context, pass variables explicitly
274
+ 4. **DatabaseResult**: `dba.fetch()` returns a `DatabaseResult` object, NOT a plain list
275
+ - Use `.records` to get list of dicts: `result.records`
276
+ - Use `.count` for row count (no `len()` support)
277
+ - Iteration works: `for row in result` or `list(result)`
278
+ - Other methods: `.to_json()`, `.to_array()`, `.to_csv()`, `.to_paginate()`
279
+ 4. **fetch_one()**: Returns a plain dict (or None), NOT a DatabaseResult
280
+ 5. **Dict access**: All query results use dict access `row["column"]` not attribute access `row.column`
281
+ 6. **Firebird connection string**: `firebird.driver:<host>/<port>:<database_path>` — note the `firebird.driver:` prefix (the module name), NOT `firebird:`
282
+ 7. **Running the app**: `uv run python app.py <port> <name>` — port and name are CLI args handled by tina4_python
283
+ 8. **SCSS**: Files in `src/scss/` are auto-compiled to `src/public/css/` on startup
284
+
285
+
254
286
  ---
255
287
 
256
288
  ## Project Structure
@@ -668,6 +700,9 @@ except:
668
700
  db.rollback()
669
701
  ```
670
702
 
703
+ **NEVER use `db.execute("COMMIT")` or `db.execute("ROLLBACK")` or `db.execute("BEGIN")`.**
704
+ Always use the proper methods: `db.commit()`, `db.rollback()`, `db.start_transaction()`. Raw SQL transaction commands bypass the framework's connection and transaction state management, leading to unpredictable behaviour and connection leaks.
705
+
671
706
  ## ORM
672
707
 
673
708
  Define models in `src/orm/` (one class per file, filename matches class name).
@@ -1310,3 +1345,5 @@ async def dashboard(request, response):
1310
1345
  </div>
1311
1346
  {% endblock %}
1312
1347
  ```
1348
+
1349
+
@@ -55,10 +55,13 @@ import datetime
55
55
 
56
56
  class Database:
57
57
 
58
- def __init__(self, connection_string, username="", password=""):
58
+ def __init__(self, connection_string, username="", password="", charset=""):
59
59
  """
60
60
  Initializes a database connection
61
61
  :param connection_string:
62
+ :param username:
63
+ :param password:
64
+ :param charset: Character set for the connection (e.g. "UTF8", "WIN1252")
62
65
  """
63
66
  # split out the connection string
64
67
  # driver:host/port:schema/path
@@ -71,6 +74,8 @@ class Database:
71
74
  username = os.environ.get("DATABASE_USERNAME", "")
72
75
  if password == "":
73
76
  password = os.environ.get("DATABASE_PASSWORD", "")
77
+ if charset == "":
78
+ charset = os.environ.get("DATABASE_CHARSET", "")
74
79
 
75
80
  if connection_string is None:
76
81
  from tina4_python import Messages
@@ -98,6 +103,7 @@ class Database:
98
103
  self.database_path = params[1]
99
104
  self.username = username
100
105
  self.password = password
106
+ self.charset = charset
101
107
 
102
108
  if self.database_engine == SQLITE:
103
109
  self.dba = self.database_module.connect(self.database_path)
@@ -149,28 +155,37 @@ class Database:
149
155
  self.database_path = temp_params[1]
150
156
 
151
157
  if self.database_engine == FIREBIRD:
152
- self.dba = self.database_module.connect(
153
- self.host + "/" + str(self.port) + ":" + self.database_path,
154
- user=self.username,
155
- password=self.password
156
- )
158
+ firebird_args = {
159
+ "dsn": self.host + "/" + str(self.port) + ":" + self.database_path,
160
+ "user": self.username,
161
+ "password": self.password,
162
+ }
163
+ if self.charset:
164
+ firebird_args["charset"] = self.charset
165
+ self.dba = self.database_module.connect(**firebird_args)
157
166
  elif self.database_engine == MYSQL:
158
- self.dba = self.database_module.connect(
159
- database=self.database_path,
160
- port=self.port,
161
- host=self.host,
162
- user=self.username,
163
- password=self.password,
164
- consume_results=True
165
- )
167
+ mysql_args = {
168
+ "database": self.database_path,
169
+ "port": self.port,
170
+ "host": self.host,
171
+ "user": self.username,
172
+ "password": self.password,
173
+ "consume_results": True,
174
+ }
175
+ if self.charset:
176
+ mysql_args["charset"] = self.charset
177
+ self.dba = self.database_module.connect(**mysql_args)
166
178
  elif self.database_engine == POSTGRES:
167
- self.dba = self.database_module.connect(
168
- dbname=self.database_path,
169
- port=self.port,
170
- host=self.host,
171
- user=self.username,
172
- password=self.password
173
- )
179
+ postgres_args = {
180
+ "dbname": self.database_path,
181
+ "port": self.port,
182
+ "host": self.host,
183
+ "user": self.username,
184
+ "password": self.password,
185
+ }
186
+ if self.charset:
187
+ postgres_args["client_encoding"] = self.charset
188
+ self.dba = self.database_module.connect(**postgres_args)
174
189
  elif self.database_engine == MSSQL:
175
190
  self.dba = self.database_module.connect(
176
191
  server=self.host,
File without changes