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.
Files changed (44) hide show
  1. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/PKG-INFO +1 -1
  2. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/exceptions.py +4 -0
  3. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ne1830PSS.py +37 -3
  4. easy_utils_dev-2.166/easy_utils_dev/uiserver.py → easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +36 -30
  5. easy_utils_dev-2.167/easy_utils_dev/uiserver.py +606 -0
  6. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/PKG-INFO +1 -1
  7. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/SOURCES.txt +1 -0
  8. easy_utils_dev-2.167/setup.py +33 -0
  9. easy_utils_dev-2.166/setup.py +0 -33
  10. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/MANIFEST.in +0 -0
  11. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/EasySsh.py +0 -0
  12. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/Events.py +0 -0
  13. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/FastQueue.py +0 -0
  14. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/NameObject.py +0 -0
  15. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/__init__.py +0 -0
  16. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/abortable.py +0 -0
  17. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/brevosmtp.py +0 -0
  18. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/check_license.py +0 -0
  19. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/cplib.py +0 -0
  20. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/custom_env.py +0 -0
  21. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/debugger.py +0 -0
  22. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/easy_oracle.py +0 -0
  23. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/encryptor.py +0 -0
  24. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept.py +0 -0
  25. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  26. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  27. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/filescompressor.py +0 -0
  28. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/generate_license.py +0 -0
  29. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/keycloakapi.py +0 -0
  30. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/lralib.py +0 -0
  31. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/nsp_kafka.py +0 -0
  32. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/openid_server.py +0 -0
  33. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/optics_utils.py +0 -0
  34. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/require_auth.py +0 -0
  35. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/simple_sqlite.py +0 -0
  36. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/temp_memory.py +0 -0
  37. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/utils.py +0 -0
  38. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/winserviceapi.py +0 -0
  39. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/wsnoclib.py +0 -0
  40. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev/wsselib.py +0 -0
  41. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  42. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/requires.txt +0 -0
  43. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/top_level.txt +0 -0
  44. {easy_utils_dev-2.166 → easy_utils_dev-2.167}/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.167
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,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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.166
3
+ Version: 2.167
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.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
+ )
@@ -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