easy-utils-dev 2.165__tar.gz → 2.167__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/PKG-INFO +1 -1
  2. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/exceptions.py +4 -0
  3. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ne1830PSS.py +41 -4
  4. easy_utils_dev-2.165/easy_utils_dev/uiserver.py → easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +104 -52
  5. easy_utils_dev-2.167/easy_utils_dev/uiserver.py +606 -0
  6. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/PKG-INFO +1 -1
  7. {easy_utils_dev-2.165 → 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.165/setup.py +0 -33
  10. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/MANIFEST.in +0 -0
  11. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/EasySsh.py +0 -0
  12. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/Events.py +0 -0
  13. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/FastQueue.py +0 -0
  14. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/NameObject.py +0 -0
  15. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/__init__.py +0 -0
  16. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/abortable.py +0 -0
  17. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/brevosmtp.py +0 -0
  18. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/check_license.py +0 -0
  19. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/cplib.py +0 -0
  20. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/custom_env.py +0 -0
  21. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/debugger.py +0 -0
  22. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/easy_oracle.py +0 -0
  23. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/encryptor.py +0 -0
  24. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept.py +0 -0
  25. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  26. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  27. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/filescompressor.py +0 -0
  28. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/generate_license.py +0 -0
  29. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/keycloakapi.py +0 -0
  30. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/lralib.py +0 -0
  31. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/nsp_kafka.py +0 -0
  32. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/openid_server.py +0 -0
  33. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/optics_utils.py +0 -0
  34. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/require_auth.py +0 -0
  35. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/simple_sqlite.py +0 -0
  36. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/temp_memory.py +0 -0
  37. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/utils.py +0 -0
  38. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/winserviceapi.py +0 -0
  39. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/wsnoclib.py +0 -0
  40. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev/wsselib.py +0 -0
  41. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  42. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/requires.txt +0 -0
  43. {easy_utils_dev-2.165 → easy_utils_dev-2.167}/easy_utils_dev.egg-info/top_level.txt +0 -0
  44. {easy_utils_dev-2.165 → 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.165
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)
@@ -20,6 +20,7 @@ class PSS1830 :
20
20
  file_name=None,
21
21
  debug_home_path=None,
22
22
  trust_env_log_path=True,
23
+ connect_to_standby_ec=False,
23
24
  debugger_kwargs: Optional[DEBUGGER] = {}
24
25
  ) -> None:
25
26
  self.port = None
@@ -55,7 +56,9 @@ class PSS1830 :
55
56
  self.full_buffer=''
56
57
  self.pssRelease = None
57
58
  self.maxConnectAttempt=3
58
- self.connect_to_standby_ec=False
59
+
60
+ self.connect_to_standby_ec = connect_to_standby_ec
61
+
59
62
  self.currentConnectAttempt=0
60
63
  self.auto_enable_tcp_forward=auto_enable_tcp_forward
61
64
  self.tcpForwardStatus=None
@@ -485,7 +488,23 @@ class PSS1830 :
485
488
  return jump_channel
486
489
  return None
487
490
 
491
+ def get_ne_family_type_root(self):
492
+ self.logger.debug(f"Getting NE Family type from root ...")
493
+ if self.sim :
494
+ self.logger.warn(f"Skip get pss family due to sim not supoorted.")
495
+ return "", False
496
+ command = 'cd /pureNeApp/export/home/platform/bin/ ; ./getShelfType'
497
+ result = self.ssh_execute(ssh=self.client , command=command)
498
+ if not result :
499
+ self.logger.error(f"Failed to get NE Family type from root")
500
+ raise exceptions.InvalidPSSElementType("")
501
+ otn = False
502
+ if 'x' in result.lower() :
503
+ otn = True
504
+ return result , otn
505
+
488
506
  def switch_to_standby_ec(self) :
507
+ isOtn = None
489
508
  if self.connect_to_standby_ec :
490
509
  self.logger.info(f"connecting to standby EC {self.neip} ...")
491
510
  if not self.sim :
@@ -494,19 +513,37 @@ class PSS1830 :
494
513
  raise exceptions.InvalidRemoteEcIp(f"couldn't connect to remote EC")
495
514
  if self.sim :
496
515
  response = "100.0.81.1"
516
+ isOtn = False
517
+ elif not self.sim :
518
+ family , isOtn = self.get_ne_family_type_root()
519
+
497
520
  if response :
498
521
  current_ec_ip = response.replace('\n' , '')
499
- switcher = {
522
+ self.logger.debug(f"Current EC IP={current_ec_ip}")
523
+ switcher_phn = {
500
524
  '100.0.81.1' : '100.0.81.18',
501
525
  '100.0.81.18' : '100.0.81.1',
502
526
  }
503
- 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}")
504
539
  if not remote_ec_ip :
505
540
  self.logger.error(f"couldn't find the standby EC IP for {current_ec_ip}")
506
541
  raise exceptions.InvalidRemoteEcIp(f"couldn't find the standby EC IP for {current_ec_ip}")
