easy-utils-dev 2.166__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.166 → easy_utils_dev-2.167}/PKG-INFO +1 -1
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/exceptions.py +4 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ne1830PSS.py +37 -3
- easy_utils_dev-2.166/easy_utils_dev/uiserver.py → easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +36 -30
- easy_utils_dev-2.167/easy_utils_dev/uiserver.py +606 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/PKG-INFO +1 -1
- {easy_utils_dev-2.166 → 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.166/setup.py +0 -33
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/MANIFEST.in +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/debugger.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/generate_license.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/temp_memory.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/utils.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/wsnoclib.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/requires.txt +0 -0
- {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.166 → 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)
|
|
@@ -488,7 +488,23 @@ class PSS1830 :
|
|
|
488
488
|
return jump_channel
|
|
489
489
|
return None
|
|
490
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
|
+
|
|
491
506
|
def switch_to_standby_ec(self) :
|
|
507
|
+
isOtn = None
|
|
492
508
|
if self.connect_to_standby_ec :
|
|
493
509
|
self.logger.info(f"connecting to standby EC {self.neip} ...")
|
|
494
510
|
if not self.sim :
|
|
@@ -497,19 +513,37 @@ class PSS1830 :
|
|
|
497
513
|
raise exceptions.InvalidRemoteEcIp(f"couldn't connect to remote EC")
|
|
498
514
|
if self.sim :
|
|
499
515
|
response = "100.0.81.1"
|
|
516
|
+
isOtn = False
|
|
517
|
+
elif not self.sim :
|
|
518
|
+
family , isOtn = self.get_ne_family_type_root()
|
|
519
|
+
|
|
500
520
|
if response :
|
|
501
521
|
current_ec_ip = response.replace('\n' , '')
|
|
502
|
-
|
|
522
|
+
self.logger.debug(f"Current EC IP={current_ec_ip}")
|
|
523
|
+
switcher_phn = {
|
|
503
524
|
'100.0.81.1' : '100.0.81.18',
|
|
504
525
|
'100.0.81.18' : '100.0.81.1',
|
|
505
526
|
}
|
|
506
|
-
|
|
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}")
|
|
507
539
|
if not remote_ec_ip :
|
|
508
540
|
self.logger.error(f"couldn't find the standby EC IP for {current_ec_ip}")
|
|
509
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 ...')
|
|
510
543
|
jump_transport = self.client.get_transport()
|
|
511
|
-
dest_addr = ( remote_ec_ip , 5122 )
|
|
544
|
+
dest_addr = ( remote_ec_ip , 5122 )
|
|
512
545
|
local_addr = ( current_ec_ip , 5122 ) # dummy source addre ss
|
|
546
|
+
self.logger.debug(f"{local_addr} --jumpto--> {dest_addr}")
|
|
513
547
|
channel = jump_transport.open_channel('direct-tcpip', dest_addr, local_addr)
|
|
514
548
|
client = self.createClient()
|
|
515
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
|
|
|
@@ -90,7 +101,7 @@ class Response :
|
|
|
90
101
|
self.request = request
|
|
91
102
|
|
|
92
103
|
def _emit(self , data, role , options={} ) :
|
|
93
|
-
self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.sid)
|
|
104
|
+
self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
|
|
94
105
|
|
|
95
106
|
def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
96
107
|
role = None
|
|
@@ -197,6 +208,7 @@ class SocketClientObject :
|
|
|
197
208
|
self.sid = None
|
|
198
209
|
self.rooms = None
|
|
199
210
|
self.csid = None
|
|
211
|
+
self.browserid = None
|
|
200
212
|
|
|
201
213
|
class UISERVER :
|
|
202
214
|
def __init__(self ,
|
|
@@ -228,7 +240,7 @@ class UISERVER :
|
|
|
228
240
|
self.enable_test_url=True
|
|
229
241
|
self.abort_requests = {}
|
|
230
242
|
self.bg_requests = {}
|
|
231
|
-
self.socketio_clients =
|
|
243
|
+
self.socketio_clients = {}
|
|
232
244
|
self.abort_base_url = '/request/abort'
|
|
233
245
|
self.return_exception_as_code_400 = True
|
|
234
246
|
self.request_reply_base_url= '/request/result'
|
|
@@ -237,7 +249,14 @@ class UISERVER :
|
|
|
237
249
|
self.httpProtocol = 'https'
|
|
238
250
|
else :
|
|
239
251
|
self.httpProtocol = 'http'
|
|
240
|
-
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
|
+
)
|
|
241
260
|
cenv[id] = self
|
|
242
261
|
self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
|
|
243
262
|
self.cache = TemporaryMemory()
|
|
@@ -288,6 +307,8 @@ class UISERVER :
|
|
|
288
307
|
gc.collect()
|
|
289
308
|
|
|
290
309
|
def create_room(self , room_id : str , members : list[str] ) :
|
|
310
|
+
if room_id in list(self.socketio_rooms.keys()) :
|
|
311
|
+
return
|
|
291
312
|
self.socketio_rooms[room_id] = members
|
|
292
313
|
|
|
293
314
|
def add_member_to_room(self , room_id : str , member : str ) :
|
|
@@ -348,7 +369,6 @@ class UISERVER :
|
|
|
348
369
|
def before_request() :
|
|
349
370
|
if self.log_url_requests and self.logger :
|
|
350
371
|
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
351
|
-
|
|
352
372
|
|
|
353
373
|
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
354
374
|
return self.Response.unauthorized(message='Secret key is invalid')
|
|
@@ -373,15 +393,9 @@ class UISERVER :
|
|
|
373
393
|
abort.in_progress = False
|
|
374
394
|
abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
|
|
375
395
|
|
|
376
|
-
|
|
377
|
-
request.sid = ''
|
|
378
396
|
abortable = request.headers.get('abortable')
|
|
379
397
|
requestId = getRandomKeysAndStr(n=10)
|
|
380
398
|
request.start_ts = getTimestamp()
|
|
381
|
-
client : SocketClientObject = self.sessions.get(request.headers.get('csid'))
|
|
382
|
-
request.sid = client.sid
|
|
383
|
-
request.client = client
|
|
384
|
-
request.csid = request.headers.get('csid')
|
|
385
399
|
request.internalid = requestId
|
|
386
400
|
if abortable :
|
|
387
401
|
abort = self.register_abortable_request(request)
|
|
@@ -470,11 +484,10 @@ class UISERVER :
|
|
|
470
484
|
client.internalid = getRandomKeysAndStr(n=20)
|
|
471
485
|
client.sid = sid
|
|
472
486
|
client.request = request
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
client.csid = csid
|
|
487
|
+
client.browserid = extract_buid( request.url , 'buid')
|
|
488
|
+
self.create_room(client.browserid , [client.sid])
|
|
476
489
|
client.rooms = self.socketio_rooms
|
|
477
|
-
self.socketio_clients
|
|
490
|
+
self.socketio_clients[sid] = client
|
|
478
491
|
self.socketio.emit('/internal/connect', {
|
|
479
492
|
'status': 200,
|
|
480
493
|
'message': 'client connected' ,
|
|
@@ -483,27 +496,20 @@ class UISERVER :
|
|
|
483
496
|
},
|
|
484
497
|
to=sid
|
|
485
498
|
)
|
|
486
|
-
|
|
487
|
-
self.sessions[csid] = client
|
|
488
|
-
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")
|
|
489
500
|
|
|
490
501
|
# socketio client connected.
|
|
491
502
|
@self.socketio.on('disconnect')
|
|
492
503
|
def handle_client_disconnect():
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
pass
|
|
500
|
-
print(f'client disconnected : csid={c.csid} | SID={request.sid} | Clients : {len(self.socketio_clients)}')
|
|
501
|
-
break
|
|
502
|
-
|
|
503
|
-
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:
|
|
504
510
|
return self.socketio
|
|
505
511
|
|
|
506
|
-
def getFlask( self ):
|
|
512
|
+
def getFlask( self ) -> Flask:
|
|
507
513
|
return self.app
|
|
508
514
|
|
|
509
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.166/setup.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
VERSION = '2.166'
|
|
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
|