easy-utils-dev 2.167__tar.gz → 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 (43) hide show
  1. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/PKG-INFO +1 -1
  2. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/generate_license.py +2 -2
  3. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/uiserver.py +21 -5
  4. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/PKG-INFO +1 -1
  5. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/SOURCES.txt +0 -1
  6. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/setup.py +1 -1
  7. easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +0 -598
  8. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/MANIFEST.in +0 -0
  9. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/EasySsh.py +0 -0
  10. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/Events.py +0 -0
  11. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/FastQueue.py +0 -0
  12. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/NameObject.py +0 -0
  13. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/__init__.py +0 -0
  14. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/abortable.py +0 -0
  15. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/brevosmtp.py +0 -0
  16. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/check_license.py +0 -0
  17. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/cplib.py +0 -0
  18. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/custom_env.py +0 -0
  19. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/debugger.py +0 -0
  20. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/easy_oracle.py +0 -0
  21. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/encryptor.py +0 -0
  22. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept.py +0 -0
  23. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  24. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  25. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/exceptions.py +0 -0
  26. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/filescompressor.py +0 -0
  27. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/keycloakapi.py +0 -0
  28. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/lralib.py +0 -0
  29. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ne1830PSS.py +0 -0
  30. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/nsp_kafka.py +0 -0
  31. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/openid_server.py +0 -0
  32. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/optics_utils.py +0 -0
  33. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/require_auth.py +0 -0
  34. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/simple_sqlite.py +0 -0
  35. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/temp_memory.py +0 -0
  36. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/utils.py +0 -0
  37. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/winserviceapi.py +0 -0
  38. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/wsnoclib.py +0 -0
  39. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/wsselib.py +0 -0
  40. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  41. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/requires.txt +0 -0
  42. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/top_level.txt +0 -0
  43. {easy_utils_dev-2.167 → easy_utils_dev-2.169}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.167