542
+ self.logger.debug('Creating transport layer for remote EC ...')
507
543
  jump_transport = self.client.get_transport()
508
- dest_addr = ( remote_ec_ip , 5122 )
544
+ dest_addr = ( remote_ec_ip , 5122 )
509
545
  local_addr = ( current_ec_ip , 5122 ) # dummy source addre ss
546
+ self.logger.debug(f"{local_addr} --jumpto--> {dest_addr}")
510
547
  channel = jump_transport.open_channel('direct-tcpip', dest_addr, local_addr)
511
548
  client = self.createClient()
512
549
  self.logger.debug(f"Trying to connect to remote controller root@{self.neip}::{remote_ec_ip}:5122:pw:{self.rootPw}")
@@ -25,10 +25,21 @@ from easy_utils_dev.debugger import DEBUGGER
25
25
  import signal
26
26
  import sys
27
27
  from tempfile import gettempdir
28
- from urllib.parse import parse_qs
28
+ from urllib.parse import urlparse, parse_qs
29
29
 
30
30
  TMP_PATH = gettempdir()
31
31
 
32
+
33
+ def extract_buid(url: str , key ) -> str | None:
34
+ """
35
+ Extracts the 'buid' query parameter from a URL.
36
+ Returns None if not found.
37
+ """
38
+ parsed = urlparse(url)
39
+ query_params = parse_qs(parsed.query)
40
+ return query_params.get( key , [None])[0]
41
+
42
+
32
43
  def getClassById( id ) :
33
44
  return cenv[id]
34
45
 
@@ -83,29 +94,74 @@ class Stream :
83
94
  } , to=sid )
84
95
 
85
96
  class Response :
86
- def __init__(self) :
97
+ def __init__(self, socket : SocketIO , request : request ) :
87
98
  self.success = self.ok
88
99
  self.failure = self.error
89
-
90
- def ok(self , result=[] , message=None , **kwargs) :
100
+ self.socket = socket
101
+ self.request = request
102
+
103
+ def _emit(self , data, role , options={} ) :
104
+ self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
105
+
106
+ def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
107
+ role = None
108
+ if alert :
109
+ role = 'alert'
110
+ elif toast :
111
+ role = 'toast'
91
112
  timestamp = getTimestamp()
