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