tina4-python 0.2.167__tar.gz → 0.2.169__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 (56) hide show
  1. {tina4_python-0.2.167 → tina4_python-0.2.169}/PKG-INFO +1 -1
  2. {tina4_python-0.2.167 → tina4_python-0.2.169}/pyproject.toml +65 -65
  3. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/CRUD.py +24 -0
  4. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/FieldTypes.py +16 -9
  5. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/ORM.py +5 -4
  6. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Template.py +5 -2
  7. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/__init__.py +5 -1
  8. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/templates/components/crud.twig +31 -4
  9. {tina4_python-0.2.167 → tina4_python-0.2.169}/.gitignore +0 -0
  10. {tina4_python-0.2.167 → tina4_python-0.2.169}/README.md +0 -0
  11. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Api.py +0 -0
  12. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Auth.py +0 -0
  13. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Constant.py +0 -0
  14. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Database.py +0 -0
  15. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/DatabaseResult.py +0 -0
  16. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/DatabaseTypes.py +0 -0
  17. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Debug.py +0 -0
  18. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Env.py +0 -0
  19. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/HtmlElement.py +0 -0
  20. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Localization.py +0 -0
  21. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Messages.py +0 -0
  22. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/MiddleWare.py +0 -0
  23. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Migration.py +0 -0
  24. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Queue.py +0 -0
  25. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Request.py +0 -0
  26. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Response.py +0 -0
  27. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Router.py +0 -0
  28. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Session.py +0 -0
  29. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/ShellColors.py +0 -0
  30. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Swagger.py +0 -0
  31. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Testing.py +0 -0
  32. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/WSDL.py +0 -0
  33. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Webserver.py +0 -0
  34. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/Websocket.py +0 -0
  35. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/cli.py +0 -0
  36. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/messages.pot +0 -0
  37. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/css/readme.md +0 -0
  38. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/favicon.ico +0 -0
  39. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/images/403.png +0 -0
  40. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/images/404.png +0 -0
  41. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/images/500.png +0 -0
  42. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/images/logo.png +0 -0
  43. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/images/readme.md +0 -0
  44. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/js/readme.md +0 -0
  45. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  46. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/js/tina4helper.js +0 -0
  47. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/swagger/index.html +0 -0
  48. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  49. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/templates/errors/403.twig +0 -0
  50. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/templates/errors/404.twig +0 -0
  51. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/templates/errors/500.twig +0 -0
  52. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/templates/readme.md +0 -0
  53. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  54. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  55. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  56. {tina4_python-0.2.167 → tina4_python-0.2.169}/tina4_python/translations/fr/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.167
