tina4-python 0.2.122__py3-none-any.whl

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 (47) hide show
  1. tina4_python/Auth.py +222 -0
  2. tina4_python/Constant.py +43 -0
  3. tina4_python/Database.py +591 -0
  4. tina4_python/DatabaseResult.py +107 -0
  5. tina4_python/DatabaseTypes.py +15 -0
  6. tina4_python/Debug.py +126 -0
  7. tina4_python/Env.py +37 -0
  8. tina4_python/Localization.py +42 -0
  9. tina4_python/Messages.py +30 -0
  10. tina4_python/MiddleWare.py +90 -0
  11. tina4_python/Migration.py +107 -0
  12. tina4_python/ORM.py +639 -0
  13. tina4_python/Queue.py +615 -0
  14. tina4_python/Request.py +19 -0
  15. tina4_python/Response.py +121 -0
  16. tina4_python/Router.py +423 -0
  17. tina4_python/Session.py +342 -0
  18. tina4_python/ShellColors.py +20 -0
  19. tina4_python/Swagger.py +228 -0
  20. tina4_python/Template.py +107 -0
  21. tina4_python/Webserver.py +429 -0
  22. tina4_python/Websocket.py +49 -0
  23. tina4_python/__init__.py +392 -0
  24. tina4_python/messages.pot +83 -0
  25. tina4_python/public/css/readme.md +0 -0
  26. tina4_python/public/favicon.ico +0 -0
  27. tina4_python/public/images/403.png +0 -0
  28. tina4_python/public/images/404.png +0 -0
  29. tina4_python/public/images/500.png +0 -0
  30. tina4_python/public/images/logo.png +0 -0
  31. tina4_python/public/images/readme.md +0 -0
  32. tina4_python/public/js/readme.md +0 -0
  33. tina4_python/public/js/reconnecting-websocket.js +365 -0
  34. tina4_python/public/js/tina4helper.js +397 -0
  35. tina4_python/public/swagger/index.html +90 -0
  36. tina4_python/public/swagger/oauth2-redirect.html +63 -0
  37. tina4_python/templates/errors/403.twig +10 -0
  38. tina4_python/templates/errors/404.twig +10 -0
  39. tina4_python/templates/errors/500.twig +11 -0
  40. tina4_python/templates/readme.md +1 -0
  41. tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  42. tina4_python/translations/en/LC_MESSAGES/messages.po +80 -0
  43. tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  44. tina4_python/translations/fr/LC_MESSAGES/messages.po +84 -0
  45. tina4_python-0.2.122.dist-info/METADATA +465 -0
  46. tina4_python-0.2.122.dist-info/RECORD +47 -0
  47. tina4_python-0.2.122.dist-info/WHEEL +4 -0
