tina4-python 0.2.138__tar.gz → 0.2.140__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 (58) hide show
  1. {tina4_python-0.2.138 → tina4_python-0.2.140}/.gitignore +1 -0
  2. {tina4_python-0.2.138 → tina4_python-0.2.140}/PKG-INFO +5 -9
  3. {tina4_python-0.2.138 → tina4_python-0.2.140}/README.md +4 -8
  4. {tina4_python-0.2.138 → tina4_python-0.2.140}/pyproject.toml +64 -63
  5. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Constant.py +3 -0
  6. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/ORM.py +5 -1
  7. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Request.py +3 -1
  8. tina4_python-0.2.140/tina4_python/Response.py +209 -0
  9. tina4_python-0.2.140/tina4_python/Router.py +672 -0
  10. tina4_python-0.2.140/tina4_python/Swagger.py +358 -0
  11. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Template.py +32 -0
  12. tina4_python-0.2.140/tina4_python/WSDL.py +438 -0
  13. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Webserver.py +23 -11
  14. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Websocket.py +1 -3
  15. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/__init__.py +87 -9
  16. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/cli.py +18 -7
  17. tina4_python-0.2.138/tina4_python/Response.py +0 -120
  18. tina4_python-0.2.138/tina4_python/Router.py +0 -442
  19. tina4_python-0.2.138/tina4_python/Swagger.py +0 -228
  20. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Api.py +0 -0
  21. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Auth.py +0 -0
  22. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/CRUD.py +0 -0
  23. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Database.py +0 -0
  24. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/DatabaseResult.py +0 -0
  25. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/DatabaseTypes.py +0 -0
  26. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Debug.py +0 -0
  27. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Env.py +0 -0
  28. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/FieldTypes.py +0 -0
  29. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/HtmlElement.py +0 -0
  30. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Localization.py +0 -0
  31. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Messages.py +0 -0
  32. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/MiddleWare.py +0 -0
  33. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Migration.py +0 -0
  34. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Queue.py +0 -0
  35. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/Session.py +0 -0
  36. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/ShellColors.py +0 -0
  37. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/messages.pot +0 -0
  38. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/css/readme.md +0 -0
  39. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/favicon.ico +0 -0
  40. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/images/403.png +0 -0
  41. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/images/404.png +0 -0
  42. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/images/500.png +0 -0
  43. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/images/logo.png +0 -0
  44. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/images/readme.md +0 -0
  45. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/js/readme.md +0 -0
  46. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  47. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/js/tina4helper.js +0 -0
  48. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/swagger/index.html +0 -0
  49. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  50. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/templates/components/crud.twig +0 -0
  51. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/templates/errors/403.twig +0 -0
  52. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/templates/errors/404.twig +0 -0
  53. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/templates/errors/500.twig +0 -0
  54. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/templates/readme.md +0 -0
  55. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  56. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  57. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  58. {tina4_python-0.2.138 → tina4_python-0.2.140}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
@@ -33,3 +33,4 @@
33
33
  /tina4_python.egg-info/
34
34
  /src/templates/crud/
35
35
  /.venv/
36
+ /publish.bat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 0.2.138
3
+ Version: 0.2.140
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
@@ -21,19 +21,15 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  Laravel joy. Python speed. 10× less code.
23
23
 
24
+ ## Quickstart
24
25
  ```bash
25
26
  pip install tina4_python
26
- ```
27
-
28
- That’s it. Save the code above as `app.py`, run it, and you have a fully working Tina4 Python web server.
29
-
30
- ```bash
31
- pip install tina4-python
27
+ tina4 init my_project
28
+ cd my_project
32
29
  python app.py
33
- # → http://localhost:7145
34
30
  ```
35
31
 
36
- You just built your first Tina4 app — zero configuration, zero classes, zero boilerplate.
32
+ You've just built your first Tina4 app — zero configuration, zero classes, zero boilerplate!
37
33
 
38
34
  ## Features
39
35
 
@@ -2,19 +2,15 @@
2
2
 
3
3
  Laravel joy. Python speed. 10× less code.
4
4
 
5
+ ## Quickstart
5
6
  ```bash
6
7
  pip install tina4_python
7
- ```
8
-
9
- That’s it. Save the code above as `app.py`, run it, and you have a fully working Tina4 Python web server.
10
-
11
- ```bash
12
- pip install tina4-python
8
+ tina4 init my_project
9
+ cd my_project
13
10
  python app.py
14
- # → http://localhost:7145
15
11
  ```
16
12
 
17
- You just built your first Tina4 app — zero configuration, zero classes, zero boilerplate.
13
+ You've just built your first Tina4 app — zero configuration, zero classes, zero boilerplate!
18
14
 