3
+ Version: 0.2.169
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,65 +1,65 @@
1
- [project]
2
- name = "tina4-python"
3
- version = "0.2.167"
4
- description = "Tina4Python - This is not another framework for Python"
5
- authors = [
6
- {name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}
7
- ]
8
- readme = "README.md"
9
- requires-python = ">=3.12,<4.0"
10
- dependencies = [
11
- "jinja2>=3.1.5,<4.0.0",
12
- "libsass (>=0.23.0,<0.24.0)",
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)",
16
- "watchdog (>=6.0.0,<7.0.0)",
17
- "bcrypt (>=4.2.1,<5.0.0)",
18
- "litequeue (>=0.9,<0.10)",
19
- "simple-websocket (>=1.1.0,<2.0.0)",
20
- "asyncer>=0.0.8",
21
- "hypercorn>=0.18.0",
22
- "requests>=2.32.5",
23
- ]
24
-
25
- [dependency-groups]
26
- dev = [
27
- "flake8>=7.2.0",
28
- "jurigged>=0.6.0",
29
- "mkdocs>=1.6.1",
30
- "mysql-connector-python>=9.3.0",
31
- "psycopg2-binary>=2.9.10",
32
- "pydoc-markdown>=4.8.2",
33
- "pytest>=8.3.5",
34
- "pytest-asyncio>=1.3.0",
35
- "python-keycloak>=5.8.1",
36
- "ruff>=0.11.9",
37
- "safety>=3.5.0",
38
- ]
39
-
40
- [build-system]
41
- requires = ["hatchling"]
42
- build-backend = "hatchling.build"
43
-
44
- [tool.hatch.build]
45
- include = [
46
- "tina4_python/**/*"
47
- ]
48
-
49
- [project.scripts]
50
- tina4 = "tina4_python.cli:main"
51
-
52
- [[tool.pydoc-markdown.loaders]]
53
- type = "python"
54
- search_path = [ "./tina4_python" ]
55
-
56
- [tool.pydoc-markdown.renderer]
57
- type = "mkdocs"
58
- site_name= "Tina4Python"
59
-
60
- [[tool.pydoc-markdown.renderer.pages]]
61
- title = "API Documentation"
62
- name = "index"
63
-
64
- contents = [ "*" ]
65
-
1
+ [project]
2
+ name = "tina4-python"
3
+ version = "0.2.169"
4
+ description = "Tina4Python - This is not another framework for Python"
5
+ authors = [
6
+ {name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12,<4.0"
10
+ dependencies = [
11
+ "jinja2>=3.1.5,<4.0.0",
12
+ "libsass (>=0.23.0,<0.24.0)",
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)",
16
+ "watchdog (>=6.0.0,<7.0.0)",
17
+ "bcrypt (>=4.2.1,<5.0.0)",
18
+ "litequeue (>=0.9,<0.10)",
19
+ "simple-websocket (>=1.1.0,<2.0.0)",
20
+ "asyncer>=0.0.8",
21
+ "hypercorn>=0.18.0",
22
+ "requests>=2.32.5",
23
+ ]
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "flake8>=7.2.0",
28
+ "jurigged>=0.6.0",
29
+ "mkdocs>=1.6.1",
30
+ "mysql-connector-python>=9.3.0",
31
+ "psycopg2-binary>=2.9.10",
32
+ "pydoc-markdown>=4.8.2",
33
+ "pytest>=8.3.5",
34
+ "pytest-asyncio>=1.3.0",
35
+ "python-keycloak>=5.8.1",
36
+ "ruff>=0.11.9",
37
+ "safety>=3.5.0",
38
+ ]
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build]
45
+ include = [
46
+ "tina4_python/**/*"
47
+ ]
48
+
49
+ [project.scripts]
50
+ tina4 = "tina4_python.cli:main"
51
+
52
+ [[tool.pydoc-markdown.loaders]]
53
+ type = "python"
54
+ search_path = [ "./tina4_python" ]
55
+
56
+ [tool.pydoc-markdown.renderer]
57
+ type = "mkdocs"
58
+ site_name= "Tina4Python"
59
+
60
+ [[tool.pydoc-markdown.renderer.pages]]
61
+ title = "API Documentation"
62
+ name = "index"
63
+
64
+ contents = [ "*" ]
65
+
@@ -252,6 +252,21 @@ class CRUD:
252
252
 
253
253
  twig_file = self.ensure_crud_template(crud_name + ".twig")
254
254
 
255
+ def parse_request_for_images(request):
256
+ """
257
+ Parses request for images and files
258
+ :param request:
259
+ :return:
260
+ """
261
+ # if there are files in the request the then I assign the file value to the request body
262
+ if request.files:
263
+ for input_name in request.files:
264
+ prefix = ""
265
+ if "image" in request.files[input_name]["content_type"]:
266
+ prefix ="data:"+request.files[input_name]["content_type"]+";base64,"
267
+ request.body[input_name] = prefix+request.files[input_name]["content"]
268
+ return request
269
+
255
270
  async def get_record(request, response):
256
271
  limit = int(request.params.get("limit", options.get("limit", 10)))
257
272
  offset = int(request.params.get("offset", options.get("offset", 0)))
@@ -285,12 +300,21 @@ class CRUD:
285
300
 
286
301
  async def post_record(request, response):
287
302
  Debug.info("CRUD CREATE", table_name, request.body)