@@ -0,0 +1,121 @@
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 json
8
+ import inspect
9
+ from datetime import datetime, date
10
+ from types import ModuleType
11
+ from tina4_python import Constant
12
+ from tina4_python import DatabaseResult
13
+ from tina4_python.ORM import ORM
14
+ from tina4_python.Template import Template
15
+
16
+ headers = {}
17
+ content = ""
18
+ http_code = Constant.HTTP_OK
19
+ content_type = Constant.TEXT_HTML
20
+
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):
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):
86
+ """
87
+ Redirects a request to redirect_url
88
+ :param redirect_url:
89
+ :return:
90
+ """
91
+ global headers
92
+ global content
93
+ global http_code
94
+ global content_type
95
+ headers = {}
96
+ http_code = Constant.HTTP_REDIRECT
97
+ headers["Location"] = redirect_url
98
+ content = ""
99
+ content_type = Constant.TEXT_HTML
100
+ return Response("", http_code, content_type, headers)
101
+
102
+
103
+ @staticmethod
104
+ def render(template_name, data=None):
105
+ global content, content_type, http_code
106
+ http_code = Constant.HTTP_OK
107
+ content_type = Constant.TEXT_HTML
108
+
109
+ return Response(Template.render(template_name, data=data), http_code, content_type)
110
+
111
+
112
+ @staticmethod
113
+ def add_header(key, value):
114
+ """
115
+ Adds a header for the response
116
+ :param key:
117
+ :param value:
118
+ :return:
119
+ """
120
+ global headers
121
+ headers[key] = value
tina4_python/Router.py ADDED
@@ -0,0 +1,423 @@
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 json
8
+ import mimetypes
9
+ import re
10
+ import os
11
+ import sys
12
+ import io
13
+ import tina4_python
14
+ from tina4_python import Constant
15
+ from tina4_python import Response
16
+ from tina4_python import Request
17
+ from tina4_python.Debug import Debug
18
+ from tina4_python.Template import Template
19
+ from tina4_python.MiddleWare import MiddleWare
20
+
21
+
22
+ class Router:
23
+ variables = None
24
+
25
+ @staticmethod
26
+ def get_variables(url, route_path):
27
+ variables = {}
28
+ url_segments = url.strip('/').split('/')
29
+ route_segments = route_path.strip('/').split('/')
30
+ for i, segment in enumerate(route_segments):
31
+ if '{' in segment: # parameter part
32
+ param_name = re.search(r'{(.*?)}', segment).group(1)
33
+ variables[param_name] = url_segments[i]
34
+ return variables
35
+
36
+ # Matches the URL to the route and extracts the parameters
37
+ @staticmethod
38
+ def match(url, route_path):
39
+ matching = False
40
+ variables = {}
41
+
42
+ # splitting URL and route and putting them into lists to compare
43
+ url_segments = url.strip('/').split('/')
44
+ route_segments = route_path.strip('/').split('/')
45
+
46
+ if len(url_segments) == len(route_segments):
47
+ matching = True
48
+ for i, segment in enumerate(route_segments):
49
+ if '{' in segment: # parameter part
50
+ param_name = re.search(r'{(.*?)}', segment).group(1)
51
+ variables[param_name] = url_segments[i]
52
+ elif segment != url_segments[i]: # non-parameter part
53
+ matching = False
54
+ break
55
+
56
+ Router.variables = variables
57
+
58
+ return matching
59
+
60
+ # Renders the URL and returns the content
61
+ @staticmethod
62
+ async def get_result(url, method, request, headers, session):
63
+ global Request
64
+ global Response
65
+
66
+ Response.headers = {}
67
+ Response.content = ""
68
+ Response.http_code = Constant.HTTP_NOT_FOUND
69
+ Response.content_type = Constant.TEXT_HTML
70
+ result = Response
71
+
72
+ Debug("Root Path " + tina4_python.root_path + " " + url, method, Constant.TINA4_LOG_DEBUG)
73
+ tina4_python.tina4_current_request["url"] = url
74
+ tina4_python.tina4_current_request["headers"] = headers
75
+
76
+ validated = False
77
+ # we can add other methods later but right now we validate posts
78
+ if method in [Constant.TINA4_GET, Constant.TINA4_POST, Constant.TINA4_PUT, Constant.TINA4_PATCH,
79
+ Constant.TINA4_DELETE]:
80
+ content_type = "text/html"
81
+ if "content-type" in headers:
82
+ content_type = headers["content-type"]
83
+
84
+ if content_type == "application/json":
85
+ content = {"error": "403 - Forbidden", "data": {"server": {"url": url}}}
86
+ else:
87
+ content = Template.render_twig_template(
88
+ "errors/403.twig", {"server": {"url": url}})
89
+
90
+ # check to see if we have an auth ability
91
+ if "authorization" in headers:
92
+ token = headers["authorization"].replace("Bearer", "").strip()
93
+ if tina4_python.tina4_auth.valid(token):
94
+ validated = True
95
+
96
+ if request["params"] is not None and "formToken" in request["params"]:
97
+ token = request["params"]["formToken"]
98
+ if tina4_python.tina4_auth.valid(token):
99
+ validated = True
100
+
101
+ if request["body"] is not None and "formToken" in request["body"]:
102
+ token = request["body"]["formToken"]
103
+ if tina4_python.tina4_auth.valid(token):
104
+ validated = True
105
+
106
+ if request["body"] is not None and "formToken" in request["body"]:
107
+ request["params"]["formToken"] = request["body"]["formToken"]
108
+ del request["body"]["formToken"]
109
+
110
+ # split URL and extract query string
111
+ url_parts = url.split('?')
112
+ url = url_parts[0]
113
+
114
+ # Serve statics
115
+ static_file = tina4_python.root_path + os.sep + "src" + os.sep + "public" + url.replace("/", os.sep)
116
+ Debug("Attempting to serve static file: " + static_file, Constant.TINA4_LOG_DEBUG)
117
+ if os.path.isfile(static_file):
118
+ mime_type = mimetypes.guess_type(url)[0]
119
+ with open(static_file, 'rb') as file:
120
+ return Response.Response(file.read(), Constant.HTTP_OK, mime_type)
121
+
122
+ old_stdout = None
123
+ buffer = io.StringIO()
124
+ for route in tina4_python.tina4_routes.values():
125
+ if route["method"] != method:
126
+ continue
127
+ Debug("Matching route " + route['route'] + " to " + url, Constant.TINA4_LOG_DEBUG)
128
+ if Router.match(url, route['route']):
129
+ if not "noauth" in route:
130
+ if "secure" in route or ("swagger" in route and route["swagger"] is not None and "secure" in route["swagger"]):
131
+ if (("secure" in route and route["secure"]) or ("swagger" in route and route["swagger"] is not None and route["swagger"]["secure"])) and not validated:
132
+ return Response.Response(content, Constant.HTTP_FORBIDDEN, Constant.TEXT_HTML)
133
+ else:
134
+ if not validated and method not in [Constant.TINA4_OPTIONS, Constant.TINA4_GET]:
135
+ return Response.Response(content, Constant.HTTP_FORBIDDEN, Constant.TEXT_HTML)
136
+
137
+ router_response = route["callback"]
138
+
139
+ # Add the inline variables & construct a Request variable
140
+ request["params"].update(Router.variables)
141
+
142
+ Request.request = request # Add the request object
143
+ Request.headers = headers # Add the headers
144
+ Request.params = request["params"]
145
+ Request.body = request["body"] if "body" in request else None
146
+ Request.files = request["files"] if "files" in request else None
147
+ Request.session = session
148
+ Request.raw_data = request["raw_data"] if "raw_data" in request else None
149
+ Request.raw_request = request["raw_request"] if "raw_request" in request else None
150
+ Request.raw_content = request["raw_content"] if "raw_content" in request else None
151
+ Request.url = url
152
+ Request.transport = request["transport"] if "transport" in request else None
153
+ Request.asgi_response = request["asgi_response"] if "asgi_response" in request else None
154
+
155
+ tina4_python.tina4_current_request = Request
156
+
157
+ old_stdout = sys.stdout # Memorize the default stdout stream
158
+ sys.stdout = buffer = io.StringIO()
159
+
160
+ if "middleware" in route:
161
+ middleware_runner = MiddleWare(route["middleware"]["class"])
162
+
163
+ if "methods" in route["middleware"] and route["middleware"]["methods"] is not None and len(route["middleware"]["methods"]) > 0:
164
+ for method in route["middleware"]["methods"]:
165
+ Request, Response = middleware_runner.call_direct_method(Request, Response, method)
166
+ else:
167
+ Request, Response = await middleware_runner.call_before_methods(Request, Response)
168
+ Request, Response = await middleware_runner.call_any_methods(Request, Response)
169
+
170
+ try:
171
+ result = await router_response(request=Request, response=Response.Response)
172
+ except Exception as e:
173
+ error_string = tina4_python.global_exception_handler(e)
174
+ if Constant.TINA4_LOG_DEBUG in os.getenv("TINA4_DEBUG_LEVEL") or Constant.TINA4_LOG_ALL in os.getenv("TINA4_DEBUG_LEVEL"):
175
+ html = Template.render_twig_template("errors/500.twig",
176
+ {"server": {"url": url}, "error_message": error_string})
177
+ return Response.Response(html, Constant.HTTP_SERVER_ERROR, Constant.TEXT_HTML)
178
+ else:
179
+ return Response.Response(error_string, Constant.HTTP_SERVER_ERROR, Constant.TEXT_HTML)
180
+
181
+ # we have found a result ... make sure we reflect this if the user didn't actually put the correct http response code in
182
+ if result is not None:
183
+ if result.http_code == Constant.HTTP_NOT_FOUND:
184
+ result.http_code = Constant.HTTP_OK
185
+
186
+ if "middleware" in route:
187
+ middleware_runner = MiddleWare(route["middleware"]["class"])
188
+
189
+ if "methods" in route["middleware"] and route["middleware"]["methods"] is not None and len(route["middleware"]["methods"]) > 0:
190
+ for method in route["middleware"]["methods"]:
191
+ Request, result = middleware_runner.call_direct_method(Request, result, method)
192
+ else:
193
+ Request, result = await middleware_runner.call_after_methods(Request, result)
194
+ Request, result = await middleware_runner.call_any_methods(Request, result)
195
+
196
+ if result is not None:
197
+ result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
198
+ if "cache" in route and route["cache"] is not None:
199
+ if not route["cache"]["cached"]:
200
+ result.headers["Cache-Control"] = "max-age=1, must-revalidate"
201
+ result.headers["Pragma"] = "no-cache"
202
+ else:
203
+ result.headers["Cache-Control"] = "max-age=" + str(
204
+ route["cache"]["max_age"]) + ", must-revalidate"
205
+ result.headers["Pragma"] = "cache"
206
+ else:
207
+ result.headers["Cache-Control"] = "max-age=-1, must-revalidate"
208
+ result.headers["Pragma"] = "cache"
209
+
210
+ break
211
+
212
+ if result is None and old_stdout is not None:
213
+ result = Response
214
+ result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
215
+ sys.stdout = old_stdout
216
+ if buffer.getvalue() != "":
217
+ try:
218
+ return Response.Response(json.loads(buffer.getvalue()), Constant.HTTP_OK, Constant.APPLICATION_JSON, result.headers)
219
+ except Exception:
220
+ return Response.Response(buffer.getvalue(), Constant.HTTP_OK, Constant.TEXT_HTML, result.headers)
221
+ else:
222
+ result = Response
223
+ result.http_code = Constant.HTTP_NOT_FOUND
224
+
225
+ # If no route is matched, serve 404
226
+ if result.http_code == Constant.HTTP_NOT_FOUND:
227
+ # Serve twigs if the files exist
228
+ twig_files = []
229
+ if url == "/":
230
+ twig_files.append("index.twig")
231
+ else:
232
+ twig_files.append(url + ".twig")
233
+ twig_files.append(url + "index.twig")
234
+
235
+ # see if we can find the twig file
236
+ for twig_file in twig_files:
237
+ if os.path.isfile(tina4_python.root_path + os.sep + "src" + os.sep + "templates" + os.sep + twig_file):
238
+ Debug("Looking for twig file",
239
+ tina4_python.root_path + os.sep + "src" + os.sep + "templates" + os.sep + twig_file,
240
+ Constant.TINA4_LOG_DEBUG)
241
+
242
+ result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
243
+ result.headers["Cache-Control"] = "max-age=-1, public"
244
+ result.headers["Pragma"] = "no-cache"
245
+ content = Template.render_twig_template(twig_file, {"request": tina4_python.tina4_current_request})
246
+ if content != "":
247
+ return Response.Response(content, Constant.HTTP_OK, Constant.TEXT_HTML, result.headers)
248
+
249
+ if result.http_code == Constant.HTTP_NOT_FOUND:
250
+ content = Template.render_twig_template(
251
+ "errors/404.twig", {"server": {"url": url}})
252
+ return Response.Response(content, Constant.HTTP_NOT_FOUND, Constant.TEXT_HTML)
253
+
254
+ result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
255
+ return result
256
+
257
+ @staticmethod
258
+ async def resolve(method, url, request, headers, session):
259
+ url = Router.clean_url(url)
260
+ Debug(method, "Resolving URL: " + url, Constant.TINA4_LOG_DEBUG)
261
+ return await Router.get_result(url, method, request, headers, session)
262
+
263
+ # cleans the url of double slashes
264
+ @staticmethod
265
+ def clean_url(url):
266
+ return url.replace('//', '/')
267
+
268
+ # adds a route to the router
269
+ @staticmethod
270
+ def add(method, route, callback):
271
+ Debug("Adding a route: " + route, Constant.TINA4_LOG_DEBUG)
272
+ if callback not in tina4_python.tina4_routes:
273
+ tina4_python.tina4_routes[callback] = {"route": route, "callback": callback, "method": method,
274
+ "swagger": None, "cached": False}
275
+ else:
276
+ tina4_python.tina4_routes[callback]["route"] = route
277
+ tina4_python.tina4_routes[callback]["callback"] = callback
278
+ tina4_python.tina4_routes[callback]["method"] = method
279
+
280
+ if '{' in route: # store the parameters if needed
281
+ route_variables = re.findall(r'{(.*?)}', route)
282
+ tina4_python.tina4_routes[callback]["params"] = route_variables
283
+
284
+
285
+
286
+ def get(path: str):
287
+ """
288
+ Get router
289
+ :param arguments:
290
+ :return:
291
+ """
292
+
293
+ def actual_get(callback):
294
+ route_paths = path.split('|')
295
+ for route_path in route_paths:
296
+ Router.add(Constant.TINA4_GET, route_path, callback)
297
+ return callback
298
+
299
+ return actual_get
300
+
301
+
302
+ def post(path):
303
+ """
304
+ Post router
305
+ :param path:
306
+ :return:
307
+ """
308
+
309
+ def actual_post(callback):
310
+ route_paths = path.split('|')
311
+ for route_path in route_paths:
312
+ Router.add(Constant.TINA4_POST, route_path, callback)
313
+ return callback
314
+
315
+ return actual_post
316
+
317
+
318
+ def put(path):
319
+ """
320
+ Put router
321
+ :param path:
322
+ :return:
323
+ """
324
+
325
+ def actual_put(callback):
326
+ route_paths = path.split('|')
327
+ for route_path in route_paths:
328
+ Router.add(Constant.TINA4_PUT, route_path, callback)
329
+ return callback
330
+
331
+ return actual_put
332
+
333
+
334
+ def patch(path):
335
+ """
336
+ Patch router
337
+ :param path:
338
+ :return:
339
+ """
340
+
341
+ def actual_patch(callback):
342
+ route_paths = path.split('|')
343
+ for route_path in route_paths:
344
+ Router.add(Constant.TINA4_PATCH, route_path, callback)
345
+ return callback
346
+
347
+ return actual_patch
348
+
349
+
350
+ def delete(path):
351
+ """
352
+ Delete router
353
+ :param path:
354
+ :return:
355
+ """
356
+
357
+ def actual_delete(callback):
358
+ route_paths = path.split('|')
359
+ for route_path in route_paths:
360
+ Router.add(Constant.TINA4_DELETE, route_path, callback)
361
+ return callback
362
+
363
+ return actual_delete
364
+
365
+
366
+ def cached(is_cached, max_age=60):
367
+ """
368
+ Sets whether the route is cached or not
369
+ :param is_cached:
370
+ :param max_age:
371
+ :return:
372
+ """
373
+
374
+ def actual_cached(callback):
375
+ if callback not in tina4_python.tina4_routes:
376
+ tina4_python.tina4_routes[callback] = {}
377
+ tina4_python.tina4_routes[callback]["cache"] = {"cached": is_cached, "max_age": max_age}
378
+ return callback
379
+
380
+ return actual_cached
381
+
382
+
383
+ def middleware(middleware, specific_methods=[]):
384
+ """
385
+ Sets middleware for the route and methods that need to be called
386
+ :param middleware:
387
+ :param specific_methods:
388
+ :return:
389
+ """
390
+
391
+ def actual_middleware(callback):
392
+ if callback not in tina4_python.tina4_routes:
393
+ tina4_python.tina4_routes[callback] = {}
394
+ tina4_python.tina4_routes[callback]["middleware"] = {"class": middleware, "methods": specific_methods}
395
+ return callback
396
+
397
+ return actual_middleware
398
+
399
+ def secured():
400
+ """
401
+ Makes a route secure - secured vs secure with swagger
402
+ :return:
403
+ """
404
+ def actual_secure(callback):
405
+ if callback not in tina4_python.tina4_routes:
406
+ tina4_python.tina4_routes[callback] = {}
407
+ tina4_python.tina4_routes[callback]["secure"] = True
408
+ return callback
409
+
410
+ return actual_secure
411
+
412
+ def noauth():
413
+ """
414
+ Defines a route with no auth
415
+ :return:
416
+ """
417
+ def actual_noauth(callback):
418
+ if callback not in tina4_python.tina4_routes:
419
+ tina4_python.tina4_routes[callback] = {}
420
+ tina4_python.tina4_routes[callback]["noauth"] = True
421
+ return callback
422
+
423
+ return actual_noauth