3
+ Version: 2.169
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -45,9 +45,9 @@ def generate_license(appname, uuid=None, expire_date=None, level="basic",write_f
45
45
  hashed = hashlib.sha256(combined_string.encode()).digest()
46
46
  license_key = base64.b64encode(hashed).decode()
47
47
  if method == 1 :
48
- license_key = license_key+'||' + expire_date
48
+ license_key = license_key+'||' + expire_date + "||" + enc.en_base64(appname)
49
49
  elif method == 2 :
50
- license_key = license_key+'||' + expire_date + "||" + ",".join(_features)
50
+ license_key = license_key+'||' + expire_date + "||" + ",".join(_features) + "||" + enc.en_base64(appname)
51
51
  if write_file :
52
52
  with open('./license.dat' , 'w') as f :
53
53
  f.write(license_key)
@@ -99,9 +99,13 @@ class Response :
99
99
  self.failure = self.error
100
100
  self.socket = socket
101
101
  self.request = request
102
+ self.cache = TemporaryMemory()
102
103
 
103
104
  def _emit(self , data, role , options={} ) :
104
105
  self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
106
+
107
+ def _emit_redirect(self , url , options={} ) :
108
+ self.socket.emit('/stream/redirect' , { 'url' : url , '_options' : options } , to=self.request.headers.get('sid'))
105
109
 
106
110
  def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
107
111
  role = None
@@ -110,11 +114,15 @@ class Response :
110
114
  elif toast :
111
115
  role = 'toast'
112
116
  timestamp = getTimestamp()
113
- r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
117
+ r = {'status' : 200 , 'ok' : True , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
114
118
  if role :
115
119
  self._emit( r , role, options)
116
120
  return r
117
121
 
122
+ def redirect(self , url , options={} ) :
123
+ self._emit_redirect( url , options )
124
+ return {'status' : 401 , 'message' : 'redirected' , 'url' : url }
125
+
118
126
  def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
119
127
  role = None
120
128
  if alert :
@@ -122,7 +130,7 @@ class Response :
122
130
  elif toast :
123
131
  role = 'toast'
124
132
  timestamp = getTimestamp()
125
- r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
133
+ r = {'status' : 400 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
126
134
  if role :
127
135
  self._emit( r , role , options)
128
136
  return r
@@ -134,7 +142,7 @@ class Response :
134
142
  elif toast :
135
143
  role = 'toast'
136
144
  timestamp = getTimestamp()
137
- r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
145
+ r = {'status' : 500 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
138
146
  if role :
139
147
  self._emit( r , role , options)
140
148
  return r
@@ -146,10 +154,15 @@ class Response :
146
154
  elif toast :
147
155
  role = 'toast'
148
156
  timestamp = getTimestamp()
149
- r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
157
+ r = {'status' : 404 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
150
158
  if role :
151
159
  self._emit( r , role , options )
152
160
  return r
161
+
162
+ def store_error_message(self , key , message ) :
163
+ key = generateToken(128)
164
+ self.cache.set(key , message)
165
+ return key
153
166
 
154
167
  def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
155
168
  role = None
@@ -158,7 +171,7 @@ class Response :
158
171
  elif toast :
159
172
  role = 'toast'
160
173
  timestamp = getTimestamp()
161
- r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
174
+ r = {'status' : 401 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
162
175
  if role :
163
176
  self._emit( r , role , options)
164
177
  return r
@@ -543,6 +556,9 @@ class UISERVER :
543
556
  def test_connection():
544
557
  return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
545
558
 
559
+ @self.app.route('/api/v1/_error/<key>' , methods=['GET'])
560
+ def get_error_message(key):
561
+ return self.Response.ok(message=self.Response.cache.get(key))
546
562
 
547
563
  if self.httpProtocol == 'http' :
548
564
  con = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.167
3
+ Version: 2.169
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -26,7 +26,6 @@ easy_utils_dev/optics_utils.py
26
26
  easy_utils_dev/require_auth.py
27
27
  easy_utils_dev/simple_sqlite.py
28
28
  easy_utils_dev/temp_memory.py
29
- easy_utils_dev/uiserver-VM026441.py
30
29
  easy_utils_dev/uiserver.py
31
30
  easy_utils_dev/utils.py
32
31
  easy_utils_dev/winserviceapi.py
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '2.167'
3
+ VERSION = '2.169'
4
4
 
5
5
  # Setting up
6
6
  setup(
@@ -1,598 +0,0 @@
1
- import gc
2
- import json
3
- import time
4
- from flask.ctx import F
5
- from werkzeug.serving import ThreadedWSGIServer
6
- from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
7
- from flask_socketio import SocketIO
8
- from engineio.async_drivers import gevent
9
- from engineio.async_drivers import threading as threading_engineio
10
- from flask_cors import CORS
11
- import logging , os
12
- from flask import jsonify, request , current_app , copy_current_request_context
13
- from flask import Flask
14
- from threading import Thread
15
- import threading
16
- from easy_utils_dev.custom_env import cenv
17
- from easy_utils_dev.utils import kill_thread
18
- from multiprocessing import Process
19
- import traceback
20
- from werkzeug.serving import make_ssl_devcert
21
- from time import sleep
22
- from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs , lget
23
- from easy_utils_dev.temp_memory import TemporaryMemory
24
- from easy_utils_dev.debugger import DEBUGGER
25
- import signal
26
- import sys
27
- from tempfile import gettempdir
28
- from urllib.parse import urlparse, parse_qs
29
-
30
- TMP_PATH = gettempdir()
31
-
32
-
33
- def extract_buid(url: str , key ) -> str | None:
34
- """
35
- Extracts the 'buid' query parameter from a URL.
36
- Returns None if not found.
37
- """
38
- parsed = urlparse(url)
39
- query_params = parse_qs(parsed.query)
40
- return query_params.get( key , [None])[0]
41
-
42
-
43
- def getClassById( id ) :
44
- return cenv[id]
45
-
46
- def create_ssl(host,output) :
47
- '''
48
- host : is the IP/Adress of the server which servers the web-server
49
- output: the output locaiton to generate the ssl certificate. it should end with filename without extension
50
- '''
51
- return make_ssl_devcert( output , host=host)
52
-
53
- def clone_request(request):
54
- """Return a plain dict clone of Flask request data."""
55
- return {
56
- "method": request.method,
57
- "path": request.path,
58
- "url": request.url,
59
- "headers": dict(request.headers),
60
- "args": request.args.to_dict(flat=False),
61
- "form": request.form.to_dict(flat=False),
62
- "json": request.get_json(silent=True),
63
- "data": request.get_data(), # raw body bytes
64
- "files": {k: v.filename for k, v in request.files.items()},
65
- "remote_addr": request.remote_addr,
66
- "cookies": request.cookies,
67
- }
68
-
69
- class Stream :
70
- def __init__(self) :
71
- self.rows = []
72
-
73
- def register_row(self , row , options={}) :
74
- _row_stream_id = getRandomKeysAndStr(n=20)
75
- row['_row_stream_id'] = _row_stream_id
76
- for key , value in options.items() :
77
- if not key.startswith('_') :
78
- raise ValueError(f"Option key '{key}' must start with '_'")
79
- row[key] = value
80
- return row
81
-
82
- def send_cell_update(self , row , cell , value , sid=None ) :
83
- _row_stream_id = row['_row_stream_id']
84
- self.socketio.emit(f'/stream/{_row_stream_id}/{cell}' , value , to=sid)
85
-
86
- def send_new_row(self , tid , row , sid=None , insert_ontop=False ) :
87
- if not row.get('_row_stream_id') :
88
- row = self.register_table_row_stream(row)
89
- self.socketio.emit(f'/stream/table/{tid}/row' , {
90
- 'row' : row,
91
- 'options' : {
92
- 'insert_ontop' : insert_ontop
93
- }
94
- } , to=sid )
95
-
96
- class Response :
97
- def __init__(self, socket : SocketIO , request : request ) :
98
- self.success = self.ok
99
- self.failure = self.error
100
- self.socket = socket
101
- self.request = request
102
-
103
- def _emit(self , data, role , options={} ) :
104
- self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
105
-
106
- def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
107
- role = None
108
- if alert :
109
- role = 'alert'
110
- elif toast :
111
- role = 'toast'
112
- timestamp = getTimestamp()
113
- r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
114
- if role :
115
- self._emit( r , role, options)
116
- return r
117
-
118
- def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
119
- role = None
120
- if alert :
121
- role = 'alert'
122
- elif toast :
123
- role = 'toast'
124
- timestamp = getTimestamp()
125
- r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
126
- if role :
127
- self._emit( r , role , options)
128
- return r
129
-
130
- def internal_error(self , message=None , alert=False, toast=False , options={} , **kwargs ) :
131
- role = None
132
- if alert :
133
- role = 'alert'
134
- elif toast :
135
- role = 'toast'
136
- timestamp = getTimestamp()
137
- r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
138
- if role :
139
- self._emit( r , role , options)
140
- return r
141
-
142
- def not_found(self , message=None , alert=False, toast=False , options={} , **kwargs) :
143
- role = None
144
- if alert :
145
- role = 'alert'
146
- elif toast :
147
- role = 'toast'
148
- timestamp = getTimestamp()
149
- r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
150
- if role :
151
- self._emit( r , role , options )
152
- return r
153
-
154
- def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
155
- role = None
156
- if alert :
157
- role = 'alert'
158
- elif toast :
159
- role = 'toast'
160
- timestamp = getTimestamp()
161
- r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
162
- if role :
163
- self._emit( r , role , options)
164
- return r
165
-
166
- class AbortRequest :
167
- def __init__(self, request ) :
168
- self.request = clone_request(request)
169
- self.abort_id = None
170
- self.abortable = False
171
- self.thread = None
172
- self.cache = None
173
- self.start_ts = getTimestamp()
174
- self.result = None
175
- self.async_request = False
176
- self.internalid = None
177
- self.in_progress = False
178
- self.start_ts = None
179
- self.end_ts = None
180
- self.execution = None
181
- self.success = None
182
- self.traceback = None
183
- self.delete_async_request_ts = None
184
- self.killed = False
185
-
186
- def abort(self) :
187
- kill_thread(self.thread)
188
- self.killed = True
189
- self.in_progress = False
190
- self.success = False
191
- self.traceback = None
192
- self.result = None
193
- self.delete_async_request_ts = getTimestamp(after_seconds=3600)
194
- self.end_ts = getTimestamp()
195
- self.execution = round(self.end_ts - self.start_ts, 2)
196
- if not self.async_request :
197
- self.cache.delete(self.abort_id)
198
- try :
199
- gc.collect()
200
- except :
201
- pass
202
-
203
- class SocketClientObject :
204
- def __init__(self ) :
205
- self.client : SocketIO
206
- self.internalid : str = None
207
- self.request = {}
208
- self.sid = None
209
- self.rooms = None
210
- self.csid = None
211
- self.browserid = None
212
-
213
- class UISERVER :
214
- def __init__(self ,
215
- logger : DEBUGGER = None,
216
- id=getRandomKey(n=15),
217
- secretkey=generateToken(),
218
- serve_with_secret_key=False,
219
- address='localhost',
220
- port=5312 ,
221
- https=False ,
222
- ssl_crt=None,
223
- ssl_key=None,
224
- template_folder='templates/' ,
225
- static_folder = 'templates/assets'
226
- ,**kwargs
227
- ) -> None:
228
- self.id = id
229
- self.static_folder = static_folder
230
- self.app = app = Flask(self.id , template_folder=template_folder , static_folder=self.static_folder )
231
- app.config['SECRET_KEY'] = secretkey
232
- CORS(app,resources={r"/*":{"origins":"*"}})
233
- self.address= address
234
- self.port = port
235
- self.thread = None
236
- self.ssl_crt=ssl_crt
237
- self.ssl_key=ssl_key
238
- self.serve_with_secret_key=serve_with_secret_key
239
- self.secretkey=secretkey
240
- self.enable_test_url=True
241
- self.abort_requests = {}
242
- self.bg_requests = {}
243
- self.socketio_clients = {}
244
- self.abort_base_url = '/request/abort'
245
- self.return_exception_as_code_400 = True
246
- self.request_reply_base_url= '/request/result'
247
- self.sessions = {}
248
- if https :
249
- self.httpProtocol = 'https'
250
- else :
251
- self.httpProtocol = 'http'
252
- self.socketio = SocketIO(
253
- app , cors_allowed_origins="*" ,
254
- async_mode='threading' ,
255
- engineio_logger=False ,
256
- always_connect=True ,
257
- manage_session=True,
258
- **kwargs
259
- )
260
- cenv[id] = self
261
- self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
262
- self.cache = TemporaryMemory()
263
- start_thread(target=self.delete_very_old_requests)
264
- self.secret_key_execlude_urls = []
265
- self.socketio_rooms = {}
266
- self.log_url_requests = True
267
- self.logger = logger
268
- self.stream = Stream()
269
- self.simulate_network_delay = False
270
- self.Response = Response(self.socketio , request )
271
- if not self.logger :
272
- self.logger = DEBUGGER(
273
- name='easy_utils_dev_uiserver',
274
- homePath=TMP_PATH
275
- )
276
- def update_cert(self , crt, ssl ) :
277
- self.ssl_crt=crt
278
- self.ssl_key=ssl
279
-
280
- def register_abortable_request(self , request , abort_id = None ) :
281
- path = request.path
282
- Abort = AbortRequest(request)
283
- if not path.startswith(self.abort_base_url) :
284
- if not abort_id :
285
- if not request.headers.get('abortid') :
286
- abort_id = getRandomKeysAndStr(n=20)
287
- else :
288
- abort_id = request.headers.get('abortid')
289
-
290
- Abort.abort_id = abort_id
291
- current_thread = threading.current_thread()
292
- Abort.thread = current_thread
293
- Abort.cache = self.cache
294
- Abort.start_ts = getTimestamp()
295
- self.cache.set( Abort , custom_key=abort_id , auto_destroy_period=120 , store_deleted_key=False )
296
- return Abort
297
-
298
- def delete_very_old_requests(self) :
299
- while True :
300
- sleep(320)
301
- now = getTimestamp()
302
- for key, value in list(self.bg_requests.items()) :
303
- value : AbortRequest = value
304
- if value.delete_async_request_ts :
305
- if value.delete_async_request_ts > now :
306
- del self.bg_requests[key]
307
- gc.collect()
308
-
309
- def create_room(self , room_id : str , members : list[str] ) :
310
- if room_id in list(self.socketio_rooms.keys()) :
311
- return
312
- self.socketio_rooms[room_id] = members
313
-
314
- def add_member_to_room(self , room_id : str , member : str ) :
315
- self.socketio_rooms[room_id].append(member)
316
-
317
- def remove_member_from_room(self , room_id : str , member : str ) :
318
- self.socketio_rooms[room_id].remove(member)
319
-
320
- def get_room_members(self , room_id : str ) :
321
- return self.socketio_rooms[room_id]
322
-
323
- def start_before_request(self) :
324
-
325
- @self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
326
- def abort_request(id : str ) :
327
- abort : AbortRequest = self.cache.get(id)
328
- timestamp = getTimestamp()
329
- if abort :
330
- abort.abort()
331
- for i in range(30) :
332
- th = abort.thread
333
- alive = th.is_alive()
334
- if not alive :
335
- break
336
- time.sleep(.25)
337
- return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
338
-
339
- else :
340
- return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
341
-
342
- @self.app.route(f"/request/traceback/<key>" , methods=['GET'])
343
- def get_traceback(key : str ) :
344
- traceback = self.cache.get(key)
345
- if traceback :
346
- return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
347
- else :
348
- return self.Response.not_found(message='Traceback not found or expired')
349
-
350
- @self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
351
- def get_result_of_async_request(id : str ) :
352
- request : AbortRequest = self.bg_requests.get(id)
353
- if request :
354
- return self.Response.ok(
355
- message='Result of async request found' ,
356
- result=request.result ,
357
- in_progress=request.in_progress ,
358
- async_request=request.async_request ,
359
- internalid=request.internalid ,
360
- start_ts=request.start_ts ,
361
- end_ts=request.end_ts ,
362
- execution=request.execution ,
363
- success=request.success ,
364
- killed=request.killed
365
- )
366
-
367
-
368
- @self.app.before_request
369
- def before_request() :
370
- if self.log_url_requests and self.logger :
371
- self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
372
-
373
- if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
374
- return self.Response.unauthorized(message='Secret key is invalid')
375
-
376
- @copy_current_request_context
377
- def run_async_job_results( target_func , abort : AbortRequest ) :
378
- abort.in_progress = True
379
- abort.async_request = True
380
- abort.internalid = request.internalid
381
- abort.start_ts = getTimestamp()
382
- try :
383
- result = target_func(*request.args, **request.form)
384
- abort.success = True
385
- except Exception as e :
386
- abort.success = False
387
- abort.result = str(e)
388
- abort.traceback = traceback.format_exc()
389
- raise
390
- abort.result = result
391
- abort.end_ts = getTimestamp()
392
- abort.execution = round(abort.end_ts - abort.start_ts, 2)
393
- abort.in_progress = False
394
- abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
395
-
396
- abortable = request.headers.get('abortable')
397
- requestId = getRandomKeysAndStr(n=10)
398
- request.start_ts = getTimestamp()
399
- request.internalid = requestId
400
- if abortable :
401
- abort = self.register_abortable_request(request)
402
- request.abortable = True
403
- request.abort_id = abort.abort_id
404
- # check here if async in the headers
405
- # if yes . i will trigger the function in thread
406
- # start_tread(#how to get the target function here ? )
407
- # now i want to return response to UI { status : 200 , message : 'request now in running bg' , abort_id : abort.abort_id }
408
- # the flask function should not be called again
409
- if request.headers.get('async') == 'false' :
410
- target_func = current_app.view_functions.get(request.endpoint)
411
- if not target_func:
412
- return self.Response.not_found(message='Route not found')
413
- th = start_thread(target=run_async_job_results, args=[target_func , abort ])
414
- abort.thread = th
415
- self.bg_requests[requestId] = abort
416
- return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
417
-
418
- if self.return_exception_as_code_400 :
419
- @self.app.errorhandler(Exception)
420
- def handle_exception(e):
421
-
422
- exc_type, exc_value, exc_traceback = sys.exc_info()
423
- key = getRandomKeysAndStr(n=10)
424
- tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
425
- # Example: file, line, function, text
426
- error_file = tb_last.filename
427
- error_line = tb_last.lineno
428
- error_func = tb_last.name
429
- error_code = tb_last.line
430
-
431
- # Log the full traceback (optional)
432
- traceback.print_exc()
433
- # Customize the error response
434
- t = getTimestamp()
435
- response = {
436
- "status" : 400 ,
437
- "key" : key,
438
- "error": str(e),
439
- "message": str(e),
440
- "type": type(e).__name__ ,
441
- "request_full_url" : request.url ,
442
- "request_method" : request.method ,
443
- "endpoint" : request.endpoint ,
444
- "request_path" : request.path ,
445
- "error_file" : os.path.basename(error_file).replace('.py' , ''),
446
- "error_line" : error_line,
447
- "error_func" : error_func,
448
- "error_code" : error_code,
449
- 'timestamp' : t ,
450
- "date" : convertTimestampToDate(t) ,
451
- 'traceback_url' : f"/request/traceback/{key}"
452
- }
453
- self.logger.error(f'error: {json.dumps(response , indent=4)}')
454
- self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
455
- return self.Response.error( **response )
456
-
457
- @self.app.after_request
458
- def after_request(response) :
459
-
460
- if self.simulate_network_delay :
461
- time.sleep(self.simulate_network_delay)
462
- now = getTimestamp()
463
- x = round(now - request.start_ts, 2)
464
- try :
465
- response.headers['internalid'] = request.internalid
466
- response.headers['start_ts'] = request.start_ts
467
- response.headers['end_ts'] = now
468
- response.headers['execution'] = x
469
- if request.abortable :
470
- response.headers['abortid'] = request.abort_id
471
- response.headers['abortable'] = True
472
- except :
473
- response.headers['abortable'] = False
474
- if self.log_url_requests and self.logger :
475
- self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
476
- return response
477
-
478
-
479
- # socketio client connected.
480
- @self.socketio.on('connect')
481
- def handle_client_connect():
482
- sid = request.sid
483
- client = SocketClientObject()
484
- client.internalid = getRandomKeysAndStr(n=20)
485
- client.sid = sid
486
- client.request = request
487
- client.browserid = extract_buid( request.url , 'buid')
488
- self.create_room(client.browserid , [client.sid])
489
- client.rooms = self.socketio_rooms
490
- self.socketio_clients[sid] = client
491
- self.socketio.emit('/internal/connect', {
492
- 'status': 200,
493
- 'message': 'client connected' ,
494
- 'internalid' : client.internalid,
495
- 'sid' : sid,
496
- },
497
- to=sid
498
- )
499
- self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
500
-
501
- # socketio client connected.
502
- @self.socketio.on('disconnect')
503
- def handle_client_disconnect():
504
- if request.sid in list(self.socketio_clients.keys()) :
505
- del self.socketio_clients[request.sid]
506
- self.logger.info(f'Disconnected : {request.sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
507
-
508
-
509
- def getSocketio( self ) -> SocketIO:
510
- return self.socketio
511
-
512
- def getFlask( self ) -> Flask:
513
- return self.app
514
-
515
- def shutdownUi(self) :
516
- if hasattr(self, 'wsgi_server') and self.wsgi_server:
517
- try:
518
- self.wsgi_server.shutdown()
519
- self.wsgi_server.server_close()
520
- except:
521
- pass
522
- kill_thread(self.thread)
523
-
524
- def _wait_th(self , t ) :
525
- # t.join()
526
- while True :
527
- time.sleep(36000)
528
-
529
-
530
- def thrStartUi(self , suppress_prints=True) :
531
- if self.enable_test_url :
532
- if not suppress_prints :
533
- print(f'TEST URL GET-METHOD /connection/test/internal')
534
- @self.app.route('/connection/test/internal' , methods=['GET'])
535
- def test_connection():
536
- return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
537
-
538
-
539
- if self.httpProtocol == 'http' :
540
- con = None
541
- elif self.httpProtocol == 'https' :
542
- con=(self.ssl_crt , self.ssl_key)
543
- self.wsgi_server = wsgi_server = ThreadedWSGIServer(
544
- host = self.address ,
545
- ssl_context=con,
546
- # ssl_context=('ssl.crt', 'ssl.key'),
547
- port = self.port,
548
- app = self.app )
549
- if not suppress_prints :
550
- print(f"web-socket: {self.fullAddress}")
551
- print(f"UI URL : {self.fullAddress}")
552
- log = logging.getLogger('werkzeug')
553
- log.setLevel(logging.ERROR)
554
- wsgi_server.serve_forever()
555
-
556
- def on_ctrl_c(self , sig=None, frame=None):
557
- self.stopUi()
558
- pid = os.getpid()
559
- os.kill(pid, signal.SIGTERM)
560
- sys.exit(0)
561
-
562
-
563
- def startUi(self , daemon=False , suppress_prints=True , block=False) :
564
- self.start_before_request()
565
- self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
566
- self.flaskprocess.daemon = daemon
567
- self.flaskprocess.start()
568
- start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
569
- signal.signal(signal.SIGINT, self.on_ctrl_c )
570
- if block:
571
- self.wait()
572
- return self.thread
573
-
574
- def wait(self):
575
- """
576
- Block the main thread to keep it alive for signal handling (Ctrl+C).
577
- This allows Ctrl+C to be properly detected. Call this after startUi()
578
- if you want signal handling to work.
579
- """
580
- try:
581
- while self.flaskprocess.is_alive():
582
- self.flaskprocess.join(timeout=0.1)
583
- except KeyboardInterrupt:
584
- if not hasattr(self, '_shutting_down'):
585
- self._shutting_down = True
586
- self.on_ctrl_c()
587
- except SystemExit:
588
- # Re-raise SystemExit to allow proper program termination
589
- raise
590
-
591
- def stopUi(self) :
592
- if hasattr(self, 'wsgi_server') and self.wsgi_server:
593
- try:
594
- self.wsgi_server.shutdown()
595
- except:
596
- pass
597
- kill_thread(self.thread)
598
- return True
File without changes