303
+
304
+ request = parse_request_for_images(request)
305
+
288
306
  self.dba.insert(table_name, request.body, primary_key=options["primary_key"])
289
307
  self.dba.commit()
290
308
  return response({"message": f"<script>showMessage('{table_nice_name} Record added');</script>"}, HTTP_OK, APPLICATION_JSON)
291
309
 
292
310
  async def update_record(request, response):
293
311
  Debug.info("CRUD UPDATE", table_name, request.params, request.body)
312
+ # Add the primary key if it is taken out of the form
313
+ if options["primary_key"] not in request.body:
314
+ request.body[options["primary_key"]] = request.params[options["primary_key"]]
315
+
316
+ request = parse_request_for_images(request)
317
+
294
318
  self.dba.update(table_name, request.body, primary_key=options["primary_key"])
295
319
  self.dba.commit()
296
320
  return response({"message": f"<script>showMessage('{table_nice_name} Record updated');</script>", "post": request.body}, HTTP_OK, APPLICATION_JSON)
@@ -9,6 +9,7 @@ import ast
9
9
  import inspect
10
10
  from datetime import datetime
11
11
  from tina4_python.DatabaseTypes import MSSQL, POSTGRES, FIREBIRD
12
+ from tina4_python import Debug
12
13
 
13
14
  class BaseField:
14
15
  primary_key = False
@@ -83,15 +84,21 @@ class BaseField:
83
84
  self.decimal_places = decimal_places
84
85
 
85
86
  if column_name is None:
86
- frame = inspect.stack()[1]
87
- # Parse python syntax of the assignment line
88
- st = ast.parse(frame.code_context[0].strip())
89
- stmt = st.body[0]
90
- # Assume class being instanced as simple assign statement
91
- assert (isinstance(stmt, ast.Assign))
92
- # Parse the target the class is assigned to
93
- target = stmt.targets[0]
94
- self.column_name = target.id
87
+ try:
88
+ frame = inspect.stack()[1]
89
+ # Parse python syntax of the assignment line
90
+ st = ast.parse(frame.code_context[0].strip())
91
+ stmt = st.body[0]
92
+ # Assume class being instanced as simple assign statement
93
+ assert (isinstance(stmt, ast.Assign))
94
+ # Parse the target the class is assigned to
95
+ target = stmt.targets[0]
96
+ if hasattr(target, 'id'):
97
+ self.column_name = target.id
98
+ else:
99
+ raise Exception("Sorry we can't determine the column name for ORM ")
100
+ except Exception as e:
101
+ Debug.error("Error determining field column for ORM Object", str(e))
95
102
  else:
96
103
  self.column_name = column_name
97
104
 
@@ -5,12 +5,10 @@
5
5
  #
6
6
  # flake8: noqa: E501
7
7
  import base64
8
- from datetime import datetime, date
9
- import ast
8
+ from datetime import date
10
9
  import json
11
10
  import os
12
11
  from tina4_python.Constant import TINA4_LOG_ERROR
13
- from tina4_python.Debug import Debug
14
12
  from tina4_python.FieldTypes import *
15
13
 
16
14
 
@@ -186,7 +184,10 @@ class ORM:
186
184
 
187
185
  data[key] = current_value.value
188
186
  elif isinstance(value, IntegerField):
189
- data[key] = int(current_value)
187
+ try:
188
+ data[key] = int(current_value)
189
+ except Exception as e:
190
+ Debug.error("Could not save", current_value, "to", key)
190
191
  else:
191
192
  data[key] = str(current_value)
192
193
 
@@ -60,8 +60,11 @@ class Template:
60
60
  @staticmethod
61
61
  def datetime_format(value, format="%H:%M %d-%m-%y"):
62
62
  if value.strip().upper() == "NOW":
63
- value = date.today()
64
- return value.strftime(format)
63
+ value = datetime.now()
64
+ try:
65
+ return value.strftime(format)
66
+ except AttributeError:
67
+ return value
65
68
 
66
69
  @staticmethod
67
70
  def production_dump(param):
