easy-utils-dev 2.139__py3-none-any.whl → 2.141__py3-none-any.whl

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.

Potentially problematic release.


This version of easy-utils-dev might be problematic. Click here for more details.

@@ -0,0 +1,486 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ from datetime import datetime
5
+ from logging.handlers import RotatingFileHandler
6
+ from .utils import getRandomKey , convert_mb_to_bytes , getTimestamp , start_thread
7
+ from .custom_env import custom_env , setupEnvironment
8
+ from .Events import EventEmitter
9
+ from threading import Thread
10
+ from time import sleep
11
+
12
+ gEvent = EventEmitter()
13
+ logging.addLevelName(25, "SCREEN")
14
+
15
+ def setGlobalHomePath( path ) :
16
+ env = custom_env()
17
+ env['debugger_homepath'] = path
18
+ gEvent.dispatchEvent('update_home_path')
19
+
20
+ def setGlobalDisableOnScreen(on_screen=False) :
21
+ env = custom_env()
22
+ env['debugger_on_screen'] = on_screen
23
+ if not on_screen :
24
+ gEvent.dispatchEvent('disable_global_printing')
25
+ else :
26
+ gEvent.dispatchEvent('enable_global_printing')
27
+
28
+
29
+ def setGlobalDebugLevel(level='info') :
30
+ env = custom_env()
31
+ env['debugger_global_level'] = level
32
+ gEvent.dispatchEvent('update_debug_level')
33
+
34
+
35
+ class DEBUGGER:
36
+ def __init__(self
37
+ , name
38
+ , level='info',
39
+ fullSyntax=True,
40
+ onscreen=True,
41
+ log_rotation=3,
42
+ homePath=None,
43
+ id=getRandomKey(9) ,
44
+ global_debugger=None,
45
+ disable_log_write=False,
46
+ file_name=None,
47
+ seperate_files=True
48
+ ):
49
+ env = custom_env()
50
+ setupEnvironment( 'debugger' )
51
+ debugger_on_screen = env.get('debugger_on_screen' , True)
52
+ env['debugger_on_screen'] = debugger_on_screen
53
+ self.env = env
54
+ self.events = gEvent
55
+ self.debuggerLabel = f"{name}"
56
+ self.logger = logging.getLogger(self.debuggerLabel)
57
+ self.set_level(level)
58
+ self.file_handler_class=None
59
+ self.LOG_SIZE_THRESHOLD_IN_BYTES = 10 * 1024 * 1024
60
+ self.BACKUP_COUNT = log_rotation
61
+ self.homePath = homePath
62
+ self.lastAbsoluteHomePath= None
63
+ self.fullSyntax=fullSyntax
64
+ self.onScreen= onscreen
65
+ self.id = id
66
+ self.how_many_times_write= 0
67
+ self.stream_service = None
68
+ if not env['debugger'].get(name) :
69
+ self.console = console_handler = logging.StreamHandler()
70
+ else :
71
+ self.console = console_handler = env['debugger'].get(name).console
72
+ if not self.logger.hasHandlers() :
73
+ self.logger.addHandler(self.console)
74
+ self.name = name
75
+ self.rotate_disabled=False
76
+ self.isInPyinstaller = False
77
+ self.log_iterations=0
78
+ self.log_iterations_threshold = 200
79
+ self.global_debugger = global_debugger
80
+ self.isLogWriteDisabled = disable_log_write
81
+ self.type = "CUSTOM_DEBUGGER"
82
+ self.seperate_files=seperate_files
83
+ if fullSyntax :
84
+ f = f"[%(asctime)s]-[{self.name}]-[%(levelname)s]: %(message)s"
85
+ else :
86
+ f = f"[{self.name}]-[%(levelname)s]: %(message)s"
87
+ self.syntax = f
88
+ self.formatter = logging.Formatter(f , datefmt='%Y-%m-%d %H:%M:%S' )
89
+ self.filename = file_name
90
+ path = self.homepath(homePath)
91
+ if not env['debugger'].get(name) :
92
+ console_handler.setFormatter(self.formatter)
93
+ if not disable_log_write :
94
+ if not env['debugger'].get(name) :
95
+ self.file_handler_class = self.createRotateFileHandler(path)
96
+ if onscreen :
97
+ self.enable_print()
98
+ elif not onscreen :
99
+ self.disable_print()
100
+ self.events.addEventListener('disable_global_printing' , self.disable_print )
101
+ self.events.addEventListener('enable_global_printing' , self.enable_print )
102
+ self.events.addEventListener('update_home_path' , self.updateGlobalHomePath )
103
+ self.events.addEventListener('update_debug_level' , self.updateGlobalSetLevel )
104
+ if env['debugger'].get(name) :
105
+ self = env['debugger'].get(name)
106
+ else:
107
+ env['debugger'][id] = self
108
+ env['debugger'][name] = self
109
+ if not env.get('debugger_on_screen' , True ) :
110
+ self.disable_print()
111
+ if env.get('debugger_on_screen' , True ) :
112
+ self.enable_print()
113
+ if os.environ.get("EASY_UTILS_DEBUG_LEVEL") :
114
+ EASY_UTILS_DEBUG_LEVEL = os.environ.get("EASY_UTILS_DEBUG_LEVEL")
115
+ if not EASY_UTILS_DEBUG_LEVEL.lower() in ['info' , 'debug' , 'warning' , 'error' , 'critical'] :
116
+ 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 ')
117
+ else :
118
+ self.set_level(EASY_UTILS_DEBUG_LEVEL)
119
+ if os.environ.get("EASY_UTILS_ENABLE_PRINT" , '' ).lower() == 'true' :
120
+ self.enable_print()
121
+
122
+ def switch_full_syntax(self , toggle) :
123
+ if toggle :
124
+ f = f"[%(asctime)s]-[{self.name}]-[%(levelname)s]: %(message)s"
125
+ else :
126
+ f = f"[{self.name}]-[%(levelname)s]: %(message)s"
127
+ self.syntax = f
128
+ self.formatter = logging.Formatter(f , datefmt='%Y-%m-%d %H:%M:%S' )
129
+ self.console.setFormatter(self.formatter)
130
+
131
+ def custom_log_syntax(self , syntax) :
132
+ '''
133
+ f"[%(asctime)s]-[{self.name}]-[%(levelname)s]: %(message)s"
134
+ '''
135
+ f = syntax
136
+ self.syntax = f
137
+ self.formatter = logging.Formatter(f , datefmt='%Y-%m-%d %H:%M:%S' )
138
+ self.console.setFormatter(self.formatter)
139
+
140
+ def updateGlobalHomePath(self ) :
141
+ if not self.isLogWriteDisabled :
142
+ getFromEnv = self.env.get('debugger_homepath' , None )
143
+ self.homepath(getFromEnv)
144
+ if getFromEnv :
145
+ self.file_handler_class = self.createRotateFileHandler(self.homePath)
146
+
147
+ def updateGlobalSetLevel( self ) :
148
+ self.set_level(self.env['debugger_global_level'])
149
+
150
+ def advertiseGlobalDebugLevel(self , level) :
151
+ setGlobalDebugLevel(level)
152
+
153
+ def disable_rotate(self) :
154
+ self.rotate_disabled = True
155
+
156
+ def enable_rotate(self) :
157
+ self.rotate_disabled = False
158
+
159
+ def createRotateFileHandler( self , path ) :
160
+ old = self.file_handler_class
161
+ if old :
162
+ self.logger.removeHandler(old)
163
+ file_handler = RotatingFileHandler(path , maxBytes=self.LOG_SIZE_THRESHOLD_IN_BYTES , backupCount=self.BACKUP_COUNT , delay=True )
164
+ self.file_handler= file_handler.setFormatter(self.formatter)
165
+ self.logger.addHandler(file_handler)
166
+ return file_handler
167
+
168
+ def update_log_iterantions_threshold(self,threshold : int ):
169
+ '''
170
+ set value when rotation should be checked. when every on_log function called.
171
+ by default rotation will be checked every 200 on_log function call.
172
+ '''
173
+ self.log_iterations_threshold = threshold
174
+
175
+ def updateGlobalDebugger(self , logger ) :
176
+ '''
177
+ this function pass the log message to other logger to write the same log message to it.
178
+ logger must be debugger class.
179
+ '''
180
+ if logger.type != 'CUSTOM_DEBUGGER' :
181
+ raise Exception(f'Invalid logger type. must pass debugger class.')
182
+ self.global_debugger = logger
183
+
184
+ def getStreamServiceUrlPath(self) :
185
+ return self.streampath
186
+
187
+ def getStreamService(self) :
188
+ return self.stream_service
189
+
190
+ def isStreamServiceAvailable(self) :
191
+ if self.stream_service :
192
+ return True
193
+ return False
194
+
195
+ def addStreamService( self , socketio , streampath='/debugger/stream/log' ) :
196
+ """
197
+ This function takes a live socketio server. it emit the log message using default path which is /debugger/stream/log
198
+ """
199
+ self.stream_service = socketio
200
+ self.streampath = streampath
201
+
202
+ def updateLogName( self , name ) :
203
+ self.name = name
204
+
205
+ def disable_log_write(self) :
206
+ '''
207
+ this function is used to disable the log write to file. if onScreen is enabled, logs will be displayed only on screen.
208
+ '''
209
+ self.isLogWriteDisabled = True
210
+ if self.file_handler_class :
211
+ self.logger.removeHandler(self.file_handler_class)
212
+
213
+ def enable_log_write(self) :
214
+ self.createRotateFileHandler(self.homePath)
215
+
216
+ def manage_file_rotation(self, record ) :
217
+ handler = self.get_rotate_handler()
218
+ if handler.shouldRollover(record) :
219
+ handler.doRollover()
220
+ self.log_iterations = 0
221
+
222
+ def get_rotate_handler(self) :
223
+ return self.file_handler_class
224
+
225
+ def change_log_size(self, size) -> bool:
226
+ '''
227
+ change the size of each log file rotation.
228
+ default is 10M
229
+ size should be passed as MB
230
+ '''
231
+ size = convert_mb_to_bytes(size)
232
+ self.LOG_SIZE_THRESHOLD_IN_BYTES = size
233
+ handler = self.get_rotate_handler()
234
+ handler.maxBytes = size
235
+ return True
236
+
237
+
238
+ def checks_in_bg(self) :
239
+ while True :
240
+ if self.env.get('GLOBAL_DEBUGGER_STREAM_SERVICE') :
241
+ self.addStreamService(socketio=self.env.get('GLOBAL_DEBUGGER_STREAM_SERVICE'))
242
+ if self.env.get('debugger_global_level' , None) :
243
+ self.set_level( level=self.env.get('debugger_global_level') )
244
+ if not self.env.get('debugger_on_screen' , True ) :
245
+ self.disable_print()
246
+ if self.env.get('debugger_on_screen' , True ) :
247
+ self.enable_print()
248
+ if os.environ.get("EASY_UTILS_DEBUG_LEVEL") :
249
+ EASY_UTILS_DEBUG_LEVEL = os.environ.get("EASY_UTILS_DEBUG_LEVEL")
250
+ if not EASY_UTILS_DEBUG_LEVEL.lower() in ['info' , 'debug' , 'warning' , 'error' , 'critical'] :
251
+ 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 ')
252
+ else :
253
+ self.set_level(EASY_UTILS_DEBUG_LEVEL)
254
+ self.updateGlobalHomePath()
255
+ if os.environ.get("EASY_UTILS_ENABLE_PRINT" , '' ).lower() == 'true' :
256
+ self.enable_print()
257
+ sleep(5)
258
+
259
+ def close(self) :
260
+ try :
261
+ logging.shutdown()
262
+ except :
263
+ pass
264
+
265
+ def homepath(self , path=None ) :
266
+ env = custom_env()
267
+ getFromEnv = env.get('debugger_homepath' , None )
268
+ if getFromEnv is not None :
269
+ self.homePath = getFromEnv
270
+ else :
271
+ if path is not None :
272
+ if self.homePath and '.log' in str(self.homePath):
273
+ self.lastAbsoluteHomePath= self.homePath
274
+ self.homePath = path
275
+ else :
276
+ self.homePath = os.path.join(os.getcwd() , 'debug')
277
+ if not os.path.exists( self.homePath ) :
278
+ try :
279
+ os.makedirs( self.homePath )
280
+ except :
281
+ pass
282
+ if self.filename :
283
+ self.homePath = os.path.join( self.homePath, f'{self.filename}.log' )
284
+ else :
285
+ self.homePath = os.path.join( self.homePath, f'{self.name}.log' )
286
+ return self.homePath
287
+
288
+ def get_current_levels(self):
289
+ """
290
+ Returns a list of log levels that will be printed based on the current logging level.
291
+ """
292
+ levels_order = [
293
+ ('debug', logging.DEBUG),
294
+ ('info', logging.INFO),
295
+ ('warning', logging.WARNING),
296
+ ('error', logging.ERROR),
297
+ ('critical', logging.CRITICAL),
298
+ ]
299
+ # Optional custom level
300
+ if hasattr(logging, 'SCREEN'):
301
+ levels_order.append(('screen', logging.SCREEN))
302
+ current_level = self.logger.level
303
+ # Return all levels with numeric value >= current_level
304
+ return [name for name, value in levels_order if value >= current_level]
305
+
306
+ def enable_print(self) :
307
+ self.onScreen = True
308
+ self.logger.addHandler(self.console)
309
+
310
+ def disable_print(self) :
311
+ self.onScreen = False
312
+ self.logger.removeHandler(self.console)
313
+
314
+ def changeHomePath( self , path ) :
315
+ p = self.homepath(path)
316
+ self.file_handler_class = self.createRotateFileHandler(p)
317
+ if self.lastAbsoluteHomePath :
318
+ os.remove(self.lastAbsoluteHomePath)
319
+
320
+ def isGlobalDebuggerDefined(self) :
321
+ if self.global_debugger :
322
+ return True
323
+ else :
324
+ return False
325
+
326
+ def set_level(self, level : str):
327
+ if 'info' in level.lower() : lvl = logging.INFO
328
+ elif 'warn' in level.lower() : lvl = logging.WARNING
329
+ elif 'warning' in level.lower() : lvl = logging.WARNING
330
+ elif 'critical' in level.lower() : lvl = logging.CRITICAL
331
+ elif 'debug' in level.lower() : lvl = logging.DEBUG
332
+ elif 'error' in level.lower() : lvl = logging.ERROR
333
+ elif 'screen' in level.lower() : lvl = logging.SCREEN
334
+ else : raise ValueError('Unknown level, not one of [info,warn,warning,critical,debug,error,screen]')
335
+ self.currentDebugLevel = level
336
+ self.logger.setLevel(lvl)
337
+
338
+ def get_current_debug_level(self) :
339
+ return self.currentDebugLevel
340
+
341
+ def get_logger(self) :
342
+ return self.logger
343
+
344
+ def before_log(self , message , level) :
345
+
346
+ def __call_thread__() :
347
+ if not level in self.get_current_levels() :
348
+ return
349
+ if self.isStreamServiceAvailable() :
350
+ d = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
351
+ streamUrl = self.getStreamServiceUrlPath()
352
+ self.stream_service.emit( streamUrl , {
353
+ 'message' : message ,
354
+ 'level' : level ,
355
+ 'msg' : message,
356
+ 'date' : d ,
357
+ 'id' : self.id,
358
+ 'formate' : 'json' ,
359
+ 'source' : self.name ,
360
+ 'getTimestamp' : getTimestamp()
361
+ })
362
+ try :
363
+ t= Thread(target=__call_thread__)
364
+ # t.daemon=True
365
+ t.start()
366
+ except :
367
+ __call_thread__()
368
+
369
+ params = {
370
+ 'screen' : True ,
371
+ 'file': True
372
+ }
373
+ if self.onScreen and self.env['debugger_on_screen'] == True :
374
+ params['screen'] = True
375
+ else :
376
+ params['screen'] = False
377
+ return params
378
+
379
+
380
+ def info(self, message , external_debugger=None,source=None):
381
+ def __call__(message) :
382
+ if source :
383
+ message = f'[{source}]-{message}'
384
+ self.before_log(message , 'info')
385
+ self.logger.info(message)
386
+ if self.isGlobalDebuggerDefined() :
387
+ self.global_debugger.info(message)
388
+ if external_debugger :
389
+ external_debugger.info(message)
390
+ try :
391
+ r=Thread(target=__call__,args=[message])
392
+ r.daemon=True
393
+ r.start()
394
+ r.join()
395
+ except :
396
+ __call__(message)
397
+
398
+ def debug(self, message , external_debugger=None,source=None):
399
+ def __call__(message) :
400
+ if source :
401
+ message = f'[{source}]-{message}'
402
+ self.before_log(message , 'debug')
403
+ self.logger.debug(message)
404
+ if self.isGlobalDebuggerDefined() :
405
+ self.global_debugger.debug(message)
406
+ if external_debugger :
407
+ external_debugger.debug(message)
408
+ try :
409
+ r=Thread(target=__call__,args=[message])
410
+ r.daemon=True
411
+ r.start()
412
+ r.join()
413
+ except :
414
+ __call__(message)
415
+
416
+ def warning(self, message , external_debugger=None,source=None):
417
+ def __call__(message) :
418
+ if source :
419
+ message = f'[{source}]-{message}'
420
+ self.before_log(message , 'warning')
421
+ self.logger.warning(message)
422
+ if self.isGlobalDebuggerDefined() :
423
+ self.global_debugger.warning(message)
424
+ if external_debugger :
425
+ external_debugger.warning(message)
426
+ try :
427
+ r=Thread(target=__call__,args=[message])
428
+ r.daemon=True
429
+ r.start()
430
+ r.join()
431
+ except :
432
+ __call__(message)
433
+
434
+ def error(self, message,external_debugger=None,source=None):
435
+ def __call__(message) :
436
+ if source :
437
+ message = f'[{source}]-{message}'
438
+ self.before_log(message , 'error')
439
+ self.logger.error(message)
440
+ if self.isGlobalDebuggerDefined() :
441
+ self.global_debugger.error(message)
442
+ if external_debugger :
443
+ external_debugger.error(message)
444
+ try :
445
+ r=Thread(target=__call__,args=[message])
446
+ r.daemon=True
447
+ r.start()
448
+ r.join()
449
+ except :
450
+ __call__(message)
451
+
452
+ def critical(self, message,external_debugger=None,source=None):
453
+ def __call__() :
454
+ if source :
455
+ message = f'[{source}]-{message}'
456
+ self.before_log(message , 'critical')
457
+ self.logger.critical(message)
458
+ if self.isGlobalDebuggerDefined() :
459
+ self.global_debugger.critical(message)
460
+ if external_debugger :
461
+ external_debugger.critical(message)
462
+ try :
463
+ r=Thread(target=__call__,args=[message])
464
+ r.daemon=True
465
+ r.start()
466
+ r.join()
467
+ except :
468
+ __call__(message)
469
+
470
+ def screen(self, message,external_debugger=None,source=None):
471
+ def __call__() :
472
+ if source :
473
+ message = f'[{source}]-{message}'
474
+ self.before_log(message , 'critical')
475
+ print(f"{self.syntax}")
476
+ if self.isGlobalDebuggerDefined() :
477
+ self.global_debugger.critical(message)
478
+ if external_debugger :
479
+ external_debugger.critical(message)
480
+ try :
481
+ r=Thread(target=__call__,args=[message])
482
+ r.daemon=True
483
+ r.start()
484
+ r.join()
485
+ except :
486
+ __call__(message)
@@ -128,6 +128,15 @@ class DEBUGGER:
128
128
  self.formatter = logging.Formatter(f , datefmt='%Y-%m-%d %H:%M:%S' )