92
- return {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
93
-
94
- def error(self , message=None , **kwargs) :
113
+ r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
114
+ if role :
115
+ self._emit( r , role, options)
116
+ return r
117
+
118
+ def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
119
+ role = None
120
+ if alert :
121
+ role = 'alert'
122
+ elif toast :
123
+ role = 'toast'
95
124
  timestamp = getTimestamp()
96
- return {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
125
+ r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
126
+ if role :
127
+ self._emit( r , role , options)
128
+ return r
97
129
 
98
- def internal_error(self , message=None , **kwargs ) :
130
+ def internal_error(self , message=None , alert=False, toast=False , options={} , **kwargs ) :
131
+ role = None
132
+ if alert :
133
+ role = 'alert'
134
+ elif toast :
135
+ role = 'toast'
99
136
  timestamp = getTimestamp()
100
- return {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
101
-
102
- def not_found(self , message=None , **kwargs) :
137
+ r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
138
+ if role :
139
+ self._emit( r , role , options)
140
+ return r
141
+
142
+ def not_found(self , message=None , alert=False, toast=False , options={} , **kwargs) :
143
+ role = None
144
+ if alert :
145
+ role = 'alert'
146
+ elif toast :
147
+ role = 'toast'
103
148
  timestamp = getTimestamp()
104
- return {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
149
+ r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
150
+ if role :
151
+ self._emit( r , role , options )
152
+ return r
105
153
 
106
- def unauthorized(self , message=None , **kwargs) :
154
+ def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
155
+ role = None
156
+ if alert :
157
+ role = 'alert'
158
+ elif toast :
159
+ role = 'toast'
107
160
  timestamp = getTimestamp()
108
- return {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
161
+ r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
162
+ if role :
163
+ self._emit( r , role , options)
164
+ return r
109
165
 
110
166
  class AbortRequest :
111
167
  def __init__(self, request ) :
@@ -152,6 +208,7 @@ class SocketClientObject :
152
208
  self.sid = None
153
209
  self.rooms = None
154
210
  self.csid = None
211
+ self.browserid = None
155
212
 
156
213
  class UISERVER :
157
214
  def __init__(self ,
@@ -183,7 +240,7 @@ class UISERVER :
183
240
  self.enable_test_url=True
184
241
  self.abort_requests = {}
185
242
  self.bg_requests = {}
186
- self.socketio_clients = []
243
+ self.socketio_clients = {}
187
244
  self.abort_base_url = '/request/abort'
188
245
  self.return_exception_as_code_400 = True
189
246
  self.request_reply_base_url= '/request/result'
@@ -192,7 +249,14 @@ class UISERVER :
192
249
  self.httpProtocol = 'https'
193
250
  else :
194
251
  self.httpProtocol = 'http'
195
- self.socketio = SocketIO(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
+ )
196
260
  cenv[id] = self
197
261
  self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
198
262
  self.cache = TemporaryMemory()
@@ -203,6 +267,7 @@ class UISERVER :
203
267
  self.logger = logger
204
268
  self.stream = Stream()
205
269
  self.simulate_network_delay = False
270
+ self.Response = Response(self.socketio , request )
206
271
  if not self.logger :
207
272
  self.logger = DEBUGGER(
208
273
  name='easy_utils_dev_uiserver',
@@ -242,6 +307,8 @@ class UISERVER :
242
307
  gc.collect()
243
308
 
244
309
  def create_room(self , room_id : str , members : list[str] ) :
310
+ if room_id in list(self.socketio_rooms.keys()) :
311
+ return
245
312
  self.socketio_rooms[room_id] = members
246
313
 
247
314
  def add_member_to_room(self , room_id : str , member : str ) :
@@ -267,24 +334,24 @@ class UISERVER :
267
334
  if not alive :
268
335
  break
269
336
  time.sleep(.25)
270
- return Response().ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
337
+ return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
271
338
 
272
339
  else :
273
- return Response().not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
340
+ return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
274
341
 
275
342
  @self.app.route(f"/request/traceback/<key>" , methods=['GET'])
276
343
  def get_traceback(key : str ) :
277
344
  traceback = self.cache.get(key)
278
345
  if traceback :
279
- return Response().ok(message='Traceback found' , traceback=traceback.get('traceback'))
346
+ return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
280
347
  else :
281
- return Response().not_found(message='Traceback not found or expired')
348
+ return self.Response.not_found(message='Traceback not found or expired')
282
349
 
283
350
  @self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
284
351
  def get_result_of_async_request(id : str ) :
285
352
  request : AbortRequest = self.bg_requests.get(id)
286
353
  if request :
287
- return Response().ok(
354
+ return self.Response.ok(
288
355
  message='Result of async request found' ,
289
356
  result=request.result ,
290
357
  in_progress=request.in_progress ,
@@ -302,10 +369,9 @@ class UISERVER :
302
369
  def before_request() :
303
370
  if self.log_url_requests and self.logger :
304
371
  self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
305
-
306
372
 
307
373
  if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
308
- return Response().unauthorized(message='Secret key is invalid')
374
+ return self.Response.unauthorized(message='Secret key is invalid')
309
375
 
310
376
  @copy_current_request_context
311
377
  def run_async_job_results( target_func , abort : AbortRequest ) :
@@ -327,15 +393,9 @@ class UISERVER :
327
393
  abort.in_progress = False
328
394
  abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
329
395
 
330
-
331
- request.sid = ''
332
396
  abortable = request.headers.get('abortable')
333
397
  requestId = getRandomKeysAndStr(n=10)
334
398
  request.start_ts = getTimestamp()
335
- client : SocketClientObject = self.sessions.get(request.headers.get('csid'))
336
- request.sid = client.sid
337
- request.client = client
338
- request.csid = request.headers.get('csid')
339
399
  request.internalid = requestId
340
400
  if abortable :
341
401
  abort = self.register_abortable_request(request)
@@ -349,11 +409,11 @@ class UISERVER :
349
409
  if request.headers.get('async') == 'false' :
350
410
  target_func = current_app.view_functions.get(request.endpoint)
351
411
  if not target_func:
352
- return Response().not_found(message='Route not found')
412
+ return self.Response.not_found(message='Route not found')
353
413
  th = start_thread(target=run_async_job_results, args=[target_func , abort ])
354
414
  abort.thread = th
355
415
  self.bg_requests[requestId] = abort
356
- return Response().ok(message='Request now in running bg' , abort_id=abort.abort_id)
416
+ return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
357
417
 
358
418
  if self.return_exception_as_code_400 :
359
419
  @self.app.errorhandler(Exception)
@@ -392,7 +452,7 @@ class UISERVER :
392
452
  }
393
453
  self.logger.error(f'error: {json.dumps(response , indent=4)}')
394
454
  self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
395
- return Response().error( **response )
455
+ return self.Response.error( **response )
396
456
 
397
457
  @self.app.after_request
398
458
  def after_request(response) :
@@ -424,11 +484,10 @@ class UISERVER :
424
484
  client.internalid = getRandomKeysAndStr(n=20)
425
485
  client.sid = sid
426
486
  client.request = request
427
- query = parse_qs(request.query_string.decode('utf-8'))
428
- csid = lget(query.get('csid') , 0 , None )
429
- client.csid = csid
487
+ client.browserid = extract_buid( request.url , 'buid')
488
+ self.create_room(client.browserid , [client.sid])
430
489
  client.rooms = self.socketio_rooms
431
- self.socketio_clients.append(client)
490
+ self.socketio_clients[sid] = client
432
491
  self.socketio.emit('/internal/connect', {
433
492
  'status': 200,
434
493
  'message': 'client connected' ,
@@ -437,27 +496,20 @@ class UISERVER :
437
496
  },
438
497
  to=sid
439
498
  )
440
- if csid :
441
- self.sessions[csid] = client
442
- print(f'client connected : csid={csid} | SID={sid} | Clients : {len(self.socketio_clients)}')
499
+ self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
443
500
 
444
501
  # socketio client connected.
445
502
  @self.socketio.on('disconnect')
446
503
  def handle_client_disconnect():
447
- for i , c in enumerate(list(self.socketio_clients)) :
448
- print(f'c.sid : {c.sid} | request.sid : {request.sid}')
449
- if c.sid == request.sid :
450
- try :
451
- del self.socketio_clients[i]
452
- except :
453
- pass
454
- print(f'client disconnected : csid={c.csid} | SID={request.sid} | Clients : {len(self.socketio_clients)}')
455
- break
456
-
457
- def getSocketio( self ):
504
+ if request.sid in list(self.socketio_clients.keys()) :
505
+ del self.socketio_clients[request.sid]
506
+ self.logger.info(f'Disconnected : {request.sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
507
+
508
+
509
+ def getSocketio( self ) -> SocketIO:
458
510
  return self.socketio
459
511
 
460
- def getFlask( self ):
512
+ def getFlask( self ) -> Flask:
461
513
  return self.app
462
514
 
463
515
  def shutdownUi(self) :
@@ -0,0 +1,606 @@
1
+ import gc
2
+ import json
3
+ import time
4
+ from flask.ctx import F
5
+ from werkzeug.serving import ThreadedWSGIServer
6
+ from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
7
+ from flask_socketio import SocketIO
8
+ from engineio.async_drivers import gevent
9
+ from engineio.async_drivers import threading as threading_engineio
10
+ from flask_cors import CORS
11
+ import logging , os
12
+ from flask import jsonify, request , current_app , copy_current_request_context
13
+ from flask import Flask
14
+ from threading import Thread
15
+ import threading
16
+ from easy_utils_dev.custom_env import cenv
17
+ from easy_utils_dev.utils import kill_thread
18
+ from multiprocessing import Process
19
+ import traceback
20
+ from werkzeug.serving import make_ssl_devcert
21
+ from time import sleep
22
+ from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs , lget
23
+ from easy_utils_dev.temp_memory import TemporaryMemory
24
+ from easy_utils_dev.debugger import DEBUGGER
25
+ import signal
26
+ import sys
27
+ from tempfile import gettempdir
28
+ from urllib.parse import urlparse, parse_qs
29
+
30
+ TMP_PATH = gettempdir()
31
+
32
+
33
+ def extract_buid(url: str , key ) -> str | None:
34
+ """
35
+ Extracts the 'buid' query parameter from a URL.
36
+ Returns None if not found.
37
+ """
38
+ parsed = urlparse(url)
39
+ query_params = parse_qs(parsed.query)
40
+ return query_params.get( key , [None])[0]
41
+
42
+
43
+ def getClassById( id ) :
44
+ return cenv[id]
45
+
46
+ def create_ssl(host,output) :
47
+ '''
48
+ host : is the IP/Adress of the server which servers the web-server
49
+ output: the output locaiton to generate the ssl certificate. it should end with filename without extension
50
+ '''
51
+ return make_ssl_devcert( output , host=host)
52
+
53
+ def clone_request(request):
54
+ """Return a plain dict clone of Flask request data."""
55
+ return {
56
+ "method": request.method,
57
+ "path": request.path,
58
+ "url": request.url,
59
+ "headers": dict(request.headers),
60
+ "args": request.args.to_dict(flat=False),
61
+ "form": request.form.to_dict(flat=False),
62
+ "json": request.get_json(silent=True),
63
+ "data": request.get_data(), # raw body bytes
64
+ "files": {k: v.filename for k, v in request.files.items()},
65
+ "remote_addr": request.remote_addr,
66
+ "cookies": request.cookies,
67
+ }
68
+
69
+ class Stream :
70
+ def __init__(self) :
71
+ self.rows = []
72
+
73
+ def register_row(self , row , options={}) :
74
+ _row_stream_id = getRandomKeysAndStr(n=20)
75
+ row['_row_stream_id'] = _row_stream_id
76
+ for key , value in options.items() :
77
+ if not key.startswith('_') :
78
+ raise ValueError(f"Option key '{key}' must start with '_'")
79
+ row[key] = value
80
+ return row
81
+
82
+ def send_cell_update(self , row , cell , value , sid=None ) :
83
+ _row_stream_id = row['_row_stream_id']
84
+ self.socketio.emit(f'/stream/{_row_stream_id}/{cell}' , value , to=sid)
85
+
86
+ def send_new_row(self , tid , row , sid=None , insert_ontop=False ) :
87
+ if not row.get('_row_stream_id') :
88
+ row = self.register_table_row_stream(row)
89
+ self.socketio.emit(f'/stream/table/{tid}/row' , {
90
+ 'row' : row,
91
+ 'options' : {
92
+ 'insert_ontop' : insert_ontop
93
+ }
94
+ } , to=sid )
95
+
96
+ class Response :
97
+ def __init__(self, socket : SocketIO , request : request ) :
98
+ self.success = self.ok
99
+ self.failure = self.error
100
+ self.socket = socket
101
+ self.request = request
102
+
103
+ def _emit(self , data, role , options={} ) :
104
+ self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
105
+
106
+ def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
107
+ role = None
108
+ if alert :
109
+ role = 'alert'
110
+ elif toast :
111
+ role = 'toast'
112
+ timestamp = getTimestamp()
113
+ r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
114
+ if role :
115
+ self._emit( r , role, options)
116
+ return r
117
+
118
+ def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
119
+ role = None
120
+ if alert :
121
+ role = 'alert'
122
+ elif toast :
123
+ role = 'toast'
124
+ timestamp = getTimestamp()
125
+ r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
126
+ if role :
127
+ self._emit( r , role , options)
128
+ return r
129
+
130
+ def internal_error(self , message=None , alert=False, toast=False , options={} , **kwargs ) :
131
+ role = None
132
+ if alert :
133
+ role = 'alert'
134
+ elif toast :
135
+ role = 'toast'
136
+ timestamp = getTimestamp()
137
+ r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
138
+ if role :
139
+ self._emit( r , role , options)
140
+ return r
141
+
142
+ def not_found(self , message=None , alert=False, toast=False , options={} , **kwargs) :
143
+ role = None
144
+ if alert :
145
+ role = 'alert'
146
+ elif toast :
147
+ role = 'toast'
148
+ timestamp = getTimestamp()
149
+ r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
150
+ if role :
151
+ self._emit( r , role , options )
152
+ return r
153
+
154
+ def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
155
+ role = None
156
+ if alert :
157
+ role = 'alert'
158
+ elif toast :
159
+ role = 'toast'
160
+ timestamp = getTimestamp()
161
+ r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
162
+ if role :
163
+ self._emit( r , role , options)
164
+ return r
165
+
166
+ class AbortRequest :
167
+ def __init__(self, request ) :
168
+ self.request = clone_request(request)
169
+ self.abort_id = None
170
+ self.abortable = False
171
+ self.thread = None
172
+ self.cache = None
173
+ self.start_ts = getTimestamp()
174
+ self.result = None
175
+ self.async_request = False
176
+ self.internalid = None
177
+ self.in_progress = False
178
+ self.start_ts = None
179
+ self.end_ts = None
180
+ self.execution = None
181
+ self.success = None
182
+ self.traceback = None
183
+ self.delete_async_request_ts = None
184
+ self.killed = False
185
+
186
+ def abort(self) :
187
+ kill_thread(self.thread)
188
+ self.killed = True
189
+ self.in_progress = False
190
+ self.success = False
191
+ self.traceback = None
192
+ self.result = None
193
+ self.delete_async_request_ts = getTimestamp(after_seconds=3600)
194
+ self.end_ts = getTimestamp()
195
+ self.execution = round(self.end_ts - self.start_ts, 2)
196
+ if not self.async_request :
197
+ self.cache.delete(self.abort_id)
198
+ try :
199
+ gc.collect()
200
+ except :
201
+ pass
202
+
203
+ class SocketClientObject :
204
+ def __init__(self ) :
205
+ self.client : SocketIO
206
+ self.internalid : str = None
207
+ self.request = {}
208
+ self.sid = None
209
+ self.rooms = None
210
+ self.csid = None
211
+ self.browserid = None
212
+
213
+ class UISERVER :
214
+ def __init__(self ,
215
+ logger : DEBUGGER = None,
216
+ id=getRandomKey(n=15),
217
+ secretkey=generateToken(),
218
+ serve_with_secret_key=False,
219
+ address='localhost',
220
+ port=5312 ,
221
+ https=False ,
222
+ ssl_crt=None,
223
+ ssl_key=None,
224
+ template_folder='templates/' ,
225
+ static_folder = 'templates/assets'
226
+ ,**kwargs
227
+ ) -> None:
228
+ self.id = id
229
+ self.static_folder = static_folder
230
+ self.app = app = Flask(self.id , template_folder=template_folder , static_folder=self.static_folder )
231
+ app.config['SECRET_KEY'] = secretkey
232
+ CORS(app,resources={r"/*":{"origins":"*"}})
233
+ self.address= address
234
+ self.port = port
235
+ self.thread = None
236
+ self.ssl_crt=ssl_crt
237
+ self.ssl_key=ssl_key
238
+ self.serve_with_secret_key=serve_with_secret_key
239
+ self.secretkey=secretkey
240
+ self.enable_test_url=True
241
+ self.abort_requests = {}
242
+ self.bg_requests = {}
243
+ self.socketio_clients = {}
244
+ self.abort_base_url = '/request/abort'
245
+ self.return_exception_as_code_400 = True
246
+ self.request_reply_base_url= '/request/result'
247
+ self.sessions = {}
248
+ if https :
249
+ self.httpProtocol = 'https'
250
+ else :
251
+ self.httpProtocol = 'http'
252
+ self.socketio = SocketIO(
253
+ app , cors_allowed_origins="*" ,
254
+ async_mode='threading' ,
255
+ engineio_logger=False ,
256
+ always_connect=True ,
257
+ manage_session=True,
258
+ **kwargs
259
+ )
260
+ cenv[id] = self
261
+ self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
262
+ self.cache = TemporaryMemory()
263
+ start_thread(target=self.delete_very_old_requests)
264
+ self.secret_key_execlude_urls = []
265
+ self.socketio_rooms = {}
266
+ self.log_url_requests = True
267
+ self.logger = logger
268
+ self.stream = Stream()
269
+ self.simulate_network_delay = False
270
+ self.Response = Response(self.socketio , request )
271
+ if not self.logger :
272
+ self.logger = DEBUGGER(
273
+ name='easy_utils_dev_uiserver',
274
+ homePath=TMP_PATH
275
+ )
276
+ def update_cert(self , crt, ssl ) :
277
+ self.ssl_crt=crt
278
+ self.ssl_key=ssl
279
+
280
+ def register_abortable_request(self , request , abort_id = None ) :
281
+ path = request.path
282
+ Abort = AbortRequest(request)
283
+ if not path.startswith(self.abort_base_url) :
284
+ if not abort_id :
285
+ if not request.headers.get('abortid') :
286
+ abort_id = getRandomKeysAndStr(n=20)
287
+ else :
288
+ abort_id = request.headers.get('abortid')
289
+
290
+ Abort.abort_id = abort_id
291
+ current_thread = threading.current_thread()
292
+ Abort.thread = current_thread
293
+ Abort.cache = self.cache
294
+ Abort.start_ts = getTimestamp()
295
+ self.cache.set( Abort , custom_key=abort_id , auto_destroy_period=120 , store_deleted_key=False )
296
+ return Abort
297
+
298
+ def delete_very_old_requests(self) :
299
+ while True :
300
+ sleep(320)
301
+ now = getTimestamp()
302
+ for key, value in list(self.bg_requests.items()) :
303
+ value : AbortRequest = value
304
+ if value.delete_async_request_ts :
305
+ if value.delete_async_request_ts > now :
306
+ del self.bg_requests[key]
307
+ gc.collect()
308
+
309
+ def create_room(self , room_id : str , members : list[str] ) :
310
+ if room_id in list(self.socketio_rooms.keys()) :
311
+ return
312
+ self.socketio_rooms[room_id] = members
313
+
314
+ def add_member_to_room(self , room_id : str , member : str ) :
315
+ if not room_id in list(self.socketio_rooms.keys()) :
316
+ return
317
+ if member in self.socketio_rooms[room_id] :
318
+ return
319
+ self.socketio_rooms[room_id].append(member)
320
+
321
+ def remove_member_from_room(self , room_id : str , member : str ) :
322
+ self.socketio_rooms[room_id].remove(member)
323
+
324
+ def get_room_members(self , room_id : str ) :
325
+ return self.socketio_rooms[room_id]
326
+
327
+ def start_before_request(self) :
328
+
329
+ @self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
330
+ def abort_request(id : str ) :
331
+ abort : AbortRequest = self.cache.get(id)
332
+ timestamp = getTimestamp()
333
+ if abort :
334
+ abort.abort()
335
+ for i in range(30) :
336
+ th = abort.thread
337
+ alive = th.is_alive()
338
+ if not alive :
339
+ break
340
+ time.sleep(.25)
341
+ return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
342
+
343
+ else :
344
+ return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
345
+
346
+ @self.app.route(f"/request/traceback/<key>" , methods=['GET'])
347
+ def get_traceback(key : str ) :
348
+ traceback = self.cache.get(key)
349
+ if traceback :
350
+ return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
351
+ else :
352
+ return self.Response.not_found(message='Traceback not found or expired')
353
+
354
+ @self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
355
+ def get_result_of_async_request(id : str ) :
356
+ request : AbortRequest = self.bg_requests.get(id)
357
+ if request :
358
+ return self.Response.ok(
359
+ message='Result of async request found' ,
360
+ result=request.result ,
361
+ in_progress=request.in_progress ,
362
+ async_request=request.async_request ,
363
+ internalid=request.internalid ,
364
+ start_ts=request.start_ts ,
365
+ end_ts=request.end_ts ,
366
+ execution=request.execution ,
367
+ success=request.success ,
368
+ killed=request.killed
369
+ )
370
+
371
+
372
+ @self.app.before_request
373
+ def before_request() :
374
+ if self.log_url_requests and self.logger :
375
+ self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
376
+
377
+ if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
378
+ return self.Response.unauthorized(message='Secret key is invalid')
379
+
380
+ @copy_current_request_context
381
+ def run_async_job_results( target_func , abort : AbortRequest ) :
382
+ abort.in_progress = True
383
+ abort.async_request = True
384
+ abort.internalid = request.internalid
385
+ abort.start_ts = getTimestamp()
386
+ try :
387
+ result = target_func(*request.args, **request.form)
388
+ abort.success = True
389
+ except Exception as e :
390
+ abort.success = False
391
+ abort.result = str(e)
392
+ abort.traceback = traceback.format_exc()
393
+ raise
394
+ abort.result = result
395
+ abort.end_ts = getTimestamp()
396
+ abort.execution = round(abort.end_ts - abort.start_ts, 2)
397
+ abort.in_progress = False
398
+ abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
399
+
400
+ abortable = request.headers.get('abortable')
401
+ requestId = getRandomKeysAndStr(n=10)
402
+ request.start_ts = getTimestamp()
403
+ request.internalid = requestId
404
+ if abortable :
405
+ abort = self.register_abortable_request(request)
406
+ request.abortable = True
407
+ request.abort_id = abort.abort_id
408
+ # check here if async in the headers
409
+ # if yes . i will trigger the function in thread
410
+ # start_tread(#how to get the target function here ? )
411
+ # now i want to return response to UI { status : 200 , message : 'request now in running bg' , abort_id : abort.abort_id }
412
+ # the flask function should not be called again
413
+ if request.headers.get('async') == 'false' :
414
+ target_func = current_app.view_functions.get(request.endpoint)
415
+ if not target_func:
416
+ return self.Response.not_found(message='Route not found')
417
+ th = start_thread(target=run_async_job_results, args=[target_func , abort ])
418
+ abort.thread = th
419
+ self.bg_requests[requestId] = abort
420
+ return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
421
+
422
+ if self.return_exception_as_code_400 :
423
+ @self.app.errorhandler(Exception)
424
+ def handle_exception(e):
425
+
426
+ exc_type, exc_value, exc_traceback = sys.exc_info()
427
+ key = getRandomKeysAndStr(n=10)
428
+ tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
429
+ # Example: file, line, function, text
430
+ error_file = tb_last.filename
431
+ error_line = tb_last.lineno
432
+ error_func = tb_last.name
433
+ error_code = tb_last.line
434
+
435
+ # Log the full traceback (optional)
436
+ traceback.print_exc()
437
+ # Customize the error response
438
+ t = getTimestamp()
439
+ response = {
440
+ "status" : 400 ,
441
+ "key" : key,
442
+ "error": str(e),
443
+ "message": str(e),
444
+ "type": type(e).__name__ ,
445
+ "request_full_url" : request.url ,
446
+ "request_method" : request.method ,
447
+ "endpoint" : request.endpoint ,
448
+ "request_path" : request.path ,
449
+ "error_file" : os.path.basename(error_file).replace('.py' , ''),
450
+ "error_line" : error_line,
451
+ "error_func" : error_func,
452
+ "error_code" : error_code,
453
+ 'timestamp' : t ,
454
+ "date" : convertTimestampToDate(t) ,
455
+ 'traceback_url' : f"/request/traceback/{key}"
456
+ }
457
+ self.logger.error(f'error: {json.dumps(response , indent=4)}')
458
+ self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
459
+ return self.Response.error( **response )
460
+
461
+ @self.app.after_request
462
+ def after_request(response) :
463
+
464
+ if self.simulate_network_delay :
465
+ time.sleep(self.simulate_network_delay)
466
+ now = getTimestamp()
467
+ x = round(now - request.start_ts, 2)
468
+ try :
469
+ response.headers['internalid'] = request.internalid
470
+ response.headers['start_ts'] = request.start_ts
471
+ response.headers['end_ts'] = now
472
+ response.headers['execution'] = x
473
+ if request.abortable :
474
+ response.headers['abortid'] = request.abort_id
475
+ response.headers['abortable'] = True
476
+ except :
477
+ response.headers['abortable'] = False
478
+ if self.log_url_requests and self.logger :
479
+ self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
480
+ return response
481
+
482
+
483
+ # socketio client connected.
484
+ @self.socketio.on('connect')
485
+ def handle_client_connect():
486
+ sid = request.sid
487
+ client = SocketClientObject()
488
+ client.internalid = getRandomKeysAndStr(n=20)
489
+ client.sid = sid
490
+ client.request = request
491
+ client.browserid = extract_buid( request.url , 'buid')
492
+ self.create_room( client.browserid , [])
493
+ self.add_member_to_room( client.browserid , sid)
494
+ client.rooms = self.socketio_rooms
495
+ self.socketio_clients[sid] = client
496
+ self.socketio.emit('/internal/connect', {
497
+ 'status': 200,
498
+ 'message': 'client connected' ,
499
+ 'internalid' : client.internalid,
500
+ 'sid' : sid,
501
+ },
502
+ to=sid
503
+ )
504
+ self.logger.debug(f"Current Rooms : {json.dumps(self.socketio_rooms , indent=4)}")
505
+ self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
506
+
507
+ # socketio client connected.
508
+ @self.socketio.on('disconnect')
509
+ def handle_client_disconnect():
510
+ if request.sid in list(self.socketio_clients.keys()) :
511
+ buid = self.socketio_clients[request.sid].browserid
512
+ del self.socketio_clients[request.sid]
513
+ self.remove_member_from_room( buid , request.sid)
514
+ self.logger.info(f'Disconnected : {request.sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
515
+
516
+
517
+ def getSocketio( self ) -> SocketIO:
518
+ return self.socketio
519
+
520
+ def getFlask( self ) -> Flask:
521
+ return self.app
522
+
523
+ def shutdownUi(self) :
524
+ if hasattr(self, 'wsgi_server') and self.wsgi_server:
525
+ try:
526
+ self.wsgi_server.shutdown()
527
+ self.wsgi_server.server_close()
528
+ except:
529
+ pass
530
+ kill_thread(self.thread)
531
+
532
+ def _wait_th(self , t ) :
533
+ # t.join()
534
+ while True :
535
+ time.sleep(36000)
536
+
537
+
538
+ def thrStartUi(self , suppress_prints=True) :
539
+ if self.enable_test_url :
540
+ if not suppress_prints :
541
+ print(f'TEST URL GET-METHOD /connection/test/internal')
542
+ @self.app.route('/connection/test/internal' , methods=['GET'])
543
+ def test_connection():
544
+ return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
545
+
546
+
547
+ if self.httpProtocol == 'http' :
548
+ con = None
549
+ elif self.httpProtocol == 'https' :
550
+ con=(self.ssl_crt , self.ssl_key)
551
+ self.wsgi_server = wsgi_server = ThreadedWSGIServer(
552
+ host = self.address ,
553
+ ssl_context=con,
554
+ # ssl_context=('ssl.crt', 'ssl.key'),
555
+ port = self.port,
556
+ app = self.app )
557
+ if not suppress_prints :
558
+ print(f"web-socket: {self.fullAddress}")
559
+ print(f"UI URL : {self.fullAddress}")
560
+ log = logging.getLogger('werkzeug')
561
+ log.setLevel(logging.ERROR)
562
+ wsgi_server.serve_forever()
563
+
564
+ def on_ctrl_c(self , sig=None, frame=None):
565
+ self.stopUi()
566
+ pid = os.getpid()
567
+ os.kill(pid, signal.SIGTERM)
568
+ sys.exit(0)
569
+
570
+
571
+ def startUi(self , daemon=False , suppress_prints=True , block=False) :
572
+ self.start_before_request()
573
+ self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
574
+ self.flaskprocess.daemon = daemon
575
+ self.flaskprocess.start()
576
+ start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
577
+ signal.signal(signal.SIGINT, self.on_ctrl_c )
578
+ if block:
579
+ self.wait()
580
+ return self.thread
581
+
582
+ def wait(self):
583
+ """
584
+ Block the main thread to keep it alive for signal handling (Ctrl+C).
585
+ This allows Ctrl+C to be properly detected. Call this after startUi()
586
+ if you want signal handling to work.
587
+ """
588
+ try:
589
+ while self.flaskprocess.is_alive():
590
+ self.flaskprocess.join(timeout=0.1)
591
+ except KeyboardInterrupt:
592
+ if not hasattr(self, '_shutting_down'):
593
+ self._shutting_down = True
594
+ self.on_ctrl_c()
595
+ except SystemExit:
596
+ # Re-raise SystemExit to allow proper program termination
597
+ raise
598
+
599
+ def stopUi(self) :
600
+ if hasattr(self, 'wsgi_server') and self.wsgi_server:
601
+ try:
602
+ self.wsgi_server.shutdown()
603
+ except:
604
+ pass
605
+ kill_thread(self.thread)
606
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.165
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.165'
4
-
5
- # Setting up
6
- setup(
7
- name="easy_utils_dev",
8
- version=VERSION,
9
- packages=find_packages(),
10
- include_package_data=True,
11
- install_requires=[
12
- 'psutil' ,
13
- 'ping3' ,
14
- 'snakebite-py3',
15
- 'flask' ,
16
- 'flask_cors' ,
17
- 'xmltodict' ,
18
- 'paramiko' ,
19
- 'oracledb' ,
20
- 'requests',
21
- 'flask_socketio',
22
- 'python-dotenv',
23
- 'gevent',
24
- 'pyzipper',
25
- 'pyjwt',
26
- 'authlib',
27
- 'kafka-python'
28
- ],
29
- keywords=['python3'],
30
- classifiers=[
31
- "Programming Language :: Python :: 3",
32
- ]
33
- )
File without changes