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,392 @@
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: E403,F401,E402
7
+ import asyncio
8
+ import gettext
9
+ import logging
10
+ import os
11
+ import shutil
12
+ import importlib
13
+ import sys
14
+ import threading
15
+ import traceback
16
+ import sass
17
+ from pathlib import Path
18
+ from watchdog.observers import Observer
19
+ from watchdog.events import PatternMatchingEventHandler, FileSystemEvent
20
+ from tina4_python.Router import get
21
+ from tina4_python import Messages, Constant
22
+ from tina4_python.Swagger import Swagger
23
+ from tina4_python.Env import load_env
24
+ from tina4_python.Webserver import Webserver
25
+ from tina4_python.Router import Router
26
+ from tina4_python.Localization import localize
27
+ from tina4_python.Auth import Auth
28
+ from tina4_python.Debug import Debug
29
+ from tina4_python.ShellColors import ShellColors
30
+ from tina4_python.Session import Session
31
+
32
+ _ = gettext.gettext
33
+
34
+ # Server startup messages
35
+ MSG_ASSUMING_ROOT_PATH = _('Assuming root path: {root_path}, library path: {library_path}')
36
+ MSG_LOAD_ALL_THINGS = _('Load all things')
37
+ MSG_SERVER_STARTED = _('Server started http://{host_name}:{port}')
38
+ MSG_SERVER_STOPPED = _('Server stopped.')
39
+ MSG_STARTING_WEBSERVER = _('Starting webserver on {port}')
40
+ MSG_ENTRY_POINT_NAME = _('Entry point name ... {name}')
41
+
42
+ if os.getenv('environment') is not None:
43
+ environment = ".env." + os.getenv('environment')
44
+ else:
45
+ environment = ".env"
46
+
47
+ load_env(environment)
48
+
49
+ debug_level = os.getenv("TINA4_DEBUG_LEVEL", Constant.TINA4_LOG_ALL)
50
+ print(ShellColors.bright_yellow + "Setting debug mode", debug_level, ShellColors.end)
51
+
52
+ if importlib.util.find_spec("jurigged"):
53
+ import jurigged
54
+
55
+ # Define the variable to be used for global routes
56
+ library_path = os.path.dirname(os.path.realpath(__file__))
57
+ root_path = os.path.realpath(os.getcwd())
58
+ sys.path.append(root_path)
59
+
60
+ if not os.path.exists(root_path + os.sep + "logs"):
61
+ os.makedirs(root_path + os.sep + "logs")
62
+
63
+ localize()
64
+
65
+ Debug(Messages.MSG_ASSUMING_ROOT_PATH.format(root_path=root_path, library_path=library_path),
66
+ Constant.TINA4_LOG_INFO)
67
+
68
+ tina4_routes = {}
69
+ tina4_current_request = {}
70
+ tina4_api_key = None
71
+ tina4_auth = Auth(root_path)
72
+
73
+
74
+ # Set up the global exception handler
75
+ def global_exception_handler(exception):
76
+ debug_level = Constant.TINA4_LOG_DEBUG
77
+ error = str(exception)
78
+ tb_str = ''.join(traceback.format_exception(None, exception, exception.__traceback__))
79
+ error_string = "Exception Error: " + error + "\n" + tb_str + "\nYou are seeing this error because Tina4 is in debug mode"
80
+ Debug.error(error_string)
81
+ if (os.getenv("TINA4_DEBUG_LEVEL", [Constant.TINA4_LOG_ALL]) == "[TINA4_LOG_ALL]"
82
+ or debug_level in os.getenv("TINA4_DEBUG_LEVEL", [Constant.TINA4_LOG_ALL])):
83
+ pass
84
+ else:
85
+ error_string = "An exception happened"
86
+ return error_string
87
+
88
+ def start_in_thread(target, exception_hook=None):
89
+ """
90
+ Starts a method in a thread
91
+ :param exception_hook:
92
+ :param target:
93
+ :return:
94
+ """
95
+ if exception_hook is not None:
96
+ threading.excepthook = exception_hook
97
+ thread = threading.Thread(target=target)
98
+ thread.start()
99
+
100
+ token = tina4_auth.get_token({"name": "Tina4"})
101
+ Debug("TEST TOKEN", token, Constant.TINA4_LOG_DEBUG)
102
+ Debug("VALID TOKEN", tina4_auth.valid(token + "a"), Constant.TINA4_LOG_DEBUG)
103
+ Debug("VALID TOKEN", tina4_auth.valid(token), Constant.TINA4_LOG_DEBUG)
104
+ Debug("PAYLOAD", tina4_auth.get_payload(token), Constant.TINA4_LOG_DEBUG)
105
+
106
+ if "API_KEY" in os.environ:
107
+ tina4_api_key = os.environ["API_KEY"]
108
+
109
+ # Hack for local development
110
+ if root_path.count("tina4_python") > 0:
111
+ root_path = root_path.split("tina4_python")[0][:-1]
112
+
113
+ # Make the beginning files for the tina4stack
114
+ if not os.path.exists(root_path + os.sep + "src"):
115
+ os.makedirs(root_path + os.sep + "src" + os.sep + "routes")
116
+ os.makedirs(root_path + os.sep + "src" + os.sep + "scss")
117
+ os.makedirs(root_path + os.sep + "src" + os.sep + "orm")
118
+ os.makedirs(root_path + os.sep + "src" + os.sep + "app")
119
+
120
+ with open(root_path + os.sep + "src" + os.sep + "__init__.py", 'w') as init_file:
121
+ init_file.write('# Start your project here')
122
+ init_file.write('\n')
123
+ if not os.path.isfile(root_path + os.sep + "app.py") and not os.path.isdir(root_path + os.sep + "tina4_python"):
124
+ with open(root_path + os.sep + "app.py", 'w') as app_file:
125
+ app_file.write('# Starting point for tina4_python, you shouldn''t need to change anything here')
126
+ app_file.write('\n')
127
+ app_file.write('from tina4_python import *')
128
+ app_file.write('\n')
129
+
130
+ if not os.path.exists(root_path + os.sep + "src" + os.sep + "app"):
131
+ os.makedirs(root_path + os.sep + "src" + os.sep + "app")
132
+
133
+ # copy over templates if needed - required for errors
134
+ if not os.path.exists(root_path + os.sep + "src" + os.sep + "templates"):
135
+ source_dir = library_path + os.sep + "templates"
136
+ destination_dir = root_path + os.sep + "src" + os.sep + "templates"
137
+ shutil.copytree(source_dir, destination_dir)
138
+
139
+ # copy over public if needed - required for static files like images and logos
140
+ if not os.path.exists(root_path + os.sep + "src" + os.sep + "public"):
141
+ source_dir = library_path + os.sep + "public"
142
+ destination_dir = root_path + os.sep + "src" + os.sep + "public"
143
+ shutil.copytree(source_dir, destination_dir)
144
+
145
+ # please keep in place otherwise autoloading of files does not work nicely, if you want this to work
146
+ # add __init__.py files in your folders
147
+ # ignore F403
148
+ if os.path.exists(root_path + os.sep + "src"):
149
+ try:
150
+ exec("from src import *")
151
+ except ImportError as e:
152
+ Debug("Cannot import src folder", str(e), Constant.TINA4_LOG_ERROR)
153
+ else:
154
+ Debug("Missing src folder", Constant.TINA4_LOG_WARNING)
155
+
156
+ if os.path.exists(root_path + os.sep + "src" + os.sep + "routes"):
157
+ try:
158
+ exec("from src.routes import *")
159
+ except ImportError as e:
160
+ Debug("Cannot import src.routes folder", str(e), Constant.TINA4_LOG_ERROR)
161
+ else:
162
+ Debug("Missing src/routes folder", Constant.TINA4_LOG_WARNING)
163
+
164
+ if os.path.exists(root_path + os.sep + "src" + os.sep + "app"):
165
+ try:
166
+ exec("from src.app import *")
167
+ except ImportError as e:
168
+ Debug("Cannot import src.app folder", str(e), Constant.TINA4_LOG_ERROR)
169
+ else:
170
+ Debug("Missing src/app folder", Constant.TINA4_LOG_WARNING)
171
+
172
+
173
+ # compile sass
174
+ def compile_scss():
175
+ try:
176
+ if os.path.exists(root_path + os.sep + "src" + os.sep + "scss"):
177
+ Debug("Compiling scss", Constant.TINA4_LOG_DEBUG)
178
+ sass.compile(dirname=(root_path + os.sep + 'src' + os.sep + 'scss',
179
+ root_path + os.sep + 'src' + os.sep + 'public' + os.sep + 'css'),
180
+ output_style='compressed')
181
+ except sass.CompileError as E:
182
+ Debug('Error compiling SASS ', E, Constant.TINA4_LOG_ERROR)
183
+
184
+
185
+ compile_scss()
186
+
187
+ class SassCompiler(PatternMatchingEventHandler):
188
+ def on_modified(self, event: FileSystemEvent) -> None:
189
+ if not event.is_directory:
190
+ compile_scss()
191
+
192
+
193
+ if os.path.exists(root_path + os.sep + "src" + os.sep + "scss"):
194
+ observer = Observer()
195
+ event_handler = SassCompiler(patterns=["*.sass", "*.scss"])
196
+ observer.schedule(event_handler, path=root_path + os.sep + "src" + os.sep + "scss", recursive=True)
197
+ observer.start()
198
+ else:
199
+ Debug("Missing scss folder", Constant.TINA4_LOG_WARNING)
200
+
201
+
202
+ # end compile sass
203
+
204
+
205
+ def file_get_contents(file_path):
206
+ return Path(file_path).read_text()
207
+
208
+
209
+ # Add swagger routes
210
+ @get(os.getenv("SWAGGER_ROUTE", "/swagger") + "/swagger.json")
211
+ async def get_swagger_json(request, response):
212
+ json = Swagger.get_json(request)
213
+ return response(json)
214
+
215
+
216
+ @get(os.getenv("SWAGGER_ROUTE", "/swagger"))
217
+ async def get_swagger(request, response):
218
+ html = file_get_contents(
219
+ root_path + os.sep + "src" + os.sep + "public" + os.sep + "swagger" + os.sep + "index.html")
220
+
221
+ html = html.replace("{SWAGGER_ROUTE}", os.getenv("SWAGGER_ROUTE", "/swagger"))
222
+ return response(html)
223
+
224
+
225
+ async def app(scope, receive, send):
226
+ """
227
+ Runs normal hypercorn, uvicorn, granian
228
+ :param scope:
229
+ :param receive:
230
+ :param send:
231
+ :return:
232
+ """
233
+ body = b""
234
+ while True and scope['type'] == 'http' or scope['type'] == 'websocket':
235
+ if scope['type'] != 'websocket':
236
+ message = await receive()
237
+ else:
238
+ message = {'type': 'websocket'}
239
+
240
+ if "body" in message:
241
+ body += message["body"]
242
+ if message['type'] == 'lifespan.startup':
243
+ await send({'type': 'lifespan.startup.complete'})
244
+ return
245
+ elif message['type'] == 'lifespan.shutdown':
246
+ await send({'type': 'lifespan.shutdown.complete'})
247
+ return
248
+ elif message["type"] == "http.disconnect" or message["type"] == "websocket.disconnect":
249
+ return
250
+ elif not message.get("more_body"):
251
+ webserver = Webserver(scope["server"][0], scope["server"][1])
252
+ parsed_headers = {}
253
+ parsed_headers_lowercase = {}
254
+ for header in scope["headers"]:
255
+ parsed_headers[header[0].decode()] = header[1].decode()
256
+ parsed_headers_lowercase[header[0].decode().lower()] = header[1].decode()
257
+
258
+ if "content-length" not in parsed_headers_lowercase and "accept" in parsed_headers_lowercase:
259
+ parsed_headers_lowercase["content-type"] = parsed_headers_lowercase["accept"].split(",")[0]
260
+ parsed_headers["Content-Type"] = parsed_headers_lowercase["content-type"]
261
+
262
+ if "content-length" not in parsed_headers_lowercase:
263
+ parsed_headers_lowercase["content-length"] = 0
264
+ parsed_headers_lowercase["Content-Length"] = parsed_headers_lowercase["content-length"]
265
+
266
+ webserver.headers = parsed_headers
267
+ webserver.lowercase_headers = parsed_headers_lowercase
268
+
269
+ webserver.path = scope["path"] + "?" + scope["query_string"].decode()
270
+
271
+ if "method" in scope:
272
+ webserver.method = scope["method"]
273
+ else:
274
+ webserver.method = "GET"
275
+
276
+ if message["type"] == "http.request":
277
+ webserver.content_raw = body
278
+ else:
279
+ webserver.content_raw = b""
280
+ webserver.content_length = parsed_headers_lowercase["content-length"]
281
+ webserver.router_handler = Router()
282
+
283
+ cookie_list = {}
284
+ if "cookie" in webserver.lowercase_headers:
285
+ cookie_list_temp = webserver.lowercase_headers["cookie"].split(";")
286
+ for cookie_value in cookie_list_temp:
287
+ cookie = cookie_value.split("=", 1)
288
+ cookie_list[cookie[0].strip()] = cookie[1].strip()
289
+
290
+ webserver.cookies = cookie_list
291
+
292
+ # initialize the session
293
+ webserver.session = Session(os.getenv("TINA4_SESSION", "PY_SESS"),
294
+ os.getenv("TINA4_SESSION_FOLDER", root_path + os.sep + "sessions"),
295
+ os.getenv("TINA4_SESSION_HANDLER", "SessionFileHandler")
296
+ )
297
+
298
+ if os.getenv("TINA4_SESSION", "PY_SESS") in webserver.cookies:
299
+ webserver.session.load(webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")])
300
+ else:
301
+ webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = webserver.session.start()
302
+
303
+ tina4_response, tina4_headers = await webserver.get_response(webserver.method, (scope, receive, send), True)
304
+
305
+ if message["type"] != "websocket":
306
+ response_headers = []
307
+ for header in tina4_headers:
308
+ header = header.split(":")
309
+ response_headers.append([header[0].strip().encode(), header[1].strip().encode()])
310
+
311
+ await send({
312
+ 'type': 'http.response.start',
313
+ 'status': tina4_response.http_code,
314
+ 'headers': response_headers,
315
+ })
316
+
317
+ if isinstance(tina4_response.content, str):
318
+ await send({
319
+ 'type': 'http.response.body',
320
+ 'body': tina4_response.content.encode(),
321
+ })
322
+ else:
323
+ await send({
324
+ 'type': 'http.response.body',
325
+ 'body': tina4_response.content,
326
+ })
327
+
328
+
329
+ def run_web_server(in_hostname="localhost", in_port=7145):
330
+ Debug(Messages.MSG_STARTING_WEBSERVER.format(port=in_port), Constant.TINA4_LOG_INFO)
331
+ webserver(in_hostname, in_port)
332
+
333
+
334
+ def webserver(host_name, port):
335
+ """
336
+ Runs the correct webserver
337
+ :param host_name:
338
+ :param port:
339
+ :return:
340
+ """
341
+ if os.getenv('TINA4_DEFAULT_WEBSERVER', 'FALSE').upper() == 'TRUE':
342
+ Debug("Using default webserver", Constant.TINA4_LOG_INFO)
343
+ # runs the built-in webserver (websockets) don't work on windows
344
+ web_server = Webserver(host_name, int(port)) # HTTPServer((host_name, int(port)), Webserver)
345
+ web_server.router_handler = Router()
346
+ # Fix the display to make it clickable
347
+ if host_name == "0.0.0.0":
348
+ host_name = "localhost"
349
+ Debug(Messages.MSG_SERVER_STARTED.format(host_name=host_name, port=port), Constant.TINA4_LOG_INFO)
350
+ try:
351
+ asyncio.run(web_server.serve_forever())
352
+ except KeyboardInterrupt:
353
+ pass
354
+ web_server.server_close()
355
+ else:
356
+ # Runs a hyper corn server
357
+ Debug("Using hypercorn webserver", Constant.TINA4_LOG_INFO)
358
+ try:
359
+ from hypercorn.config import Config
360
+ from hypercorn.asyncio import serve
361
+ config = Config()
362
+ config.bind = [host_name + ":" + str(port)]
363
+ asyncio.run(serve(app, config))
364
+ except Exception as e:
365
+ Debug("Not running Hypercorn webserver", str(e), Constant.TINA4_LOG_WARNING)
366
+
367
+ Debug(Messages.MSG_SERVER_STOPPED, Constant.TINA4_LOG_INFO)
368
+
369
+ if importlib.util.find_spec("jurigged"):
370
+ Debug("Jurigged enabled", Constant.TINA4_LOG_INFO)
371
+ jurigged.watch(["./src/app", "./src/orm", "./src/routes", "./src/templates"])
372
+
373
+ # Start up a webserver based on params passed on the command line
374
+ HOSTNAME = "localhost"
375
+ PORT = 7145
376
+ if len(sys.argv) > 1:
377
+ PORT = sys.argv[1]
378
+ if ":" in PORT:
379
+ SERVER_CONFIG = PORT.split(":")
380
+ HOSTNAME = SERVER_CONFIG[0]
381
+ PORT = SERVER_CONFIG[1]
382
+
383
+ if PORT != "stop" and PORT != "manual":
384
+ try:
385
+ PORT = int(PORT)
386
+ run_web_server(HOSTNAME, PORT)
387
+ except Exception as e:
388
+ Debug("Not running webserver", str(e), Constant.TINA4_LOG_WARNING)
389
+ else:
390
+ Debug("Webserver is set to manual start, please call " + ShellColors.bright_red +
391
+ "run_web_server(<HOSTNAME>, <PORT>)" + ShellColors.end + " in your code",
392
+ Constant.TINA4_LOG_WARNING)
@@ -0,0 +1,83 @@
1
+ # Translations template for PROJECT.
2
+ # Copyright (C) 2024 ORGANIZATION
3
+ # This file is distributed under the same license as the PROJECT project.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PROJECT VERSION\n"
10
+ "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11
+ "POT-Creation-Date: 2024-02-14 18:17+0200\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=utf-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Generated-By: Babel 2.13.1\n"
19
+
20
+ #: Messages.py:5
21
+ msgid "Debug: {message}"
22
+ msgstr ""
23
+
24
+ #: Messages.py:6
25
+ msgid "Warning: {message}"
26
+ msgstr ""
27
+
28
+ #: Messages.py:7
29
+ msgid "Error: {message}"
30
+ msgstr ""
31
+
32
+ #: Messages.py:8
33
+ msgid "Info: {message}"
34
+ msgstr ""
35
+
36
+ #: Messages.py:12
37
+ msgid "Matching: {matching}"
38
+ msgstr ""
39
+
40
+ #: Messages.py:13
41
+ msgid "Variables: {variables}"
42
+ msgstr ""
43
+
44
+ #: Messages.py:14
45
+ msgid "Root Path {root_path} {url}"
46
+ msgstr ""
47
+
48
+ #: Messages.py:15
49
+ msgid "Attempting to serve static file: {static_file}"
50
+ msgstr ""
51
+
52
+ #: Messages.py:16
53
+ msgid "Attempting to serve CSS file: {css_file}"
54
+ msgstr ""
55
+
56
+ #: Messages.py:17
57
+ msgid "Attempting to serve image file: {image_file}"
58
+ msgstr ""
59
+
60
+ #: __init__.py:15
61
+ msgid "Assuming root path: {root_path}, library path: {library_path}"
62
+ msgstr ""
63
+
64
+ #: __init__.py:16
65
+ msgid "Load all things"
66
+ msgstr ""
67
+
68
+ #: __init__.py:17
69
+ msgid "Server started http://{host_name}:{port}"
70
+ msgstr ""
71
+
72
+ #: __init__.py:18
73
+ msgid "Server stopped."
74
+ msgstr ""
75
+
76
+ #: __init__.py:19
77
+ msgid "Starting webserver on {port}"
78
+ msgstr ""
79
+
80
+ #: __init__.py:20
81
+ msgid "Entry point name ... {name}"
82
+ msgstr ""
83
+
File without changes
Binary file
Binary file
Binary file
Binary file
Binary file
File without changes
File without changes