easy-utils-dev 2.168__tar.gz → 2.170__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.168 → easy_utils_dev-2.170}/PKG-INFO +1 -1
  2. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/generate_license.py +2 -2
  3. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/uiserver.py +29 -16
  4. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/PKG-INFO +1 -1
  5. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/SOURCES.txt +0 -1
  6. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/setup.py +1 -1
  7. easy_utils_dev-2.168/easy_utils_dev/uiserver-VM026441.py +0 -598
  8. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/MANIFEST.in +0 -0
  9. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/EasySsh.py +0 -0
  10. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/Events.py +0 -0
  11. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/FastQueue.py +0 -0
  12. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/NameObject.py +0 -0
  13. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/__init__.py +0 -0
  14. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/abortable.py +0 -0
  15. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/brevosmtp.py +0 -0
  16. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/check_license.py +0 -0
  17. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/cplib.py +0 -0
  18. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/custom_env.py +0 -0
  19. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/debugger.py +0 -0
  20. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/easy_oracle.py +0 -0
  21. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/encryptor.py +0 -0
  22. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept.py +0 -0
  23. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  24. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  25. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/exceptions.py +0 -0
  26. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/filescompressor.py +0 -0
  27. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/keycloakapi.py +0 -0
  28. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/lralib.py +0 -0
  29. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ne1830PSS.py +0 -0
  30. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/nsp_kafka.py +0 -0
  31. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/openid_server.py +0 -0
  32. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/optics_utils.py +0 -0
  33. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/require_auth.py +0 -0
  34. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/simple_sqlite.py +0 -0
  35. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/temp_memory.py +0 -0
  36. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/utils.py +0 -0
  37. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/winserviceapi.py +0 -0
  38. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/wsnoclib.py +0 -0
  39. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/wsselib.py +0 -0
  40. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  41. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/requires.txt +0 -0
  42. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/top_level.txt +0 -0
  43. {easy_utils_dev-2.168 → easy_utils_dev-2.170}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.168
3
+ Version: 2.170
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,6 +99,7 @@ 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'))
@@ -113,7 +114,7 @@ class Response :
113
114
  elif toast :
114
115
  role = 'toast'
115
116
  timestamp = getTimestamp()
116
- r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
117
+ r = {'status' : 200 , 'ok' : True , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
117
118
  if role :
118
119
  self._emit( r , role, options)
119
120
  return r
@@ -129,7 +130,7 @@ class Response :
129
130
  elif toast :
130
131
  role = 'toast'
131
132
  timestamp = getTimestamp()
132
- r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
133
+ r = {'status' : 400 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
133
134
  if role :
134
135
  self._emit( r , role , options)
135
136
  return r
@@ -141,7 +142,7 @@ class Response :
141
142
  elif toast :
142
143
  role = 'toast'
143
144
  timestamp = getTimestamp()
144
- r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
145
+ r = {'status' : 500 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
145
146
  if role :
146
147
  self._emit( r , role , options)
147
148
  return r
@@ -153,10 +154,15 @@ class Response :
153
154
  elif toast :
154
155
  role = 'toast'
155
156
  timestamp = getTimestamp()
156
- r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
157
+ r = {'status' : 404 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
157
158
  if role :
158
159
  self._emit( r , role , options )
159
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
160
166
 
161
167
  def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
162
168
  role = None
@@ -165,7 +171,7 @@ class Response :
165
171
  elif toast :
166
172
  role = 'toast'
167
173
  timestamp = getTimestamp()
168
- r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
174
+ r = {'status' : 401 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
169
175
  if role :
170
176
  self._emit( r , role , options)
171
177
  return r
@@ -229,7 +235,8 @@ class UISERVER :
229
235
  ssl_crt=None,
230
236
  ssl_key=None,
231
237
  template_folder='templates/' ,
232
- static_folder = 'templates/assets'
238
+ static_folder = 'templates/assets',
239
+ socketio_async_mode = "threading"
233
240
  ,**kwargs
234
241
  ) -> None:
235
242
  self.id = id
@@ -252,22 +259,24 @@ class UISERVER :
252
259
  self.return_exception_as_code_400 = True
253
260
  self.request_reply_base_url= '/request/result'
254
261
  self.sessions = {}
262
+ self.socketio_async_mode = socketio_async_mode
255
263
  if https :
256
264
  self.httpProtocol = 'https'
257
265
  else :
258
266
  self.httpProtocol = 'http'
259
267
  self.socketio = SocketIO(
260
- app , cors_allowed_origins="*" ,
261
- async_mode='threading' ,
262
- engineio_logger=False ,
263
- always_connect=True ,
264
- manage_session=True,
265
- **kwargs
266
- )
268
+ app , cors_allowed_origins="*" ,
269
+ async_mode=self.socketio_async_mode ,
270
+ engineio_logger=False ,
271
+ always_connect=True ,
272
+ manage_session=True,
273
+ **kwargs
274
+ )
267
275
  cenv[id] = self
268
276
  self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
269
277
  self.cache = TemporaryMemory()
270
278
  start_thread(target=self.delete_very_old_requests)
279
+ self.app.config['TEMPLATES_AUTO_RELOAD'] = True
271
280
  self.secret_key_execlude_urls = []
272
281
  self.socketio_rooms = {}
273
282
  self.log_url_requests = True
@@ -280,6 +289,8 @@ class UISERVER :
280
289
  name='easy_utils_dev_uiserver',
281
290
  homePath=TMP_PATH
282
291
  )
292
+ self.start_before_request()
293
+
283
294
  def update_cert(self , crt, ssl ) :
284
295
  self.ssl_crt=crt
285
296
  self.ssl_key=ssl
@@ -378,7 +389,7 @@ class UISERVER :
378
389
 
379
390
  @self.app.before_request
380
391
  def before_request() :
381
- if self.log_url_requests and self.logger :
392
+ if self.log_url_requests and self.logger and request.method != 'OPTIONS' :
382
393
  self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
383
394
 
384
395
  if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
@@ -482,7 +493,7 @@ class UISERVER :
482
493
  response.headers['abortable'] = True
483
494
  except :
484
495
  response.headers['abortable'] = False
485
- if self.log_url_requests and self.logger :
496
+ if self.log_url_requests and self.logger and request.method != 'OPTIONS':
486
497
  self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
487
498
  return response
488
499
 
@@ -550,6 +561,9 @@ class UISERVER :
550
561
  def test_connection():
551
562
  return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
552
563
 
564
+ @self.app.route('/api/v1/_error/<key>' , methods=['GET'])
565
+ def get_error_message(key):
566
+ return self.Response.ok(message=self.Response.cache.get(key))
553
567
 
554
568
  if self.httpProtocol == 'http' :
555
569
  con = None
@@ -576,7 +590,6 @@ class UISERVER :
576
590
 
577
591
 
578
592
  def startUi(self , daemon=False , suppress_prints=True , block=False) :
579
- self.start_before_request()
580
593
  self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
581
594
  self.flaskprocess.daemon = daemon
582
595
  self.flaskprocess.start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.168
3
+ Version: 2.170
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.168'
3
+ VERSION = '2.170'
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