129
129
  self.console.setFormatter(self.formatter)
130
130
 
131
+ def custom_log_syntax(self , syntax) :
132
+ '''
133
+ f"[%(asctime)s]-[{self.name}]-[%(levelname)s]: %(message)s"
134
+ '''
135
+ f = syntax
136
+ self.syntax = f
137
+ self.formatter = logging.Formatter(f , datefmt='%Y-%m-%d %H:%M:%S' )
138
+ self.console.setFormatter(self.formatter)
139
+
131
140
  def updateGlobalHomePath(self ) :
132
141
  if not self.isLogWriteDisabled :
133
142
  getFromEnv = self.env.get('debugger_homepath' , None )
@@ -17,21 +17,28 @@ class TemporaryMemory :
17
17
  sleep(5)
18
18
  now = getTimestamp()
19
19
  for key, value in list(self.store.items()) :
20
- if now >= value.get('removeTimestamp' , 0) :
21
- if value.get('store_deleted_key' , False) :
22
- self.deletions.append(key)
23
- self.delete(key)
20
+ if value.get('removeTimestamp') :
21
+ if now >= value.get('removeTimestamp' , 0) :
22
+ if value.get('store_deleted_key' , False) :
23
+ self.deletions.append(key)
24
+ self.delete(key)
24
25
 
