easy-utils-dev 2.154__tar.gz → 2.156__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 (43) hide show
  1. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/PKG-INFO +1 -1
  2. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/debugger.py +19 -14
  3. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/ne1830PSS.py +16 -2
  4. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/uiserver.py +118 -11
  5. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/wsnoclib.py +7 -0
  6. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/PKG-INFO +1 -1
  7. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/setup.py +1 -1
  8. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/MANIFEST.in +0 -0
  9. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/EasySsh.py +0 -0
  10. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/Events.py +0 -0
  11. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/FastQueue.py +0 -0
  12. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/NameObject.py +0 -0
  13. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/__init__.py +0 -0
  14. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/abortable.py +0 -0
  15. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/brevosmtp.py +0 -0
  16. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/check_license.py +0 -0
  17. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/cplib.py +0 -0
  18. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/custom_env.py +0 -0
  19. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/debugger-C-PF4PAMMP.py +0 -0
  20. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/easy_oracle.py +0 -0
  21. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/encryptor.py +0 -0
  22. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/ept.py +0 -0
  23. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  24. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  25. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/exceptions.py +0 -0
  26. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/filescompressor.py +0 -0
  27. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/generate_license.py +0 -0
  28. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/keycloakapi.py +0 -0
  29. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/lralib.py +0 -0
  30. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/nsp_kafka.py +0 -0
  31. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/openid_server.py +0 -0
  32. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/optics_utils.py +0 -0
  33. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/require_auth.py +0 -0
  34. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/simple_sqlite.py +0 -0
  35. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/temp_memory.py +0 -0
  36. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/utils.py +0 -0
  37. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/winserviceapi.py +0 -0
  38. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev/wsselib.py +0 -0
  39. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/SOURCES.txt +0 -0
  40. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  41. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/requires.txt +0 -0
  42. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/top_level.txt +0 -0
  43. {easy_utils_dev-2.154 → easy_utils_dev-2.156}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.154
3
+ Version: 2.156
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -45,7 +45,8 @@ class DEBUGGER:
45
45
  global_debugger=None,
46
46
  disable_log_write=False,
47
47
  file_name=None,
48
- seperate_files=True
48
+ seperate_files=True,
49
+ trust_env_log_path=True
49
50
  ):
50
51
  env = custom_env()
51
52
  setupEnvironment( 'debugger' )
@@ -63,6 +64,7 @@ class DEBUGGER:
63
64
  self.create_log_path(homePath, file_name)
64
65
  self.fullSyntax=fullSyntax
65
66
  self.onScreen= onscreen
67
+ self.trust_env_log_path = trust_env_log_path
66
68
  self.id = id
67
69
  self.stream_service = None
68
70
  if not env['debugger'].get(name) :
@@ -118,19 +120,18 @@ class DEBUGGER:
118
120
  start_thread(target=self.checks_in_bg)
119
121
 
120
122
  def create_log_path(self , base_path , logname ) :
123
+ # print(f"Creating log path : {base_path} {logname}")
121
124
  if not base_path :
122
- base_path = os.path.join(os.getcwd() , 'debug')
125
+ base_path = os.getcwd()
123
126
  if not logname :
124
127
  logname = self.name
125
- mkdirs(base_path)
126
- self.baseHomePath = base_path
127
- self.filename = logname
128
- self.lastAbsoluteHomePath = base_path
129
- self.log_path_with_filename = os.path.join(base_path, f'{logname}.log')
130
- # print(f"Filename : {self.filename}")
131
- # print(f"Base Home Path : {self.baseHomePath}")
132
- # print(f"Log path With Filename : {self.log_path_with_filename}")
133
- # print(f"Last Absolute Home Path : {self.lastAbsoluteHomePath}")
128
+ if base_path :
129
+ mkdirs(base_path)
130
+ self.baseHomePath = base_path
131
+ self.filename = logname
132
+ self.lastAbsoluteHomePath = base_path
133
+ self.log_path_with_filename = os.path.join(base_path, f'{logname}.log')
134
+
134
135
 
135
136
  def fix_file_not_found_file_rotation(self) :
136
137
  for i in range(self.BACKUP_COUNT) :
@@ -164,11 +165,13 @@ class DEBUGGER:
164
165
  self.console.setFormatter(self.formatter)
165
166
 
166
167
  def updateGlobalHomePath(self ) :
168
+ if not self.trust_env_log_path :
169
+ return
167
170
  if not self.isLogWriteDisabled :
168
171
  getFromEnv = self.env.get('debugger_homepath' , None )
169
172
  # print(f"getFromEnv : {getFromEnv}")
170
- self.create_log_path(getFromEnv, self.filename)
171
173
  if getFromEnv :