19
15
  ## Features
20
16
 
@@ -1,63 +1,64 @@
1
- [project]
2
- name = "tina4-python"
3
- version = "0.2.138"
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
- ]
23
-
24
- [dependency-groups]
25
- dev = [
26
- "flake8>=7.2.0",
27
- "jurigged>=0.6.0",
28
- "mkdocs>=1.6.1",
29
- "mysql-connector-python>=9.3.0",
30
- "psycopg2-binary>=2.9.10",
31
- "pydoc-markdown>=4.8.2",
32
- "pytest>=8.3.5",
33
- "python-keycloak>=5.8.1",
34
- "ruff>=0.11.9",
35
- "safety>=3.5.0",
36
- ]
37
-
38
- [build-system]
39
- requires = ["hatchling"]
40
- build-backend = "hatchling.build"
41
-
42
- [tool.hatch.build]
43
- include = [
44
- "tina4_python/**/*"
45
- ]
46
-
47
- [project.scripts]
48
- tina4 = "tina4_python.cli:main"
49
-
50
- [[tool.pydoc-markdown.loaders]]
51
- type = "python"
52
- search_path = [ "./tina4_python" ]
53
-
54
- [tool.pydoc-markdown.renderer]
55
- type = "mkdocs"
56
- site_name= "Tina4Python"
57
-
58
- [[tool.pydoc-markdown.renderer.pages]]
59
- title = "API Documentation"
60
- name = "index"
61
-
62
- contents = [ "*" ]
63
-
1
+ [project]
2
+ name = "tina4-python"
3
+ version = "0.2.140"
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
+ ]
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "flake8>=7.2.0",
27
+ "jurigged>=0.6.0",
28
+ "mkdocs>=1.6.1",
29
+ "mysql-connector-python>=9.3.0",
30
+ "psycopg2-binary>=2.9.10",
31
+ "pydoc-markdown>=4.8.2",
32
+ "pytest>=8.3.5",
33
+ "pytest-asyncio>=1.3.0",
34
+ "python-keycloak>=5.8.1",
35
+ "ruff>=0.11.9",
36
+ "safety>=3.5.0",
37
+ ]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build]
44
+ include = [
45
+ "tina4_python/**/*"
46
+ ]
47
+
48
+ [project.scripts]
49
+ tina4 = "tina4_python.cli:main"
50
+
51
+ [[tool.pydoc-markdown.loaders]]
52
+ type = "python"
53
+ search_path = [ "./tina4_python" ]
54
+
55
+ [tool.pydoc-markdown.renderer]
56
+ type = "mkdocs"
57
+ site_name= "Tina4Python"
58
+
59
+ [[tool.pydoc-markdown.renderer.pages]]
60
+ title = "API Documentation"
61
+ name = "index"
62
+
63
+ contents = [ "*" ]
64
+
@@ -71,6 +71,8 @@ LOOKUP_HTTP_CODE = {
71
71
  HTTP_NO_CONTENT: "No Content",
72
72
  HTTP_PARTIAL_CONTENT: "Partial Content",
73
73
  HTTP_REDIRECT: "Redirect",
74
+ HTTP_REDIRECT_MOVED: "Redirect Moved",
75
+ HTTP_REDIRECT_OTHER: "Redirect Other",
74
76
  HTTP_BAD_REQUEST: "Bad Request",
75
77
  HTTP_UNAUTHORIZED: "Unauthorized",
76
78
  HTTP_FORBIDDEN: "Forbidden",
@@ -85,6 +87,7 @@ LOOKUP_HTTP_CODE = {
85
87
  TEXT_HTML = "text/html" #: HTML documents and templates
86
88
  TEXT_CSS = "text/css" #: Cascading Style Sheets
87
89
  TEXT_PLAIN = "text/plain" #: Plain text files
90
+ TEXT_JAVASCRIPT = "text/javascript" #: Javascript files
88
91
  APPLICATION_JSON = "application/json" #: JSON API responses
89
92
  APPLICATION_XML = "application/xml" #: XML data (RSS, SOAP, etc.)
90
93
 
@@ -197,6 +197,9 @@ class ORM:
197
197
  return self.to_json()
198
198
 
199
199
  def __create_table__(self, table_name, execute=False):
200
+ if self.__dba__ is None:
201
+ Debug.warning("Create Table", table_name, "database not assigned to ORM , use orm(dba)")
202
+ return False
200
203
  sql = "create table " + table_name + " ("
201
204
  counter = 0
202
205
  for field, field_definition in self.__field_definitions__.items():
@@ -213,6 +216,7 @@ class ORM:
213
216
 
214
217
  if execute:
215
218
  self.__dba__.execute(sql)
219
+ return None
216
220
  else:
217
221
  return sql
218
222
 
@@ -221,7 +225,7 @@ class ORM:
221
225
  Creates the table for the ORM structure
222
226
  :return:
223
227
  """
224
- self.__dba__.create_table(self.__table_name__, True)
228
+ return self.__create_table__(self.__table_name__, True)
225
229
 
226
230
  def __build_sql(self, column_names="*", join="", filter="", group_by="", having="", order_by=""):
227
231
  """
@@ -15,5 +15,7 @@ files = {}
15
15
  raw_request = None
16
16
  raw_data = None
17
17
  raw_content = None
18
- transport = None
18
+ asgi_scope = None
19
+ asgi_reader = None
20
+ asgi_writer = None
19
21
  asgi_response = None
@@ -0,0 +1,209 @@
1
+ #
2
+ # Tina4 - This is not a 4ramework.
3
+ # Copy-right 2007 - current Tina4
4
+ # License: MIT https://opensource.org/licenses/MIT
5
+ #
6
+ # flake8: noqa: E501
7
+ import os
8
+ import json
9
+ import inspect
10
+ from datetime import datetime, date
11
+ from types import ModuleType
12
+ from tina4_python import Constant
13
+ from tina4_python import DatabaseResult
14
+ from tina4_python.ORM import ORM
15
+ from tina4_python.Template import Template
16
+
17
+ headers = {}
18
+ content = ""
19
+ http_code = Constant.HTTP_OK
20
+ content_type = Constant.TEXT_HTML
21
+
22
+ class Response:
23
+
24
+ @staticmethod
25
+ def convert_special_types(obj):
26
+ if isinstance(obj, dict):
27
+ return {k: Response.convert_special_types(v) for k, v in obj.items()}
28
+ elif isinstance(obj, list):
29
+ return [Response.convert_special_types(i) for i in obj]
30
+ elif isinstance(obj, (date, datetime)):
31
+ return obj.isoformat()
32
+ else:
33
+ return obj
34
+
35
+ def __init__(self, content_in=None, http_code_in=None, content_type_in=None,
36
+ headers_in=None):
37
+ global headers
38
+ global content
39
+ global http_code
40
+ global content_type
41
+
42
+ if (not isinstance(content_in, bool) and not isinstance(content_in, object)
43
+ and not isinstance(content_in, bytes)
44
+ and not isinstance(content_in, str)
45
+ and not isinstance(content_in, list) and inspect.isclass(type(content_in))):
46
+ content_in = dict(content_in)
47
+
48
+ if isinstance(content_in, ORM):
49
+ content_type = Constant.APPLICATION_JSON
50
+ content_in = content_in.to_json()
51
+
52
+ # check if database result
53
+ if type(content_in) is DatabaseResult.DatabaseResult:
54
+ content_type = Constant.APPLICATION_JSON
55
+ content_in = content_in.to_json()
56
+
57
+ # convert the dictionary or list into JSON
58
+ if not isinstance(content_in, bool) and type(content_in) is dict or type(content_in) is list:
59
+ content_in = json.dumps(Response.convert_special_types(content_in))
60
+ content_type = Constant.APPLICATION_JSON
61
+
62
+ if isinstance(content_in, bool):
63
+ if content_in:
64
+ content_in = "True"
65
+ else:
66
+ content_in = "False"
67
+
68
+ if isinstance(content_in, ModuleType):
69
+ content_in = json.dumps({"error": "Cannot decode object of type " + str(type(content_in))})
70
+ content_type = Constant.APPLICATION_JSON
71
+
72
+ if content is not None and isinstance(content_in, str) and http_code_in == Constant.HTTP_OK:
73
+ content_in = content + content_in
74
+
75
+ self.headers = headers_in if headers_in is not None else headers
76
+ self.content = content_in if content_in is not None else content
77
+ self.http_code = http_code_in if http_code_in is not None else http_code
78
+ self.content_type = content_type_in if content_type_in is not None else content_type
79
+ headers = self.headers
80
+ http_code = self.http_code
81
+ content_type = self.content_type
82
+ content = self.content
83
+
84
+ @staticmethod
85
+ def redirect(redirect_url, http_code_in=Constant.HTTP_REDIRECT):
86
+ """
87
+ Redirects a request to redirect_url
88
+ :param http_code_in:
89
+ :param redirect_url:
90
+ :return:
91
+ """
92
+ global headers
93
+ global content
94
+ global http_code
95
+ global content_type
96
+ headers = {}
97
+ http_code = http_code_in
98
+ headers["Location"] = redirect_url
99
+ content = "Redirecting..."
100
+ content_type = Constant.TEXT_HTML
101
+ return Response("Redirecting...", http_code, content_type, headers)
102
+
103
+
104
+ @staticmethod
105
+ def render(template_name, data=None):
106
+ global content, content_type, http_code
107
+ http_code = Constant.HTTP_OK
108
+ content_type = Constant.TEXT_HTML
109
+
110
+ return Response(Template.render(template_name, data=data), http_code, content_type)
111
+
112
+ @staticmethod
113
+ def file(file_path: str, root_path: str = "src/public"):
114
+ """
115
+ Serve a static file from the file system.
116
+
117
+ Args:
118
+ file_path (str): The requested file path (e.g., "images/logo.png", "css/style.css")
119
+ root_path (str): Base directory to serve files from (defaults to src/public)
120
+
121
+ Returns:
122
+ Response: A properly configured Response object with file content and correct MIME type
123
+ """
124
+ global content, content_type, http_code
125
+
126
+ # Resolve full path and prevent directory traversal
127
+ full_path = os.path.abspath(os.path.join(root_path, file_path.lstrip("/")))
128
+
129
+ # Security: ensure the requested file is inside the root_path
130
+ if not full_path.startswith(os.path.abspath(root_path)):
131
+ http_code = Constant.HTTP_FORBIDDEN
132
+ content_type = Constant.TEXT_PLAIN
133
+ content = "403 - Forbidden"
134
+ return Response(content, http_code, content_type)
135
+
136
+ # Check if file exists
137
+ if not os.path.isfile(full_path):
138
+ http_code = Constant.HTTP_NOT_FOUND
139
+ content_type = Constant.TEXT_PLAIN
140
+ content = "404 - File Not Found"
141
+ return Response(content, http_code, content_type)
142
+
143
+ # Determine MIME type
144
+ extension = os.path.splitext(file_path)[1].lower()
145
+ mime_map = {
146
+ ".html": Constant.TEXT_HTML,
147
+ ".css": Constant.TEXT_CSS,
148
+ ".js": Constant.TEXT_JAVASCRIPT,
149
+ ".json": Constant.APPLICATION_JSON,
150
+ ".png": "image/png",
151
+ ".jpg": "image/jpeg",
152
+ ".jpeg": "image/jpeg",
153
+ ".gif": "image/gif",
154
+ ".svg": "image/svg+xml",
155
+ ".ico": "image/x-icon",
156
+ ".woff": "font/woff",
157
+ ".woff2":"font/woff2",
158
+ ".ttf": "font/ttf",
159
+ ".pdf": "application/pdf",
160
+ ".txt": Constant.TEXT_PLAIN,
161
+ }
162
+ content_type = mime_map.get(extension, "application/octet-stream")
163
+
164
+ # Read file content (binary for non-text, text for text)
165
+ try:
166
+ if content_type.startswith("text/") or content_type in ["application/json", "image/svg+xml"]:
167
+ with open(full_path, "r", encoding="utf-8") as f:
168
+ content = f.read()
169
+ else:
170
+ with open(full_path, "rb") as f:
171
+ content = f.read()
172
+ except Exception as e:
173
+ http_code = Constant.HTTP_BAD_REQUEST
174
+ content_type = Constant.TEXT_PLAIN
175
+ content = f"Error reading file: {str(e)}"
176
+ return Response(content, http_code, content_type)
177
+
178
+ http_code = Constant.HTTP_OK
179
+ return Response(content, http_code, content_type)
180
+
181
+ @staticmethod
182
+ def add_header(key, value):
183
+ """
184
+ Adds a header for the response
185
+ :param key:
186
+ :param value:
187
+ :return:
188
+ """
189
+ global headers
190
+ headers[key] = value
191
+
192
+ @staticmethod
193
+ def wsdl(wsdl_instance):
194
+ """
195
+ Sets the response for a WSDL/SOAP handler.
196
+
197
+ This method handles WSDL and SOAP responses by calling the handle method of the provided
198
+ WSDL instance, setting the content type to 'text/xml', and updating the response content
199
+ and status code accordingly.
200
+
201
+ Args:
202
+ wsdl_instance: Instance of a WSDL subclass (e.g., CIS(request)).
203
+
204
+ Returns:
205
+ Self (the Response object) with updated content, headers, and status.
206
+ """
207
+ xml_content = wsdl_instance.handle()
208
+
209
+ return Response(xml_content, Constant.HTTP_OK, Constant.APPLICATION_XML)