25
26
  def delete(self , key ) :
26
- del self.store[key]
27
+ try :
28
+ del self.store[key]
29
+ except :
30
+ pass
27
31
  gc.collect()
28
32
 
29
- def get(self , key ) :
30
- return self.store.get(key , {}).get('item')
33
+ def get(self , key , default=None ) :
34
+ return self.store.get(key , {}).get('item' , default)
31
35
 
32
36
  def save(self, item , custom_key=None ,auto_destroy_period=60 , store_deleted_key=True) :
33
37
  now = getTimestamp()
34
- later = getTimestamp(after_seconds=auto_destroy_period)
38
+ if auto_destroy_period :
39
+ later = getTimestamp(after_seconds=auto_destroy_period)
40
+ else :
41
+ later = None
35
42
  if not custom_key :
36
43
  custom_key = f"{getTimestamp()}-{generateToken(iter=4)}".upper()
37
44
  self.store[custom_key] = {
@@ -1,18 +1,23 @@
1
+ import copy
2
+ import gc
3
+ import time
1
4
  from werkzeug.serving import ThreadedWSGIServer
2
5
  from easy_utils_dev.utils import getRandomKey , generateToken , getTimestamp
3
6
  from flask_socketio import SocketIO
4
7
  from engineio.async_drivers import gevent
5
- from engineio.async_drivers import threading
6
8
  from flask_cors import CORS
7
9
  import logging , os
10
+ from flask import jsonify, request , current_app
8
11
  from flask import Flask
9
12
  from threading import Thread
13
+ import threading
10
14
  from easy_utils_dev.custom_env import cenv
11
15
  from easy_utils_dev.utils import kill_thread
12
16
  from multiprocessing import Process
13
17
  from werkzeug.serving import make_ssl_devcert
14
18
  from time import sleep
15
19
  from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs
20
+ from easy_utils_dev.temp_memory import TemporaryMemory
16
21
 
17
22
  def getClassById( id ) :
18
23
  return cenv[id]
@@ -23,87 +28,54 @@ def create_ssl(host,output) :
23
28
  output: the output locaiton to generate the ssl certificate. it should end with filename without extension
24
29
  '''
25
30
  return make_ssl_devcert( output , host=host)
26
-
27
-
28
31
 
29
- class Abort :
30
- def __init__(self, requestId=getRandomKeysAndStr(50)) :
31
- self.requestId = requestId
32
- self.result= None
32
+ def clone_request(request):
33
+ """Return a plain dict clone of Flask request data."""
34
+ return {
35
+ "method": request.method,
36
+ "path": request.path,
37
+ "url": request.url,
38
+ "headers": dict(request.headers),
39
+ "args": request.args.to_dict(flat=False),
40
+ "form": request.form.to_dict(flat=False),
41
+ "json": request.get_json(silent=True),
42
+ "data": request.get_data(), # raw body bytes
43
+ "files": {k: v.filename for k, v in request.files.items()},
44
+ "remote_addr": request.remote_addr,
45
+ "cookies": request.cookies,
46
+ }
47
+
48
+ class AbortRequest :
49
+ def __init__(self, request ) :
50
+ self.request = clone_request(request)
51
+ self.abort_id = None
52
+ self.abortable = False
33
53
  self.thread = None
34
- self.aborted=False
35
- self.response = {}
36
- self.error = False
37
- self.message = ''
38
- self.starttimestamp = getTimestamp()
39
- self.endtimestamp = 0
40
- self.kill = self.api_abort
41
- pass
42
-
43
-
44
- def abortable_process(self , operation , args=[] , kwargs={}) :
45
- def thread_run() :
46
- try :
47
- self.result = operation( *args, **kwargs )
48
- self.error = False
49
- self.message = ''
50
- self.endtimestamp = getTimestamp()
51
- except Exception as error :
52
- self.error = True
53
- self.message = str(error)
54
- thread = self.thread = start_thread( target = thread_run )
55
- while thread.is_alive() :
56
- sleep(.1)
57
- if self.aborted :
58
- self.response = {
59
- 'message' : 'request aborted.' ,
60
- 'id' : self.requestId ,
61
- 'status' : 405 ,
62
- 'result' : None,
63
- 'error' : self.error ,
64
- 'error_message' : '',
65
- 'starttimestamp' : self.starttimestamp,
66
- 'endtimestamp' : self.endtimestamp,
67
- 'aborted' : True,
68
- 'threadIsAlive' : thread.is_alive()
69
- }
70
- return self.response
71
- sleep(.2)
72
- self.response = {
73
- 'message' : 'request completed.' ,
74
- 'id' : self.requestId ,
75
- 'status' : 200 ,
76
- 'result' : self.result ,
77
- 'error' : self.error ,
78
- 'error_message' : self.message ,
79
- 'starttimestamp' : self.starttimestamp,
80
- 'endtimestamp' : self.endtimestamp,
81
- 'aborted' : False ,
82
- 'threadIsAlive' : thread.is_alive()
83
- }
84
- return self.response
85
-
86
-
87
- def api_abort(self) :
88
- self.endtimestamp = getTimestamp()
54
+ self.cache = None
55
+ self.start_ts = getTimestamp()
56
+
57
+ def abort(self) :
89
58
  kill_thread(self.thread)
90
- sleep(.5)
91
- self.aborted=True
59
+ self.cache.delete(self.abort_id)
60
+ try :
61
+ gc.collect()
62
+ except :
63
+ pass
92
64
 
93
65
 
94
66
  class UISERVER :
95
67
  def __init__(self ,
96
- id=getRandomKey(n=15),
97
- secretkey=generateToken(),
98
- address='localhost',
99
- port=5312 ,
100
- https=False ,
101
- ssl_crt=None,
102
- ssl_key=None,
103
- template_folder='templates/' ,
104
- static_folder = 'templates/assets'
105
- ,**kwargs
106
- ) -> None:
68
+ id=getRandomKey(n=15),
69
+ secretkey=generateToken(),
70
+ address='localhost',
71
+ port=5312 ,
72
+ https=False ,
73
+ ssl_crt=None,
74
+ ssl_key=None,
75
+ template_folder='templates/' ,
76
+ static_folder = 'templates/assets'
77
+ ,**kwargs
78
+ ) -> None:
107
79
  self.id = id
108
80
  self.static_folder = static_folder
109
81
  self.app = app = Flask(self.id , template_folder=template_folder , static_folder=self.static_folder )
@@ -116,6 +88,7 @@ class UISERVER :
116
88
  self.ssl_key=ssl_key
117
89
  self.enable_test_url=True
118
90
  self.abort_requests = {}
91
+ self.abort_base_url = '/request/abort'
119
92
  if https :
120
93
  self.httpProtocol = 'https'
121
94
  else :
@@ -123,18 +96,78 @@ class UISERVER :
123
96
  self.socketio = SocketIO(app , cors_allowed_origins="*" ,async_mode='threading' , engineio_logger=False , always_connect=True ,**kwargs )
124
97
  cenv[id] = self
125
98
  self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
99
+ self.cache = TemporaryMemory()
126
100
 
127
101
  def update_cert(self , crt, ssl ) :
128
102
  self.ssl_crt=crt
129
103
  self.ssl_key=ssl
130
104
 
105
+ def register_abortable_request(self , request , abort_id = None ) :
106
+ path = request.path
107
+ Abort = AbortRequest(request)
108
+ if not path.startswith(self.abort_base_url) :
109
+ if not abort_id :
110
+ if not request.headers.get('abortid') :
111
+ abort_id = getRandomKeysAndStr(n=20)
112
+ else :
113
+ abort_id = request.headers.get('abortid')
114
+
115
+ Abort.abort_id = abort_id
116
+ current_thread = threading.current_thread()
117
+ Abort.thread = current_thread
118
+ Abort.cache = self.cache
119
+ Abort.start_ts = getTimestamp()
120
+ self.cache.set( Abort , custom_key=abort_id , auto_destroy_period=120 , store_deleted_key=False )
121
+ return Abort
122
+
123
+ def start_before_request(self) :
124
+
125
+ @self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
126
+ def abort_request(id : str ) :
127
+ abort : AbortRequest = self.cache.get(id)
128
+ timestamp = getTimestamp()
129
+ if abort :
130
+ abort.abort()
131
+ for i in range(30) :
132
+ th = abort.thread
133
+ alive = th.is_alive()
134
+ if not alive :
135
+ break
136
+ time.sleep(.25)
137
+ return { 'status' : 200 , 'message' : 'Request aborted' , 'abort_timestamp' : timestamp , 'abort_id' : id , 'alive' : alive , 'url' : abort.request.get('path')}
138
+ else :
139
+ return { 'status' : 404 , 'message' : 'Request not found or request is not abortable. Check request headers for abortable flag.'}
140
+
141
+ @self.app.before_request
142
+ def before_request() :
143
+ abortable = request.headers.get('abortable')
144
+ if abortable :
145
+ abort = self.register_abortable_request(request)
146
+ request.abortable = True
147
+ request.abort_id = abort.abort_id
148
+ # check here if async in the headers
149
+ # if yes . i will trigger the function in thread
150
+ # start_tread(#how to get the target function here ? )
151
+ # now i want to return response to UI { status : 200 , message : 'request now in running bg' , abort_id : abort.abort_id }
152
+ # the flask function should not be called again
153
+ if request.headers.get('async') == 'false' :
154
+ target_func = current_app.view_functions.get(request.endpoint)
155
+ if not target_func:
156
+ return jsonify({"error": "Route not found"}), 404
157
+ th = start_thread(target=target_func, args=request.args, kwargs=request.form)
158
+ abort.thread = th
159
+ return {"status": 200, "message": "Request now in running bg", "abort_id": abort.abort_id} , 200
160
+
161
+ @self.app.after_request
162
+ def after_request(response) :
163
+ try :
164
+ if request.abortable :
165
+ response.headers['abortid'] = request.abort_id
166
+ response.headers['abortable'] = True
167
+ except :
168
+ response.headers['abortable'] = False
169
+ return response
131
170
 
132
- def getAbort(self , id ) :
133
- result : Abort = self.abort_requests.get(id , Abort)
134
- return result
135
-
136
- def updateAbort( self , id , abort ) :
137
- self.abort_requests[id] = abort
138
171
 
139
172
  def getInstance(self) :
140
173
  return self.getFlask() , self.getSocketio() , self.getWsgi()
@@ -181,6 +214,7 @@ class UISERVER :
181
214
  wsgi_server.serve_forever()
182
215
 
183
216
  def startUi(self ,daemon , suppress_prints=True) :
217
+ self.start_before_request()
184
218
  self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
185
219
  self.flaskprocess.daemon = False
186
220
  self.flaskprocess.start()
@@ -80,6 +80,7 @@ class WSNOCLIB :
80
80
  self.refresh_inprogress = False
81
81
  self.kafka = kafka
82
82
  self.refresh_thread = None
83
+ self.token_refresh_count = 0
83
84
  self.session = WSNOCSession(self)
84
85
  if register_atexit :
85
86
  atexit.register(self.goodbye)
@@ -135,7 +136,7 @@ class WSNOCLIB :
135
136
  data = {
136
137
  "grant_type": "client_credentials"
137
138
  }
138
- r = self.session.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), json=data)
139
+ r = self.session.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), json=data , retries=5)
139
140
  if not r.ok :
140
141
  self.logger.debug(f'fail message {r.text}')
141
142
  raise Exception(f'Failed to authenticate WSNOC. Return status code : {r.status_code}')
@@ -200,6 +201,7 @@ class WSNOCLIB :
200
201
  if self.kafka.enable_auto_refresh :
201
202
  self.renewSubscription()
202
203
  self.kafka.refresh_inprogress = False
204
+ self.token_refresh_count += 1
203
205
  self.runAutoRefreshThread()
204
206
 
205
207
  def kafka_connect( self ,
@@ -704,17 +706,18 @@ class WSNOCSession(requests.Session):
704
706
  self._wsnoc = wsnoc
705
707
  self.verify = False
706
708
  self.retries = 0
709
+ self.debug_this_request = False
707
710
  self.skip_hold_for_token_refresh = False
708
711
 
709
712
  def rebuild_auth(self, prepared_request, response):
710
713
  return
711
714
 
712
- def hold_for_token_refresh(self, url) :
715
+ def hold_for_token_refresh(self, url=None) :
713
716
  while self._wsnoc.refresh_inprogress :
714
- self._wsnoc.logger.debug(f'Waiting for token refresh. {url}')
715
- sleep(.1)
717
+ self._wsnoc.logger.info(f'Waiting for token refresh. {url if url else "No URL"}')
718
+ sleep(.5)
716
719
 
717
- def request(self, method, url , retries=0 , skip_hold_for_token_refresh=False , **kwargs):
720
+ def request(self, method, url , retries=0 , skip_hold_for_token_refresh=False , debug_this_request=False , **kwargs):
718
721
  self._wsnoc.logger.debug(f'[{method}] : {url}')
719
722
  if not skip_hold_for_token_refresh :
720
723
  self.hold_for_token_refresh(url)
@@ -726,12 +729,21 @@ class WSNOCSession(requests.Session):
726
729
  request_headers['Authorization'] = token
727
730
  kwargs['headers'] = request_headers
728
731
  request = super().request(method, url, **kwargs )
732
+ if debug_this_request :
733
+ self._wsnoc.logger.info(f'''
734
+ [DEBUG] [{method}] : {url}
735
+ [DEBUG] Headers: {request_headers}
736
+ [DEBUG] Body: {kwargs.get('data' , {})}
737
+ [DEBUG] Response: {request.text}
738
+ [DEBUG] OK: {request.ok}
739
+ [DEBUG] Method: {request.request.method}
740
+ ''')
729
741
  for i in range(retries) :
730
742
  if request.ok :
731
743
  break
732
744
  if not request.ok :
733
745
  sleep(1)
734
- self.hold_for_token_refresh()
746
+ self.hold_for_token_refresh(url)
735
747
  request = super().request(method, url, **kwargs )
736
748
  self._wsnoc.logger.debug(f'[Try-{i}] [{method}] : {url}- {request.status_code}')
737
749
  self._wsnoc.logger.info(f'[{method}] : {url} - [{request.status_code}]')
@@ -1,21 +1,23 @@
1
- Metadata-Version: 2.1
2
- Name: easy-utils-dev
3
- Version: 2.139
1
+ Metadata-Version: 2.4
2
+ Name: easy_utils_dev
3
+ Version: 2.141
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
7
7
  Requires-Dist: ping3
8
8
  Requires-Dist: flask
9
- Requires-Dist: flask-cors
9
+ Requires-Dist: flask_cors
10
10
  Requires-Dist: xmltodict
11
11
  Requires-Dist: paramiko
12
12
  Requires-Dist: oracledb
13
13
  Requires-Dist: requests
14
- Requires-Dist: flask-socketio
14
+ Requires-Dist: flask_socketio
15
15
  Requires-Dist: python-dotenv
16
16
  Requires-Dist: gevent
17
17
  Requires-Dist: pyzipper
18
18
  Requires-Dist: pyjwt
19
19
  Requires-Dist: authlib
20
20
  Requires-Dist: kafka-python
21
-
21
+ Dynamic: classifier
22
+ Dynamic: keywords
23
+ Dynamic: requires-dist
@@ -8,7 +8,8 @@ easy_utils_dev/brevosmtp.py,sha256=A5n13MnVQnDuSjYQ91-MLftmqfg3VQ-36Zqw9OtoTB4,3
8
8
  easy_utils_dev/check_license.py,sha256=C8vKXwaduoF3FSuDJ-J_j5jStNNyUdS-mOdLNfsCjmc,4825
9
9
  easy_utils_dev/cplib.py,sha256=TPCFNQh4jYSGsm_CrMdivmD3G5B1rprco2O4B0BSnzo,19570
10
10
  easy_utils_dev/custom_env.py,sha256=vxrjikpSNJlKfoBE-ef88UExlpXucUe-HcwHMn3gfB0,1510
11
- easy_utils_dev/debugger.py,sha256=Oz4WcT59FbHouTpprslocljDvevFdmP9hc_zNRVmSdE,18318
11
+ easy_utils_dev/debugger-C-PF4PAMMP.py,sha256=5GmZU1l9R3CSZVIGff-l-gYaf-imJGSGTsh-cxNz6og,18637
12
+ easy_utils_dev/debugger.py,sha256=5GmZU1l9R3CSZVIGff-l-gYaf-imJGSGTsh-cxNz6og,18637
12
13
  easy_utils_dev/easy_oracle.py,sha256=Jyc3HSl6eyLayjS8NoE4GOaf8otQlonR5_qOg2h1DjE,2157
13
14
  easy_utils_dev/encryptor.py,sha256=f5Zjn0DGtXCyhldpVnBtfcTb4h4Wp0eQPHusEYwIags,1512
14
15
  easy_utils_dev/ept.py,sha256=X-Z0_XCNfKK1TxQZquPMBqo3ZuxnK4TvWAYPnBUMlPI,25363
@@ -23,15 +24,15 @@ easy_utils_dev/openid_server.py,sha256=_odeg6omuizSUEJLtbAVn2PnG9vkcUAQ7rU3K5yXk
23
24
  easy_utils_dev/optics_utils.py,sha256=G-hFX2iiUCSJjk7BICBRGvVoDq0IBONLZSjagoB5FMg,964
24
25
  easy_utils_dev/require_auth.py,sha256=UsYAxfLX5wda6hd0nfLR_tl0bGQ4DYIpaTowsYSku-E,881
25
26
  easy_utils_dev/simple_sqlite.py,sha256=J-mcTUnHmAn0eCPD8j-WEoA19uzHRXJ4YRJsyx9B-do,13113
26
- easy_utils_dev/temp_memory.py,sha256=gfC-izYw8Sg1DD5iOdL8R5-fdB5RK0qkXZie9WmFjPA,1431
27
- easy_utils_dev/uiserver.py,sha256=d9jImwgSURMH4MIU4Zkro2b20cAbuV8rO_If42FVyZ4,6826
27
+ easy_utils_dev/temp_memory.py,sha256=0Dx_vNUSFQRMtJZNT8tUZXubcG7T6jxevw2WYuGmVe8,1658
28
+ easy_utils_dev/uiserver.py,sha256=zR-3DCo2FUF6Z7vpnzrKuWUl1MC0TparS8_LEDOyuuc,8886
28
29
  easy_utils_dev/utils.py,sha256=BmVnbxc336c6WTeDFcEHN6Mavt7fJrIEyK4GXODV3gI,13345
29
30
  easy_utils_dev/winserviceapi.py,sha256=2ZP6jaSt1-5vEJYXqwBhwX-1-eQ3V3YzntsoOoko2cw,18804
30
- easy_utils_dev/wsnoclib.py,sha256=25sjxutCAOHHGaK7-t1owNq9fV29BHFK1Dox08SZdFg,32876
31
+ easy_utils_dev/wsnoclib.py,sha256=kqkOkiafXnrIgdlV__3uyrAoqxG5dmIHnwqm0EkwS2U,33457
31
32
  easy_utils_dev/wsselib.py,sha256=YweScnoAAH_t29EeIjBpkQ6HtX0Rp9mQudRsRce2SE8,7920
32
33
  easy_utils_dev/ept_sql/create_dirs.sql,sha256=KWfX-Nc6lvr_BC-P6O97NE0idoPW4GNKUKUCgonJhto,3508
33
34
  easy_utils_dev/ept_sql/create_ept_tables.sql,sha256=WDHyIyeReV8_QaYBPIpSy-lto3OKvZtex1tWs-FPURQ,67737
34
- easy_utils_dev-2.139.dist-info/METADATA,sha256=-C0ualpFQyudh54KYQSws8PVDWoPXSme1Bd8FZNA3-E,510
35
- easy_utils_dev-2.139.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
36
- easy_utils_dev-2.139.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
37
- easy_utils_dev-2.139.dist-info/RECORD,,
35
+ easy_utils_dev-2.141.dist-info/METADATA,sha256=dvjJ7N765oLyHIafIY7Q6mh53jjxqeJlLS7vjiSEUxg,572
36
+ easy_utils_dev-2.141.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ easy_utils_dev-2.141.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
38
+ easy_utils_dev-2.141.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5