174
+ self.create_log_path(getFromEnv, self.filename)
172
175
  self.file_handler_class = self.createRotateFileHandler(self.log_path_with_filename)
173
176
 
174
177
  def updateGlobalSetLevel( self ) :
@@ -272,7 +275,8 @@ class DEBUGGER:
272
275
  self.logger.error(f'EASY_UTILS_DEBUG_LEVEL ENV must be one of [info,debug,warning,error,critical] | Current Env Variable Is "{EASY_UTILS_DEBUG_LEVEL}". Skipping ')
273
276
  else :
274
277
  self.set_level(EASY_UTILS_DEBUG_LEVEL)
275
- self.updateGlobalHomePath()
278
+ if self.trust_env_log_path :
279
+ self.updateGlobalHomePath()
276
280
  if os.environ.get("EASY_UTILS_ENABLE_PRINT" , '' ).lower() == 'true' :
277
281
  self.enable_print()
278
282
  sleep(10)
@@ -313,12 +317,13 @@ class DEBUGGER:
313
317
  def changeHomePath( self , path ) :
314
318
  def delet_later(lastAbsoluteHomePath) :
315
319
  if lastAbsoluteHomePath :
316
- sleep(3)
320
+ sleep(1)
317
321
  try :
318
322
  shutil.rmtree(lastAbsoluteHomePath)
319
323
  except :
320
324
  pass
321
325
  start_thread(target=delet_later, args=[self.lastAbsoluteHomePath])
326
+ sleep(.5)
322
327
  self.create_log_path( path , self.filename)
323
328
  self.file_handler_class = self.createRotateFileHandler(self.log_path_with_filename)
324
329
 
@@ -12,9 +12,23 @@ from easy_utils_dev import exceptions
12
12
 
13
13
 
14
14
  class PSS1830 :
15
- def __init__(self , sim=False , debug_name='Auto1830PSS' , auto_enable_tcp_forward=False,file_name=None,debug_home_path=None ) -> None:
15
+ def __init__(self ,
16
+ sim=False ,
17
+ debug_name='Auto1830PSS' ,
18
+ auto_enable_tcp_forward=False,
19
+ file_name=None,
20
+ debug_home_path=None,
21
+ trust_env_log_path=True,
22
+ debugger_kwargs={}
23
+ ) -> None:
16
24
  self.port = None
17
- self.logger = DEBUGGER(debug_name,file_name=file_name, homePath=debug_home_path)
25
+ self.trust_env_log_path = trust_env_log_path
26
+ self.logger = DEBUGGER(
27
+ debug_name,file_name=file_name,
28
+ homePath=debug_home_path,
29
+ trust_env_log_path=trust_env_log_path,
30
+ **debugger_kwargs
31
+ )
18
32
  self.connected = False
19
33
  self.channel = None
20
34
  self.nodeName = None
@@ -1,9 +1,12 @@
1
1
  import gc
2
+ import json
2
3
  import time
4
+ from flask.ctx import F
3
5
  from werkzeug.serving import ThreadedWSGIServer
4
- from easy_utils_dev.utils import getRandomKey , generateToken , getTimestamp
6
+ from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
5
7
  from flask_socketio import SocketIO
6
8
  from engineio.async_drivers import gevent
9
+ from engineio.async_drivers import threading as threading_engineio
7
10
  from flask_cors import CORS
8
11
  import logging , os
9
12
  from flask import jsonify, request , current_app , copy_current_request_context
@@ -18,6 +21,9 @@ from werkzeug.serving import make_ssl_devcert
18
21
  from time import sleep
19
22
  from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs
20
23
  from easy_utils_dev.temp_memory import TemporaryMemory
24
+ from easy_utils_dev.debugger import DEBUGGER
25
+ import signal
26
+ import sys
21
27
 
22
28
  def getClassById( id ) :
23
29
  return cenv[id]
@@ -92,6 +98,7 @@ class SocketClientObject :
92
98
 
93
99
  class UISERVER :