@@ -217,7 +217,7 @@ from .Router import get, post, put, patch, delete, middleware, cached, noauth, s
217
217
  from .Testing import tests, assert_equal, assert_raises
218
218
  from .Debug import Debug
219
219
  from .Database import Database
220
- from .ORM import ORM
220
+ from .ORM import ORM, orm
221
221
  from .Api import Api
222
222
  from .Template import template
223
223
  from .Swagger import description, secure, summary, example, example_response, tags, params, describe
@@ -231,10 +231,14 @@ for deco in (get, post, put, patch, delete, middleware, cached, noauth, secured,
231
231
  if deco.__name__ not in builtins.__dict__:
232
232
  builtins.__dict__[deco.__name__] = deco
233
233
 
234
+
235
+
234
236
  builtins.Debug = Debug
235
237
  builtins.Api = Api
236
238
  builtins.Database = Database
237
239
  builtins.ORM = ORM
240
+ builtins.orm = orm
241
+
238
242
 
239
243
  # Auto-import everything from src folders
240
244
  if os.path.exists(root_path + os.sep + "src"):
@@ -59,7 +59,7 @@
59
59
  {# --- Only show image if it's base64-like and long enough --- #}
60
60
  {% if is_image and value|length > 50 and value is string %}
61
61
  {% set src = value %}
62
- {% if value[:10] != 'data:image/' %}
62
+ {% if value[:11] != 'data:image/' %}
63
63
  {% set src = 'data:image/' ~ mime_type ~ ';base64,' ~ value %}
64
64
  {% endif %}
65
65
 
@@ -131,7 +131,7 @@
131
131
  {# --- Only show image if it's base64-like and long enough --- #}
132
132
  {% if is_image %}
133
133
  {% set src = value %}
134
- {% if value[:10] != 'data:image/' %}
134
+ {% if value[:11] != 'data:image/' %}
135
135
  {% set src = 'data:image/' ~ mime_type ~ ';base64,' ~ value %}
136
136
  {% endif %}
137
137
 
@@ -253,7 +253,7 @@
253
253
 
254
254
  html += `
255
255
  <div class="mb-3">
256
- <label class="form-label fw-bold">${label}</label>
256
+ <label class="form-label">${label}</label>
257
257
  <div class="border rounded p-3 bg-light text-center">
258
258
  <img src="${imageSrc}"
259
259
  alt="Current ${label}"
@@ -317,7 +317,33 @@
317
317
  value = '';
318
318
  type = `{{ column.type|default('text') }}`;
319
319
 
320
- html += `<div class="mb-3">
320
+ if (field.indexOf('image') !== -1) {
321
+ let imageSrc = value;
322
+ if (value.length > 0 && !value.startsWith('data:image/')) {
323
+ imageSrc = `data:image/${mimeType};base64,${value}`;
324
+ }
325
+
326
+ html += `
327
+ <div class="mb-3">
328
+ <label class="form-label">${label}</label>
329
+ <div class="border rounded p-3 bg-light text-center">
330
+ <img src="${imageSrc}"
331
+ alt="Current ${label}"
332
+ class="img-thumbnail mb-3"
333
+ style="max-height: 180px; max-width: 100%;">
334
+ <div>
335
+ <input type="file"
336
+ accept="image/*"
337
+ class="form-control form-control-sm"
338
+ id="${field}"
339
+ name="${field}">
340
+ <small class="text-muted">Choose new image (optional)</small>
341
+ </div>
342
+ </div>
343
+ </div>`;
344
+ } else {
345
+
346
+ html += `<div class="mb-3">
321
347
  <label for="${field}" class="form-label">${label}</label>
322
348
  <input type="${type === 'password' ? 'password' : 'text'}"
323
349
  class="form-control"
@@ -326,6 +352,7 @@
326
352
  placeholder="${label}"
327
353
  value='${value}'>
328
354
  </div>`;
355
+ }
329
356
  {% endfor %}
330
357
 
331
358
  modalBody.innerHTML = html;
File without changes