easy-utils-dev 2.165__tar.gz → 2.167__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.
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/PKG-INFO +1 -1
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/exceptions.py +4 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ne1830PSS.py +41 -4
- easy_utils_dev-2.165/easy_utils_dev/uiserver.py → easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +104 -52
- easy_utils_dev-2.167/easy_utils_dev/uiserver.py +606 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/PKG-INFO +1 -1
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/SOURCES.txt +1 -0
- easy_utils_dev-2.167/setup.py +33 -0
- easy_utils_dev-2.165/setup.py +0 -33
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/MANIFEST.in +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/debugger.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/generate_license.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/temp_memory.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/utils.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/wsnoclib.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/requires.txt +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.165 → easy_utils_dev-2.167}/setup.cfg +0 -0
|
@@ -52,5 +52,9 @@ class GmreFailure(Exception) :
|
|
|
52
52
|
super().__init__(message)
|
|
53
53
|
|
|
54
54
|
class PSSError(Exception) :
|
|
55
|
+
def __init__(self ,message=''):
|
|
56
|
+
super().__init__(message)
|
|
57
|
+
|
|
58
|
+
class InvalidPSSElementType(Exception) :
|
|
55
59
|
def __init__(self ,message=''):
|
|
56
60
|
super().__init__(message)
|
|
@@ -20,6 +20,7 @@ class PSS1830 :
|
|
|
20
20
|
file_name=None,
|
|
21
21
|
debug_home_path=None,
|
|
22
22
|
trust_env_log_path=True,
|
|
23
|
+
connect_to_standby_ec=False,
|
|
23
24
|
debugger_kwargs: Optional[DEBUGGER] = {}
|
|
24
25
|
) -> None:
|
|
25
26
|
self.port = None
|
|
@@ -55,7 +56,9 @@ class PSS1830 :
|
|
|
55
56
|
self.full_buffer=''
|
|
56
57
|
self.pssRelease = None
|
|
57
58
|
self.maxConnectAttempt=3
|
|
58
|
-
|
|
59
|
+
|
|
60
|
+
self.connect_to_standby_ec = connect_to_standby_ec
|
|
61
|
+
|
|
59
62
|
self.currentConnectAttempt=0
|
|
60
63
|
self.auto_enable_tcp_forward=auto_enable_tcp_forward
|
|
61
64
|
self.tcpForwardStatus=None
|
|
@@ -485,7 +488,23 @@ class PSS1830 :
|
|
|
485
488
|
return jump_channel
|
|
486
489
|
return None
|
|
487
490
|
|
|
491
|
+
def get_ne_family_type_root(self):
|
|
492
|
+
self.logger.debug(f"Getting NE Family type from root ...")
|
|
493
|
+
if self.sim :
|
|
494
|
+
self.logger.warn(f"Skip get pss family due to sim not supoorted.")
|
|
495
|
+
return "", False
|
|
496
|
+
command = 'cd /pureNeApp/export/home/platform/bin/ ; ./getShelfType'
|
|
497
|
+
result = self.ssh_execute(ssh=self.client , command=command)
|
|
498
|
+
if not result :
|
|
499
|
+
self.logger.error(f"Failed to get NE Family type from root")
|
|
500
|
+
raise exceptions.InvalidPSSElementType("")
|
|
501
|
+
otn = False
|
|
502
|
+
if 'x' in result.lower() :
|
|
503
|
+
otn = True
|
|
504
|
+
return result , otn
|
|
505
|
+
|
|
488
506
|
def switch_to_standby_ec(self) :
|
|
507
|
+
isOtn = None
|
|
489
508
|
if self.connect_to_standby_ec :
|
|
490
509
|
self.logger.info(f"connecting to standby EC {self.neip} ...")
|
|
491
510
|
if not self.sim :
|
|
@@ -494,19 +513,37 @@ class PSS1830 :
|
|
|
494
513
|
raise exceptions.InvalidRemoteEcIp(f"couldn't connect to remote EC")
|
|
495
514
|
if self.sim :
|
|
496
515
|
response = "100.0.81.1"
|
|
516
|
+
isOtn = False
|
|
517
|
+
elif not self.sim :
|
|
518
|
+
family , isOtn = self.get_ne_family_type_root()
|
|
519
|
+
|
|
497
520
|
if response :
|
|
498
521
|
current_ec_ip = response.replace('\n' , '')
|
|
499
|
-
|
|
522
|
+
self.logger.debug(f"Current EC IP={current_ec_ip}")
|
|
523
|
+
switcher_phn = {
|
|
500
524
|
'100.0.81.1' : '100.0.81.18',
|
|
501
525
|
'100.0.81.18' : '100.0.81.1',
|
|
502
526
|
}
|
|
503
|
-
|
|
527
|
+
switcher_otn = {
|
|
528
|
+
'100.0.81.1' : '100.0.81.2',
|
|
529
|
+
'100.0.81.2' : '100.0.81.1',
|
|
530
|
+
}
|
|
531
|
+
self.logger.debug(f"SwitchEC OTN_MODE={isOtn}")
|
|
532
|
+
if isOtn :
|
|
533
|
+
self.logger.debug(f"Using Switcher Obejct={switcher_otn}")
|
|
534
|
+
remote_ec_ip = switcher_otn.get(current_ec_ip)
|
|
535
|
+
else :
|
|
536
|
+
self.logger.debug(f"Using Switcher Obejct={switcher_phn}")
|
|
537
|
+
remote_ec_ip = switcher_phn.get(current_ec_ip)
|
|
538
|
+
self.logger.debug(f"Remote EC IP={current_ec_ip}")
|
|
504
539
|
if not remote_ec_ip :
|
|
505
540
|
self.logger.error(f"couldn't find the standby EC IP for {current_ec_ip}")
|
|
506
541
|
raise exceptions.InvalidRemoteEcIp(f"couldn't find the standby EC IP for {current_ec_ip}")
|
|
542
|
+
self.logger.debug('Creating transport layer for remote EC ...')
|
|
507
543
|
jump_transport = self.client.get_transport()
|
|
508
|
-
dest_addr = ( remote_ec_ip , 5122 )
|
|
544
|
+
dest_addr = ( remote_ec_ip , 5122 )
|
|
509
545
|
local_addr = ( current_ec_ip , 5122 ) # dummy source addre ss
|
|
546
|
+
self.logger.debug(f"{local_addr} --jumpto--> {dest_addr}")
|
|
510
547
|
channel = jump_transport.open_channel('direct-tcpip', dest_addr, local_addr)
|
|
511
548
|
client = self.createClient()
|
|
512
549
|
self.logger.debug(f"Trying to connect to remote controller root@{self.neip}::{remote_ec_ip}:5122:pw:{self.rootPw}")
|
|
@@ -25,10 +25,21 @@ from easy_utils_dev.debugger import DEBUGGER
|
|
|
25
25
|
import signal
|
|
26
26
|
import sys
|
|
27
27
|
from tempfile import gettempdir
|
|
28
|
-
from urllib.parse import parse_qs
|
|
28
|
+
from urllib.parse import urlparse, parse_qs
|
|
29
29
|
|
|
30
30
|
TMP_PATH = gettempdir()
|
|
31
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
|
+
|
|
32
43
|
def getClassById( id ) :
|
|
33
44
|
return cenv[id]
|
|
34
45
|
|
|
@@ -83,29 +94,74 @@ class Stream :
|
|
|
83
94
|
} , to=sid )
|
|
84
95
|
|
|
85
96
|
class Response :
|
|
86
|
-
def __init__(self) :
|
|
97
|
+
def __init__(self, socket : SocketIO , request : request ) :
|
|
87
98
|
self.success = self.ok
|
|
88
99
|
self.failure = self.error
|
|
89
|
-
|
|
90
|
-
|
|
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'
|
|
91
112
|
timestamp = getTimestamp()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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'
|
|
95
124
|
timestamp = getTimestamp()
|
|
96
|
-
|
|
125
|
+
r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
126
|
+
if role :
|
|
127
|
+
self._emit( r , role , options)
|
|
128
|
+
return r
|
|
97
129
|
|
|
98
|
-
def internal_error(self , message=None , **kwargs ) :
|
|
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'
|
|
99
136
|
timestamp = getTimestamp()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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'
|
|
103
148
|
timestamp = getTimestamp()
|
|
104
|
-
|
|
149
|
+
r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
150
|
+
if role :
|
|
151
|
+
self._emit( r , role , options )
|
|
152
|
+
return r
|
|
105
153
|
|
|
106
|
-
def unauthorized(self , message=None , **kwargs) :
|
|
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'
|
|
107
160
|
timestamp = getTimestamp()
|
|
108
|
-
|
|
161
|
+
r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
162
|
+
if role :
|
|
163
|
+
self._emit( r , role , options)
|
|
164
|
+
return r
|
|
109
165
|
|
|
110
166
|
class AbortRequest :
|
|
111
167
|
def __init__(self, request ) :
|
|
@@ -152,6 +208,7 @@ class SocketClientObject :
|
|
|
152
208
|
self.sid = None
|
|
153
209
|
self.rooms = None
|
|
154
210
|
self.csid = None
|
|
211
|
+
self.browserid = None
|
|
155
212
|
|
|
156
213
|
class UISERVER :
|
|
157
214
|
def __init__(self ,
|
|
@@ -183,7 +240,7 @@ class UISERVER :
|
|
|
183
240
|
self.enable_test_url=True
|
|
184
241
|
self.abort_requests = {}
|
|
185
242
|
self.bg_requests = {}
|
|
186
|
-
self.socketio_clients =
|
|
243
|
+
self.socketio_clients = {}
|
|
187
244
|
self.abort_base_url = '/request/abort'
|
|
188
245
|
self.return_exception_as_code_400 = True
|
|
189
246
|
self.request_reply_base_url= '/request/result'
|
|
@@ -192,7 +249,14 @@ class UISERVER :
|
|
|
192
249
|
self.httpProtocol = 'https'
|
|
193
250
|
else :
|
|
194
251
|
self.httpProtocol = 'http'
|
|
195
|
-
self.socketio = SocketIO(
|
|
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
|
+
)
|
|
196
260
|
cenv[id] = self
|
|
197
261
|
self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
|
|
198
262
|
self.cache = TemporaryMemory()
|
|
@@ -203,6 +267,7 @@ class UISERVER :
|
|
|
203
267
|
self.logger = logger
|
|
204
268
|
self.stream = Stream()
|
|
205
269
|
self.simulate_network_delay = False
|
|
270
|
+
self.Response = Response(self.socketio , request )
|
|
206
271
|
if not self.logger :
|
|
207
272
|
self.logger = DEBUGGER(
|
|
208
273
|
name='easy_utils_dev_uiserver',
|
|
@@ -242,6 +307,8 @@ class UISERVER :
|
|
|
242
307
|
gc.collect()
|
|
243
308
|
|
|
244
309
|
def create_room(self , room_id : str , members : list[str] ) :
|
|
310
|
+
if room_id in list(self.socketio_rooms.keys()) :
|
|
311
|
+
return
|
|
245
312
|
self.socketio_rooms[room_id] = members
|
|
246
313
|
|
|
247
314
|
def add_member_to_room(self , room_id : str , member : str ) :
|
|
@@ -267,24 +334,24 @@ class UISERVER :
|
|
|
267
334
|
if not alive :
|
|
268
335
|
break
|
|
269
336
|
time.sleep(.25)
|
|
270
|
-
return Response
|
|
337
|
+
return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
|
|
271
338
|
|
|
272
339
|
else :
|
|
273
|
-
return Response
|
|
340
|
+
return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
|
|
274
341
|
|
|
275
342
|
@self.app.route(f"/request/traceback/<key>" , methods=['GET'])
|
|
276
343
|
def get_traceback(key : str ) :
|
|
277
344
|
traceback = self.cache.get(key)
|
|
278
345
|
if traceback :
|
|
279
|
-
return Response
|
|
346
|
+
return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
|
|
280
347
|
else :
|
|
281
|
-
return Response
|
|
348
|
+
return self.Response.not_found(message='Traceback not found or expired')
|
|
282
349
|
|
|
283
350
|
@self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
|
|
284
351
|
def get_result_of_async_request(id : str ) :
|
|
285
352
|
request : AbortRequest = self.bg_requests.get(id)
|
|
286
353
|
if request :
|
|
287
|
-
return Response
|
|
354
|
+
return self.Response.ok(
|
|
288
355
|
message='Result of async request found' ,
|
|
289
356
|
result=request.result ,
|
|
290
357
|
in_progress=request.in_progress ,
|
|
@@ -302,10 +369,9 @@ class UISERVER :
|
|
|
302
369
|
def before_request() :
|
|
303
370
|
if self.log_url_requests and self.logger :
|
|
304
371
|
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
305
|
-
|
|
306
372
|
|
|
307
373
|
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
308
|
-
return Response
|
|
374
|
+
return self.Response.unauthorized(message='Secret key is invalid')
|
|
309
375
|
|
|
310
376
|
@copy_current_request_context
|
|
311
377
|
def run_async_job_results( target_func , abort : AbortRequest ) :
|
|
@@ -327,15 +393,9 @@ class UISERVER :
|
|
|
327
393
|
abort.in_progress = False
|
|
328
394
|
abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
|
|
329
395
|
|
|
330
|
-
|
|
331
|
-
request.sid = ''
|
|
332
396
|
abortable = request.headers.get('abortable')
|
|
333
397
|
requestId = getRandomKeysAndStr(n=10)
|
|
334
398
|
request.start_ts = getTimestamp()
|
|
335
|
-
client : SocketClientObject = self.sessions.get(request.headers.get('csid'))
|
|
336
|
-
request.sid = client.sid
|
|
337
|
-
request.client = client
|
|
338
|
-
request.csid = request.headers.get('csid')
|
|
339
399
|
request.internalid = requestId
|
|
340
400
|
if abortable :
|
|
341
401
|
abort = self.register_abortable_request(request)
|
|
@@ -349,11 +409,11 @@ class UISERVER :
|
|
|
349
409
|
if request.headers.get('async') == 'false' :
|
|
350
410
|
target_func = current_app.view_functions.get(request.endpoint)
|
|
351
411
|
if not target_func:
|
|
352
|
-
return Response
|
|
412
|
+
return self.Response.not_found(message='Route not found')
|
|
353
413
|
th = start_thread(target=run_async_job_results, args=[target_func , abort ])
|
|
354
414
|
abort.thread = th
|
|
355
415
|
self.bg_requests[requestId] = abort
|
|
356
|
-
return Response
|
|
416
|
+
return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
|
|
357
417
|
|
|
358
418
|
if self.return_exception_as_code_400 :
|
|
359
419
|
@self.app.errorhandler(Exception)
|
|
@@ -392,7 +452,7 @@ class UISERVER :
|
|
|
392
452
|
}
|
|
393
453
|
self.logger.error(f'error: {json.dumps(response , indent=4)}')
|
|
394
454
|
self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
|
|
395
|
-
return Response
|
|
455
|
+
return self.Response.error( **response )
|
|
396
456
|
|
|
397
457
|
@self.app.after_request
|
|
398
458
|
def after_request(response) :
|
|
@@ -424,11 +484,10 @@ class UISERVER :
|
|
|
424
484
|
client.internalid = getRandomKeysAndStr(n=20)
|
|
425
485
|
client.sid = sid
|
|
426
486
|
client.request = request
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
client.csid = csid
|
|
487
|
+
client.browserid = extract_buid( request.url , 'buid')
|
|
488
|
+
self.create_room(client.browserid , [client.sid])
|
|
430
489
|
client.rooms = self.socketio_rooms
|
|
431
|
-
self.socketio_clients
|
|
490
|
+
self.socketio_clients[sid] = client
|
|
432
491
|
self.socketio.emit('/internal/connect', {
|
|
433
492
|
'status': 200,
|
|
434
493
|
'message': 'client connected' ,
|
|
@@ -437,27 +496,20 @@ class UISERVER :
|
|
|
437
496
|
},
|
|
438
497
|
to=sid
|
|
439
498
|
)
|
|
440
|
-
|
|
441
|
-
self.sessions[csid] = client
|
|
442
|
-
print(f'client connected : csid={csid} | SID={sid} | Clients : {len(self.socketio_clients)}')
|
|
499
|
+
self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
|
|
443
500
|
|
|
444
501
|
# socketio client connected.
|
|
445
502
|
@self.socketio.on('disconnect')
|
|
446
503
|
def handle_client_disconnect():
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
pass
|
|
454
|
-
print(f'client disconnected : csid={c.csid} | SID={request.sid} | Clients : {len(self.socketio_clients)}')
|
|
455
|
-
break
|
|
456
|
-
|
|
457
|
-
def getSocketio( self ):
|
|
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:
|
|
458
510
|
return self.socketio
|
|
459
511
|
|
|
460
|
-
def getFlask( self ):
|
|
512
|
+
def getFlask( self ) -> Flask:
|
|
461
513
|
return self.app
|
|
462
514
|
|
|
463
515
|
def shutdownUi(self) :
|
|
@@ -0,0 +1,606 @@
|
|
|
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
|
+
if not room_id in list(self.socketio_rooms.keys()) :
|
|
316
|
+
return
|
|
317
|
+
if member in self.socketio_rooms[room_id] :
|
|
318
|
+
return
|
|
319
|
+
self.socketio_rooms[room_id].append(member)
|
|
320
|
+
|
|
321
|
+
def remove_member_from_room(self , room_id : str , member : str ) :
|
|
322
|
+
self.socketio_rooms[room_id].remove(member)
|
|
323
|
+
|
|
324
|
+
def get_room_members(self , room_id : str ) :
|
|
325
|
+
return self.socketio_rooms[room_id]
|
|
326
|
+
|
|
327
|
+
def start_before_request(self) :
|
|
328
|
+
|
|
329
|
+
@self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
|
|
330
|
+
def abort_request(id : str ) :
|
|
331
|
+
abort : AbortRequest = self.cache.get(id)
|
|
332
|
+
timestamp = getTimestamp()
|
|
333
|
+
if abort :
|
|
334
|
+
abort.abort()
|
|
335
|
+
for i in range(30) :
|
|
336
|
+
th = abort.thread
|
|
337
|
+
alive = th.is_alive()
|
|
338
|
+
if not alive :
|
|
339
|
+
break
|
|
340
|
+
time.sleep(.25)
|
|
341
|
+
return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
|
|
342
|
+
|
|
343
|
+
else :
|
|
344
|
+
return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
|
|
345
|
+
|
|
346
|
+
@self.app.route(f"/request/traceback/<key>" , methods=['GET'])
|
|
347
|
+
def get_traceback(key : str ) :
|
|
348
|
+
traceback = self.cache.get(key)
|
|
349
|
+
if traceback :
|
|
350
|
+
return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
|
|
351
|
+
else :
|
|
352
|
+
return self.Response.not_found(message='Traceback not found or expired')
|
|
353
|
+
|
|
354
|
+
@self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
|
|
355
|
+
def get_result_of_async_request(id : str ) :
|
|
356
|
+
request : AbortRequest = self.bg_requests.get(id)
|
|
357
|
+
if request :
|
|
358
|
+
return self.Response.ok(
|
|
359
|
+
message='Result of async request found' ,
|
|
360
|
+
result=request.result ,
|
|
361
|
+
in_progress=request.in_progress ,
|
|
362
|
+
async_request=request.async_request ,
|
|
363
|
+
internalid=request.internalid ,
|
|
364
|
+
start_ts=request.start_ts ,
|
|
365
|
+
end_ts=request.end_ts ,
|
|
366
|
+
execution=request.execution ,
|
|
367
|
+
success=request.success ,
|
|
368
|
+
killed=request.killed
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@self.app.before_request
|
|
373
|
+
def before_request() :
|
|
374
|
+
if self.log_url_requests and self.logger :
|
|
375
|
+
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
376
|
+
|
|
377
|
+
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
378
|
+
return self.Response.unauthorized(message='Secret key is invalid')
|
|
379
|
+
|
|
380
|
+
@copy_current_request_context
|
|
381
|
+
def run_async_job_results( target_func , abort : AbortRequest ) :
|
|
382
|
+
abort.in_progress = True
|
|
383
|
+
abort.async_request = True
|
|
384
|
+
abort.internalid = request.internalid
|
|
385
|
+
abort.start_ts = getTimestamp()
|
|
386
|
+
try :
|
|
387
|
+
result = target_func(*request.args, **request.form)
|
|
388
|
+
abort.success = True
|
|
389
|
+
except Exception as e :
|
|
390
|
+
abort.success = False
|
|
391
|
+
abort.result = str(e)
|
|
392
|
+
abort.traceback = traceback.format_exc()
|
|
393
|
+
raise
|
|
394
|
+
abort.result = result
|
|
395
|
+
abort.end_ts = getTimestamp()
|
|
396
|
+
abort.execution = round(abort.end_ts - abort.start_ts, 2)
|
|
397
|
+
abort.in_progress = False
|
|
398
|
+
abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
|
|
399
|
+
|
|
400
|
+
abortable = request.headers.get('abortable')
|
|
401
|
+
requestId = getRandomKeysAndStr(n=10)
|
|
402
|
+
request.start_ts = getTimestamp()
|
|
403
|
+
request.internalid = requestId
|
|
404
|
+
if abortable :
|
|
405
|
+
abort = self.register_abortable_request(request)
|
|
406
|
+
request.abortable = True
|
|
407
|
+
request.abort_id = abort.abort_id
|
|
408
|
+
# check here if async in the headers
|
|
409
|
+
# if yes . i will trigger the function in thread
|
|
410
|
+
# start_tread(#how to get the target function here ? )
|
|
411
|
+
# now i want to return response to UI { status : 200 , message : 'request now in running bg' , abort_id : abort.abort_id }
|
|
412
|
+
# the flask function should not be called again
|
|
413
|
+
if request.headers.get('async') == 'false' :
|
|
414
|
+
target_func = current_app.view_functions.get(request.endpoint)
|
|
415
|
+
if not target_func:
|
|
416
|
+
return self.Response.not_found(message='Route not found')
|
|
417
|
+
th = start_thread(target=run_async_job_results, args=[target_func , abort ])
|
|
418
|
+
abort.thread = th
|
|
419
|
+
self.bg_requests[requestId] = abort
|
|
420
|
+
return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
|
|
421
|
+
|
|
422
|
+
if self.return_exception_as_code_400 :
|
|
423
|
+
@self.app.errorhandler(Exception)
|
|
424
|
+
def handle_exception(e):
|
|
425
|
+
|
|
426
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
427
|
+
key = getRandomKeysAndStr(n=10)
|
|
428
|
+
tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
|
|
429
|
+
# Example: file, line, function, text
|
|
430
|
+
error_file = tb_last.filename
|
|
431
|
+
error_line = tb_last.lineno
|
|
432
|
+
error_func = tb_last.name
|
|
433
|
+
error_code = tb_last.line
|
|
434
|
+
|
|
435
|
+
# Log the full traceback (optional)
|
|
436
|
+
traceback.print_exc()
|
|
437
|
+
# Customize the error response
|
|
438
|
+
t = getTimestamp()
|
|
439
|
+
response = {
|
|
440
|
+
"status" : 400 ,
|
|
441
|
+
"key" : key,
|
|
442
|
+
"error": str(e),
|
|
443
|
+
"message": str(e),
|
|
444
|
+
"type": type(e).__name__ ,
|
|
445
|
+
"request_full_url" : request.url ,
|
|
446
|
+
"request_method" : request.method ,
|
|
447
|
+
"endpoint" : request.endpoint ,
|
|
448
|
+
"request_path" : request.path ,
|
|
449
|
+
"error_file" : os.path.basename(error_file).replace('.py' , ''),
|
|
450
|
+
"error_line" : error_line,
|
|
451
|
+
"error_func" : error_func,
|
|
452
|
+
"error_code" : error_code,
|
|
453
|
+
'timestamp' : t ,
|
|
454
|
+
"date" : convertTimestampToDate(t) ,
|
|
455
|
+
'traceback_url' : f"/request/traceback/{key}"
|
|
456
|
+
}
|
|
457
|
+
self.logger.error(f'error: {json.dumps(response , indent=4)}')
|
|
458
|
+
self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
|
|
459
|
+
return self.Response.error( **response )
|
|
460
|
+
|
|
461
|
+
@self.app.after_request
|
|
462
|
+
def after_request(response) :
|
|
463
|
+
|
|
464
|
+
if self.simulate_network_delay :
|
|
465
|
+
time.sleep(self.simulate_network_delay)
|
|
466
|
+
now = getTimestamp()
|
|
467
|
+
x = round(now - request.start_ts, 2)
|
|
468
|
+
try :
|
|
469
|
+
response.headers['internalid'] = request.internalid
|
|
470
|
+
response.headers['start_ts'] = request.start_ts
|
|
471
|
+
response.headers['end_ts'] = now
|
|
472
|
+
response.headers['execution'] = x
|
|
473
|
+
if request.abortable :
|
|
474
|
+
response.headers['abortid'] = request.abort_id
|
|
475
|
+
response.headers['abortable'] = True
|
|
476
|
+
except :
|
|
477
|
+
response.headers['abortable'] = False
|
|
478
|
+
if self.log_url_requests and self.logger :
|
|
479
|
+
self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
|
|
480
|
+
return response
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# socketio client connected.
|
|
484
|
+
@self.socketio.on('connect')
|
|
485
|
+
def handle_client_connect():
|
|
486
|
+
sid = request.sid
|
|
487
|
+
client = SocketClientObject()
|
|
488
|
+
client.internalid = getRandomKeysAndStr(n=20)
|
|
489
|
+
client.sid = sid
|
|
490
|
+
client.request = request
|
|
491
|
+
client.browserid = extract_buid( request.url , 'buid')
|
|
492
|
+
self.create_room( client.browserid , [])
|
|
493
|
+
self.add_member_to_room( client.browserid , sid)
|
|
494
|
+
client.rooms = self.socketio_rooms
|
|
495
|
+
self.socketio_clients[sid] = client
|
|
496
|
+
self.socketio.emit('/internal/connect', {
|
|
497
|
+
'status': 200,
|
|
498
|
+
'message': 'client connected' ,
|
|
499
|
+
'internalid' : client.internalid,
|
|
500
|
+
'sid' : sid,
|
|
501
|
+
},
|
|
502
|
+
to=sid
|
|
503
|
+
)
|
|
504
|
+
self.logger.debug(f"Current Rooms : {json.dumps(self.socketio_rooms , indent=4)}")
|
|
505
|
+
self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
|
|
506
|
+
|
|
507
|
+
# socketio client connected.
|
|
508
|
+
@self.socketio.on('disconnect')
|
|
509
|
+
def handle_client_disconnect():
|
|
510
|
+
if request.sid in list(self.socketio_clients.keys()) :
|
|
511
|
+
buid = self.socketio_clients[request.sid].browserid
|
|
512
|
+
del self.socketio_clients[request.sid]
|
|
513
|
+
self.remove_member_from_room( buid , request.sid)
|
|
514
|
+
self.logger.info(f'Disconnected : {request.sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def getSocketio( self ) -> SocketIO:
|
|
518
|
+
return self.socketio
|
|
519
|
+
|
|
520
|
+
def getFlask( self ) -> Flask:
|
|
521
|
+
return self.app
|
|
522
|
+
|
|
523
|
+
def shutdownUi(self) :
|
|
524
|
+
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
525
|
+
try:
|
|
526
|
+
self.wsgi_server.shutdown()
|
|
527
|
+
self.wsgi_server.server_close()
|
|
528
|
+
except:
|
|
529
|
+
pass
|
|
530
|
+
kill_thread(self.thread)
|
|
531
|
+
|
|
532
|
+
def _wait_th(self , t ) :
|
|
533
|
+
# t.join()
|
|
534
|
+
while True :
|
|
535
|
+
time.sleep(36000)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def thrStartUi(self , suppress_prints=True) :
|
|
539
|
+
if self.enable_test_url :
|
|
540
|
+
if not suppress_prints :
|
|
541
|
+
print(f'TEST URL GET-METHOD /connection/test/internal')
|
|
542
|
+
@self.app.route('/connection/test/internal' , methods=['GET'])
|
|
543
|
+
def test_connection():
|
|
544
|
+
return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
if self.httpProtocol == 'http' :
|
|
548
|
+
con = None
|
|
549
|
+
elif self.httpProtocol == 'https' :
|
|
550
|
+
con=(self.ssl_crt , self.ssl_key)
|
|
551
|
+
self.wsgi_server = wsgi_server = ThreadedWSGIServer(
|
|
552
|
+
host = self.address ,
|
|
553
|
+
ssl_context=con,
|
|
554
|
+
# ssl_context=('ssl.crt', 'ssl.key'),
|
|
555
|
+
port = self.port,
|
|
556
|
+
app = self.app )
|
|
557
|
+
if not suppress_prints :
|
|
558
|
+
print(f"web-socket: {self.fullAddress}")
|
|
559
|
+
print(f"UI URL : {self.fullAddress}")
|
|
560
|
+
log = logging.getLogger('werkzeug')
|
|
561
|
+
log.setLevel(logging.ERROR)
|
|
562
|
+
wsgi_server.serve_forever()
|
|
563
|
+
|
|
564
|
+
def on_ctrl_c(self , sig=None, frame=None):
|
|
565
|
+
self.stopUi()
|
|
566
|
+
pid = os.getpid()
|
|
567
|
+
os.kill(pid, signal.SIGTERM)
|
|
568
|
+
sys.exit(0)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def startUi(self , daemon=False , suppress_prints=True , block=False) :
|
|
572
|
+
self.start_before_request()
|
|
573
|
+
self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
|
|
574
|
+
self.flaskprocess.daemon = daemon
|
|
575
|
+
self.flaskprocess.start()
|
|
576
|
+
start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
|
|
577
|
+
signal.signal(signal.SIGINT, self.on_ctrl_c )
|
|
578
|
+
if block:
|
|
579
|
+
self.wait()
|
|
580
|
+
return self.thread
|
|
581
|
+
|
|
582
|
+
def wait(self):
|
|
583
|
+
"""
|
|
584
|
+
Block the main thread to keep it alive for signal handling (Ctrl+C).
|
|
585
|
+
This allows Ctrl+C to be properly detected. Call this after startUi()
|
|
586
|
+
if you want signal handling to work.
|
|
587
|
+
"""
|
|
588
|
+
try:
|
|
589
|
+
while self.flaskprocess.is_alive():
|
|
590
|
+
self.flaskprocess.join(timeout=0.1)
|
|
591
|
+
except KeyboardInterrupt:
|
|
592
|
+
if not hasattr(self, '_shutting_down'):
|
|
593
|
+
self._shutting_down = True
|
|
594
|
+
self.on_ctrl_c()
|
|
595
|
+
except SystemExit:
|
|
596
|
+
# Re-raise SystemExit to allow proper program termination
|
|
597
|
+
raise
|
|
598
|
+
|
|
599
|
+
def stopUi(self) :
|
|
600
|
+
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
601
|
+
try:
|
|
602
|
+
self.wsgi_server.shutdown()
|
|
603
|
+
except:
|
|
604
|
+
pass
|
|
605
|
+
kill_thread(self.thread)
|
|
606
|
+
return True
|
|
@@ -26,6 +26,7 @@ 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
|
|
29
30
|
easy_utils_dev/uiserver.py
|
|
30
31
|
easy_utils_dev/utils.py
|
|
31
32
|
easy_utils_dev/winserviceapi.py
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
VERSION = '2.167'
|
|
4
|
+
|
|
5
|
+
# Setting up
|
|
6
|
+
setup(
|
|
7
|
+
name="easy_utils_dev",
|
|
8
|
+
version=VERSION,
|
|
9
|
+
packages=find_packages(),
|
|
10
|
+
include_package_data=True,
|
|
11
|
+
install_requires=[
|
|
12
|
+
'psutil' ,
|
|
13
|
+
'ping3' ,
|
|
14
|
+
'snakebite-py3',
|
|
15
|
+
'flask' ,
|
|
16
|
+
'flask_cors' ,
|
|
17
|
+
'xmltodict' ,
|
|
18
|
+
'paramiko' ,
|
|
19
|
+
'oracledb' ,
|
|
20
|
+
'requests',
|
|
21
|
+
'flask_socketio',
|
|
22
|
+
'python-dotenv',
|
|
23
|
+
'gevent',
|
|
24
|
+
'pyzipper',
|
|
25
|
+
'pyjwt',
|
|
26
|
+
'authlib',
|
|
27
|
+
'kafka-python'
|
|
28
|
+
],
|
|
29
|
+
keywords=['python3'],
|
|
30
|
+
classifiers=[
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
]
|
|
33
|
+
)
|
easy_utils_dev-2.165/setup.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
VERSION = '2.165'
|
|
4
|
-
|
|
5
|
-
# Setting up
|
|
6
|
-
setup(
|
|
7
|
-
name="easy_utils_dev",
|
|
8
|
-
version=VERSION,
|
|
9
|
-
packages=find_packages(),
|
|
10
|
-
include_package_data=True,
|
|
11
|
-
install_requires=[
|
|
12
|
-
'psutil' ,
|
|
13
|
-
'ping3' ,
|
|
14
|
-
'snakebite-py3',
|
|
15
|
-
'flask' ,
|
|
16
|
-
'flask_cors' ,
|
|
17
|
-
'xmltodict' ,
|
|
18
|
-
'paramiko' ,
|
|
19
|
-
'oracledb' ,
|
|
20
|
-
'requests',
|
|
21
|
-
'flask_socketio',
|
|
22
|
-
'python-dotenv',
|
|
23
|
-
'gevent',
|
|
24
|
-
'pyzipper',
|
|
25
|
-
'pyjwt',
|
|
26
|
-
'authlib',
|
|
27
|
-
'kafka-python'
|
|
28
|
-
],
|
|
29
|
-
keywords=['python3'],
|
|
30
|
-
classifiers=[
|
|
31
|
-
"Programming Language :: Python :: 3",
|
|
32
|
-
]
|
|
33
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|