94
100
  def __init__(self ,
101
+ logger : DEBUGGER = None,
95
102
  id=getRandomKey(n=15),
96
103
  secretkey=generateToken(),
97
104
  serve_with_secret_key=False,
@@ -121,6 +128,7 @@ class UISERVER :
121
128
  self.bg_requests = {}
122
129
  self.socketio_clients = []
123
130
  self.abort_base_url = '/request/abort'
131
+ self.return_exception_as_code_400 = True
124
132
  self.request_reply_base_url= '/request/result'
125
133
  if https :
126
134
  self.httpProtocol = 'https'
@@ -133,6 +141,10 @@ class UISERVER :
133
141
  start_thread(target=self.delete_very_old_requests)
134
142
  self.secret_key_execlude_urls = []
135
143
  self.socketio_rooms = {}
144
+ self.log_url_requests = True
145
+ self.logger = logger
146
+ self.simulate_network_delay = False
147
+
136
148
 
137
149
  def update_cert(self , crt, ssl ) :
138
150
  self.ssl_crt=crt
@@ -158,6 +170,7 @@ class UISERVER :
158
170
 
159
171
  def delete_very_old_requests(self) :
160
172
  while True :
173
+ sleep(320)
161
174
  now = getTimestamp()
162
175
  for key, value in list(self.bg_requests.items()) :
163
176
  value : AbortRequest = value
@@ -165,7 +178,6 @@ class UISERVER :
165
178
  if value.delete_async_request_ts > now :
166
179
  del self.bg_requests[key]
167
180
  gc.collect()
168
- sleep(320)
169
181
 
170
182
  def create_room(self , room_id : str , members : list[str] ) :
171
183
  self.socketio_rooms[room_id] = members
@@ -197,6 +209,14 @@ class UISERVER :
197
209
  else :
198
210
  return { 'status' : 404 , 'message' : 'Request not found or request is not abortable. Check request headers for abortable flag.'}
199
211
 
212
+ @self.app.route(f"/request/traceback/<key>" , methods=['GET'])
213
+ def get_traceback(key : str ) :
214
+ traceback = self.cache.get(key)
215
+ if traceback :
216
+ return { 'status' : 200 , 'traceback' : traceback.get('traceback') }
217
+ else :
218
+ return { 'status' : 404 , 'message' : 'Traceback not found or expired' }
219
+
200
220
  @self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
201
221
  def get_result_of_async_request(id : str ) :
202
222
  request : AbortRequest = self.bg_requests.get(id)
@@ -217,6 +237,10 @@ class UISERVER :
217
237
 
218
238
  @self.app.before_request
219
239
  def before_request() :
240
+ if self.log_url_requests and self.logger :
241
+ self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
242
+
243
+
220
244
  if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
221
245
  return jsonify({"error": "Secret key is invalid"}), 401
222
246
 
@@ -257,26 +281,72 @@ class UISERVER :
257
281
  if request.headers.get('async') == 'false' :
258
282
  target_func = current_app.view_functions.get(request.endpoint)
259
283
  if not target_func:
260
- return jsonify({"error": "Route not found"}), 404
284
+ return {"error": "Route not found"}, 404
261
285
  th = start_thread(target=run_async_job_results, args=[target_func , abort ])
262
286
  abort.thread = th
263
287
  self.bg_requests[requestId] = abort
264
288
  return {"status": 200, "message": "Request now in running bg", "abort_id": abort.abort_id} , 200
265
289
 
290
+ if self.return_exception_as_code_400 :
291
+ @self.app.errorhandler(Exception)
292
+ def handle_exception(e):
293
+
294
+ exc_type, exc_value, exc_traceback = sys.exc_info()
295
+ key = getRandomKeysAndStr(n=10)
296
+ tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
297
+ # Example: file, line, function, text
298
+ error_file = tb_last.filename
299
+ error_line = tb_last.lineno
300
+ error_func = tb_last.name
301
+ error_code = tb_last.line
302
+
303
+ # Log the full traceback (optional)
304
+ traceback.print_exc()
305
+ # Customize the error response
306
+ t = getTimestamp()
307
+ response = {
308
+ "status" : 400 ,
309
+ "key" : key,
310
+ "error": str(e),
311
+ "message": str(e),
312
+ "type": type(e).__name__ ,
313
+ "request_full_url" : request.url ,
314
+ "request_method" : request.method ,
315
+ "endpoint" : request.endpoint ,
316
+ "request_path" : request.path ,
317
+ "error_file" : os.path.basename(error_file).replace('.py' , ''),
318
+ "error_line" : error_line,
319
+ "error_func" : error_func,
320
+ "error_code" : error_code,
321
+ 'timestamp' : t ,
322
+ "date" : convertTimestampToDate(t) ,
323
+ 'traceback_url' : f"/request/traceback/{key}"
324
+ }
325
+ self.logger.error(f'error: {json.dumps(response , indent=4)}')
326
+ self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
327
+ # del response['traceback']
328
+ return response , 200
266
329
 
267
330
  @self.app.after_request
268
331
  def after_request(response) :
332
+
333
+ if self.simulate_network_delay :
334
+ time.sleep(self.simulate_network_delay)
335
+
269
336
  try :
270
337
  now = getTimestamp()
271
338
  response.headers['internalid'] = request.internalid
272
339
  response.headers['start_ts'] = request.start_ts
273
340
  response.headers['end_ts'] = now
274
- response.headers['execution'] = round(now - request.start_ts, 2)
341
+ x = round(now - request.start_ts, 2)
342
+ response.headers['execution'] = x
275
343
  if request.abortable :
276
344
  response.headers['abortid'] = request.abort_id
277
345
  response.headers['abortable'] = True
278
346
  except :
279
347
  response.headers['abortable'] = False
348
+ if self.log_url_requests and self.logger :
349
+ self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
280
350
  return response
281
351
 
282
352
  # socketio client connected.
@@ -322,12 +392,18 @@ class UISERVER :
322
392
  return self.wsgi_server
323
393
 
324
394
  def shutdownUi(self) :
395
+ if hasattr(self, 'wsgi_server') and self.wsgi_server:
396
+ try:
397
+ self.wsgi_server.shutdown()
398
+ self.wsgi_server.server_close()
399
+ except:
400
+ pass
325
401
  kill_thread(self.thread)
326
- self.wsgi_server.server_close()
327
- self.wsgi_server.shutdown()
328
402
 
329
403
  def _wait_th(self , t ) :
330
- t.join()
404
+ # t.join()
405
+ while True :
406
+ time.sleep(10)
331
407
 
332
408
 
333
409
  def thrStartUi(self , suppress_prints=True) :
@@ -356,15 +432,46 @@ class UISERVER :
356
432
  log.setLevel(logging.ERROR)
357
433
  wsgi_server.serve_forever()
358
434
 
359
- def startUi(self ,daemon , suppress_prints=True) :
435
+ def on_ctrl_c(self , sig=None, frame=None):
436
+ self.stopUi()
437
+ pid = os.getpid()
438
+ os.kill(pid, signal.SIGTERM)
439
+ sys.exit(0)
440
+
441
+
442
+ def startUi(self , daemon=False , suppress_prints=True , block=False) :
360
443
  self.start_before_request()
361
444
  self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
362
- self.flaskprocess.daemon = False
445
+ self.flaskprocess.daemon = daemon
363
446
  self.flaskprocess.start()
364
447
  start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
448
+ signal.signal(signal.SIGINT, self.on_ctrl_c )
449
+ if block:
450
+ self.wait()
365
451
  return self.thread
366
452
 
453
+ def wait(self):
454
+ """
455
+ Block the main thread to keep it alive for signal handling (Ctrl+C).
456
+ This allows Ctrl+C to be properly detected. Call this after startUi()
457
+ if you want signal handling to work.
458
+ """
459
+ try:
460
+ while self.flaskprocess.is_alive():
461
+ self.flaskprocess.join(timeout=0.1)
462
+ except KeyboardInterrupt:
463
+ if not hasattr(self, '_shutting_down'):
464
+ self._shutting_down = True
465
+ self.on_ctrl_c()
466
+ except SystemExit:
467
+ # Re-raise SystemExit to allow proper program termination
468
+ raise
469
+
367
470
  def stopUi(self) :
471
+ if hasattr(self, 'wsgi_server') and self.wsgi_server:
472
+ try:
473
+ self.wsgi_server.shutdown()
474
+ except:
475
+ pass
368
476
  kill_thread(self.thread)
369
- return True
370
-
477
+ return True
@@ -1,3 +1,5 @@
1
+ from ast import Import
2
+ import sys
1
3
  from easy_utils_dev.debugger import DEBUGGER
2
4
  import requests , json , subprocess
3
5
  from requests.auth import HTTPBasicAuth as BAuth
@@ -16,9 +18,11 @@ from easy_utils_dev.utils import kill_thread
16
18
  import atexit
17
19
  try :
18
20
  from snakebite.client import Client as HdfsClient
21
+ SNAKEBITEIMPORTED = True
19
22
  except Exception as e:
20
23
  print(f'Warning: snakebite is not installed. PM Hadoop client will not be available. {e}')
21
24
  HdfsClient = None
25
+ SNAKEBITEIMPORTED = False
22
26
 
23
27
  from datetime import datetime
24
28
 
@@ -751,6 +755,9 @@ class PmHadoopClient :
751
755
 
752
756
 
753
757
  def connect(self) :
758
+ if not SNAKEBITEIMPORTED :
759
+ self.logger.error(f"HDFS Client importing had error. Maybe not supporting on this OS {sys.platform}. Exit")
760
+ raise ImportError(f"HDFS Client importing had error. Maybe not supporting on this OS {sys.platform}. Exit")
754
761
  if not self.wsnoc.connected :
755
762
  raise Exception('WSNOC is not connected')
756
763
  try :
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.154
3
+ Version: 2.156
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '2.154'
3
+ VERSION = '2.156'
4
4
 
5
5
  # Setting up
6
6
  setup(
File without changes