easy-utils-dev 2.138__py3-none-any.whl → 2.140__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.
easy_utils_dev/EasySsh.py CHANGED
@@ -69,7 +69,7 @@ class CREATESSH :
69
69
  ch.disconnect = disconnect
70
70
  return ch
71
71
 
72
- def ssh_execute(self ,command , merge_output=False , hide_output=False) :
72
+ def ssh_execute(self ,command , merge_output=False , hide_output=True) :
73
73
  self.logger.info(f"executing {command}")
74
74
  try :
75
75
  stdin_ , stdout_ , stderr_ = self.ssh.exec_command(command)
@@ -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] = {
@@ -35,6 +35,7 @@ class KafkaConfig :
35
35
  self.kafka_subscription_deleted = False
36
36
  self.base_url = None
37
37
  self.kafka_thread=None
38
+ self.enable_auto_refresh = False
38
39
 
39
40
 
40
41
 
@@ -69,7 +70,8 @@ class WSNOCLIB :
69
70
  self.onGoingRequests=0
70
71
  self.fastQueue = FastQueue(request_max_count)
71
72
  self.queue = []
72
- self.tokenRefreshPeriod = 2700
73
+ self.token = {}
74
+ self.tokenRefreshPeriod = None
73
75
  self.final_results = []
74
76
  self.killed=False
75
77
  self.nes=[]
@@ -78,6 +80,7 @@ class WSNOCLIB :
78
80
  self.refresh_inprogress = False
79
81
  self.kafka = kafka
80
82
  self.refresh_thread = None
83
+ self.token_refresh_count = 0
81
84
  self.session = WSNOCSession(self)
82
85
  if register_atexit :
83
86
  atexit.register(self.goodbye)
@@ -124,33 +127,23 @@ class WSNOCLIB :
124
127
  return
125
128
  if not pingAddress(self.address) :
126
129
  raise Exception(f'Address {self.address} is not pingable.')
127
- self.logger.info(f'Connecting to {self.address} using username: {self.username} BAuth={BAuth(self.username, self.password)}')
130
+ # self.logger.info(f'Connecting to {self.address} using username: {self.username}')
128
131
  self.logger.debug(f'Connecting to {self.address} using username: {self.username}')
129
132
  URL = f"https://{self.address}/rest-gateway/rest/api/v1/auth/token"
130
- self.logger.debug(f'Login URL is {URL}')
131
133
  headers = {
132
134
  "Content-Type": "application/json"
133
135
  }
134
136
  data = {
135
137
  "grant_type": "client_credentials"
136
138
  }
137
- r = requests.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), verify=False, json=data)
138
- self.logger.debug(f"""
139
- response_header={r.headers}
140
- response_encoding={r.encoding}
141
- response_reason={r.reason}
142
- body={r.request.body}
143
- headers={r.request.headers}
144
- URL={URL}
145
- content={r.text}
146
- """)
147
- self.logger.info(f'Request return status code : {r.status_code}')
148
- self.logger.debug(f"Login Request response body is {r.text}")
149
- if r.status_code != 200 :
139
+ r = self.session.post(url = URL , headers=headers , auth=BAuth(self.username, self.password), json=data , retries=5)
140
+ if not r.ok :
150
141
  self.logger.debug(f'fail message {r.text}')
151
142
  raise Exception(f'Failed to authenticate WSNOC. Return status code : {r.status_code}')
152
143
  self.access_token = r.json()["access_token"]
153
144
  self.refresh_token = r.json()["refresh_token"]
145
+ if not self.tokenRefreshPeriod :
146
+ self.tokenRefreshPeriod = int(r.json()["expires_in"]) - 100
154
147
  self.bearer_token = f'Bearer {self.access_token}'
155
148
  self.token = r.json()
156
149
  self.token.update({'bearer_token' : self.bearer_token })
@@ -202,12 +195,14 @@ class WSNOCLIB :
202
195
  sleep(self.tokenRefreshPeriod)
203
196
  self.logger.info(f"Waiting period completed. Starting Revoking/Login process ...")
204
197
  self.refresh_inprogress = True
205
- self.logout(logout=False)
206
- sleep(30)
207
- self.logger.info(f"Logout process completed. Starting Reconnecting ...")
208
- self.connect(self.auto_refresh_token)
198
+ self.renew_by_refresh_token()
209
199
  self.refresh_inprogress = False
210
- self.event.dispatchEvent('onTokenRefresh', self.token)
200
+ self.kafka.refresh_inprogress = True
201
+ if self.kafka.enable_auto_refresh :
202
+ self.renewSubscription()
203
+ self.kafka.refresh_inprogress = False
204
+ self.token_refresh_count += 1
205
+ self.runAutoRefreshThread()
211
206
 
212
207
  def kafka_connect( self ,
213
208
  user ,
@@ -253,7 +248,6 @@ class WSNOCLIB :
253
248
  self.logger.debug(f"nsp_address: {nsp_address}")
254
249
  self.logger.debug(f"NewRelease: {new_release}")
255
250
  self.kafka.kafka_subscription_deleted= False
256
-
257
251
  if not self.external_nsp :
258
252
  if new_release :
259
253
  self.kafka.kafka_nsp_os_name = 'nspos-kafka'
@@ -265,8 +259,6 @@ class WSNOCLIB :
265
259
  self.kafka.kafka_address = nsp_address
266
260
  self.kafka.kafka_port = 9192
267
261
  self.kafka.kafka_nsp_os_name = 'nspos-tomcat'
268
-
269
- self.logger.info('requesting kafka subscription ...')
270
262
  if self.loggedOut or self.killed:
271
263
  self.logger.error(f"WSNOC API Authentication process loggedout or killed. exit")
272
264
  raise Exception('WSNOC API Authentication process loggedout or killed. exit')
@@ -283,21 +275,38 @@ class WSNOCLIB :
283
275
  self.nsp_key = f'{self.temp_dir}/nsp.key'
284
276
  self.nsp_cert = f'{self.temp_dir}/nsp.pem'
285
277
  if not self.external_nsp :
278
+ ####################
279
+ ####################
280
+ #
281
+ # IN CASE OF INTERNAL NSP
282
+ #
283
+ ####################
284
+ ####################
285
+ checkContainer = self.ssh.ssh_execute(f"docker ps | grep -i 'nspos-kafka' | wc -l")
286
+ if checkContainer != '0' :
287
+ self.kafka.kafka_nsp_os_name = 'nspos-kafka'
288
+ self.kafka.kafka_port = None
286
289
  self.logger.debug(f"Working on internal NSP to copy the files ...")
287
290
  self.ssh.ssh_execute(f"docker cp {self.kafka.kafka_nsp_os_name}:/opt/nsp/os/ssl/certs/nsp/nsp.pem /tmp/nsp.pem")
288
291
  self.ssh.ssh_execute(f"docker cp {self.kafka.kafka_nsp_os_name}:/opt/nsp/os/ssl/nsp.key /tmp/nsp.key")
289
292
  sftp.get('/tmp/nsp.pem' , f'{self.temp_dir}/nsp.pem')
290
293
  sftp.get('/tmp/nsp.key' , f'{self.temp_dir}/nsp.key')
291
294
  self.kafka.ca_cert = f'{self.temp_dir}/nsp.pem'
292
- baseUrl = self.kafka.base_url = None
293
- port = self.kafka.kafka_api_port
295
+ self.kafka.base_url = f'https://{self.address}'
294
296
  else :
297
+ ####################
298
+ ####################
299
+ #
300
+ # IN CASE OF EXTERNAL NSP
301
+ #
302
+ ####################
303
+ ####################
295
304
  self.logger.debug(f"Working on external NSP to copy the files ...")
296
305
  CertLoc = f"""find /var/lib/kubelet/pods/ -type d -path "*/volumes/kubernetes.io~empty-dir/shared-tls-volume" | head -n1"""
297
306
  CertLoc = self.ssh.ssh_execute(CertLoc).replace('\n','')
298
307
  self.logger.debug(f"CertLoc Host: {CertLoc}")
299
- baseUrl = self.kafka.base_url = f'https://{nsp_address}'
300
- port = self.kafka.kafka_port = None
308
+ self.kafka.base_url = f'https://{nsp_address}'
309
+ self.kafka.kafka_port = None
301
310
  if len(CertLoc) > 15 :
302
311
  self.logger.debug(f"Copying cert files from nsp host machine ....")
303
312
  copies = [
@@ -334,7 +343,6 @@ class WSNOCLIB :
334
343
  self.logger.debug(f'affectedObjectType Filter : filter updated with "{filter}"')
335
344
  if custom_filter_expression :
336
345
  filter += f" {custom_filter_expression}"
337
- URL = '/nbi-notification/api/v1/notifications/subscriptions'
338
346
  kafkaForm = {
339
347
  "categories": [
340
348
  {
@@ -344,19 +352,18 @@ class WSNOCLIB :
344
352
  ]
345
353
  }
346
354
  self.logger.debug(f"Kafka Filter Form : {kafkaForm}")
347
- response = self.post(
348
- url=URL,
349
- baseUrl=baseUrl,
350
- return_json=True,
351
- port=port,
352
- body=json.dumps(kafkaForm)
353
- )
354
- if response :
355
+ if self.kafka.kafka_port is not None :
356
+ URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions"
357
+ else :
358
+ URL = f"{self.kafka.base_url}/nbi-notification/api/v1/notifications/subscriptions"
359
+ response = self.session.post(URL , json=kafkaForm , retries=3)
360
+ if response.ok :
361
+ response = response.json()
355
362
  self.kafka.subscriptionId = response['response']['data']['subscriptionId']
356
363
  self.kafka.response = response
357
364
  self.kafka.topicId = response['response']['data']['topicId']
358
365
  if auto_refresh :
359
- self.kafka.kafka_thread = start_thread(target=self.renewSubscription)
366
+ self.kafka.enable_auto_refresh = True
360
367
  self.killed=False
361
368
  else :
362
369
  self.logger.error(f"Failed to create kafka subscription.")
@@ -365,81 +372,84 @@ class WSNOCLIB :
365
372
  return self.kafka
366
373
 
367
374
  def change_kafka_refresh_period(self , period : int =3000) :
368
- self.kafka.kafka_refresh_period = period
375
+ print('Deprecated function change_kafka_refresh_period. Kafka refresh period is now managed by WSNOC API SLEEP PERIOD')
369
376
 
370
377
  def renewSubscription(self) :
371
- while True :
372
- try :
373
- sleep(self.kafka.kafka_refresh_period)
374
- if self.loggedOut or self.killed:
375
- break
376
- self.logger.info('Renewing subscription ...')
377
- self.kafka.refresh_inprogress = True
378
- URL = f"/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
379
- response = self.post(
380
- url=URL,
381
- baseUrl=self.kafka.base_url,
382
- port=self.kafka.kafka_port,
383
- )
384
- self.logger.info(f'Renewing subscription [{response.status_code}]')
385
- self.logger.info(f'Renewing subscription Response: [{response.text}]')
386
- except Exception as error :
387
- self.logger.error(f'failed to renew subscription. {error}')
388
- self.logger.debug(traceback.format_exc())
389
- self.kafka.refresh_inprogress = False
390
-
391
- def deleteKafkaSubscription(self , subscriptionId=None) :
392
- self.logger.info(f'Deleting subscription subscriptionId:{subscriptionId}')
393
- if not subscriptionId :
394
- self.logger.info(f'Deleting subscription subscriptionId:{self.kafka.subscriptionId}')
395
- subscriptionId=self.kafka.subscriptionId
396
- URL = f"/nbi-notification/api/v1/notifications/subscriptions/{subscriptionId}"
397
- self.kafka.kafka_subscription_deleted= True
398
- self.delete(
399
- url=URL,
400
- return_json=True,
401
- port=self.kafka.kafka_api_port,
402
- )
403
- self.killed=True
404
- return True
378
+ self.logger.info('Renewing subscription ...')
379
+ if self.kafka.kafka_port is not None :
380
+ URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
381
+ else :
382
+ URL = f"{self.kafka.base_url}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
383
+ response = self.session.post(URL , retries=3)
384
+ if not response.ok :
385
+ self.logger.error(f'failed to renew subscription. {response.text}')
386
+
387
+ # def renewSubscription(self) :
388
+ # while True :
389
+ # try :
390
+ # sleep(self.kafka.kafka_refresh_period)
391
+ # if self.loggedOut or self.killed:
392
+ # break
393
+ # self.logger.info('Renewing subscription ...')
394
+ # self.kafka.refresh_inprogress = True
395
+ # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
396
+ # response = self.session.post(URL , retries=3)
397
+ # self.logger.debug(f'Renewing subscription Response: [{response.text}]')
398
+ # except Exception as error :
399
+ # self.logger.error(f'failed to renew subscription. {error}')
400
+ # self.logger.debug(traceback.format_exc())
401
+ # self.kafka.refresh_inprogress = False
402
+
403
+ # def deleteKafkaSubscription(self , subscriptionId=None) :
404
+ # self.logger.info(f'Deleting subscription subscriptionId:{subscriptionId}')
405
+ # if not subscriptionId :
406
+ # self.logger.info(f'Deleting subscription subscriptionId:{self.kafka.subscriptionId}')
407
+ # subscriptionId=self.kafka.subscriptionId
408
+ # self.kafka.kafka_subscription_deleted= True
409
+ # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{subscriptionId}"
410
+ # response = self.session.delete(URL , retries=3)
411
+ # return response
405
412
 
406
- def handle_beautify_alarm(self , alarm ) :
407
- oalarm = alarm
408
- alarm = alarm['data']['ietf-restconf:notification']
409
- if 'create' in str(list(alarm.keys())) :
410
- alarmData = alarm['nsp-fault:alarm-create']
411
- oalarm['dataEnh'] = {
412
- 'newAlarm' : True,
413
- 'alarmChange' : False,
414
- 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
415
- 'neId' : alarmData['neId'],
416
- 'neName ' : alarmData['neName'],
417
- 'alarmName' : alarmData['alarmName'],
418
- 'cleared' : False,
419
- 'aknowledged' : False,
420
- **alarmData ,
421
- }
422
- elif 'change' in str(list(alarm.keys())) :
423
- alarmData = alarm['nsp-fault:alarm-change']
424
- cleared = False
425
- aknowledged = False
426
- if 'severity' in list(alarmData.keys()) :
427
- if alarmData['severity']['new-value'] == 'cleared' :
428
- cleared = True
429
- if 'acknowledged' in list(alarmData.keys()) :
430
- aknowledged = alarmData['acknowledged']['new-value']
431
- oalarm['dataEnh'] = {
432
- 'newAlarm' : False,
433
- 'alarmChange' : True,
434
- 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
435
- 'cleared' : cleared,
436
- 'aknowledged' : aknowledged,
437
- **alarmData ,
438
- }
439
- return oalarm
440
-
441
-
442
- def kafka_listen(self) :
413
+ # def handle_beautify_alarm(self , alarm ) :
414
+ # oalarm = alarm
415
+ # alarm = alarm['data']['ietf-restconf:notification']
416
+ # if 'create' in str(list(alarm.keys())) :
417
+ # alarmData = alarm['nsp-fault:alarm-create']
418
+ # oalarm['dataEnh'] = {
419
+ # 'newAlarm' : True,
420
+ # 'alarmChange' : False,
421
+ # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
422
+ # 'neId' : alarmData['neId'],
423
+ # 'neName ' : alarmData['neName'],
424
+ # 'alarmName' : alarmData['alarmName'],
425
+ # 'cleared' : False,
426
+ # 'aknowledged' : False,
427
+ # **alarmData ,
428
+ # }
429
+ # elif 'change' in str(list(alarm.keys())) :
430
+ # alarmData = alarm['nsp-fault:alarm-change']
431
+ # cleared = False
432
+ # aknowledged = False
433
+ # if 'severity' in list(alarmData.keys()) :
434
+ # if alarmData['severity']['new-value'] == 'cleared' :
435
+ # cleared = True
436
+ # if 'acknowledged' in list(alarmData.keys()) :
437
+ # aknowledged = alarmData['acknowledged']['new-value']
438
+ # oalarm['dataEnh'] = {
439
+ # 'newAlarm' : False,
440
+ # 'alarmChange' : True,
441
+ # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
442
+ # 'cleared' : cleared,
443
+ # 'aknowledged' : aknowledged,
444
+ # **alarmData ,
445
+ # }
446
+ # return oalarm
447
+
448
+
449
+ def kafka_listen(self) :
450
+ def hold_if_kafka_refresh_inprogress() :
451
+ while self.kafka.refresh_inprogress :
452
+ sleep(.1)
443
453
  self.logger.info('Listening to Kafka Notifications ...')
444
454
  if not self.kafka.topicId :
445
455
  self.logger.error(f'kafka is not established. exit.')
@@ -464,9 +474,7 @@ class WSNOCLIB :
464
474
  )
465
475
  try:
466
476
  while True:
467
- if self.kafka.refresh_inprogress :
468
- self.logger.info(f"Waiting for kafka subscription to be ready. In refresh process...")
469
- sleep(1)
477
+ hold_if_kafka_refresh_inprogress()
470
478
  if self.kafka.kafka_subscription_deleted :
471
479
  self.logger.info(f"Kafka subscription is deleted. exit.")
472
480
  break
@@ -583,6 +591,27 @@ class WSNOCLIB :
583
591
  return r.json()
584
592
  return r
585
593
 
594
+ def renew_by_refresh_token(self) :
595
+ URL = f"https://{self.address}/rest-gateway/rest/api/v1/auth/token"
596
+ headers = {
597
+ "Content-Type": "application/json"
598
+ }
599
+ data = {
600
+ "grant_type": "refresh_token",
601
+ "refresh_token": f"{self.refresh_token}"
602
+
603
+ }
604
+ r = self.session.post(URL , headers=headers , json=data , auth=BAuth(self.username, self.password) , skip_hold_for_token_refresh=True)
605
+ if r.ok :
606
+ if not self.tokenRefreshPeriod :
607
+ self.tokenRefreshPeriod = int(r.json()["expires_in"]) - 100
608
+ self.access_token = r.json()["access_token"]
609
+ self.refresh_token = r.json()["refresh_token"]
610
+ self.bearer_token = f'Bearer {self.access_token}'
611
+ self.token = r.json()
612
+ self.token.update({'bearer_token' : self.bearer_token })
613
+ return r
614
+
586
615
  def session_info(self) :
587
616
  self.logger.debug('Getting Version ...')
588
617
  response = self.get( url='/oms1350/data/common/sessionInfo')
@@ -676,33 +705,48 @@ class WSNOCSession(requests.Session):
676
705
  self.headers.update({"Content-Type": "application/json"}) # base defaults
677
706
  self._wsnoc = wsnoc
678
707
  self.verify = False
708
+ self.retries = 0
709
+ self.debug_this_request = False
710
+ self.skip_hold_for_token_refresh = False
679
711
 
680
712
  def rebuild_auth(self, prepared_request, response):
681
713
  return
682
714
 
683
- def hold_for_token_refresh(self) :
715
+ def hold_for_token_refresh(self, url=None) :
684
716
  while self._wsnoc.refresh_inprogress :
685
- sleep(.1)
717
+ self._wsnoc.logger.info(f'Waiting for token refresh. {url if url else "No URL"}')
718
+ sleep(.5)
686
719
 
687
- def request(self, method, url , retries=0 , **kwargs):
720
+ def request(self, method, url , retries=0 , skip_hold_for_token_refresh=False , debug_this_request=False , **kwargs):
688
721
  self._wsnoc.logger.debug(f'[{method}] : {url}')
689
- self.hold_for_token_refresh()
722
+ if not skip_hold_for_token_refresh :
723
+ self.hold_for_token_refresh(url)
690
724
  self._wsnoc.api_count += 1
691
725
  token = self._wsnoc.getLatestToken().get('bearer_token')
692
726
  request_headers = kwargs.get('headers' , {})
693
- if not request_headers.get('Authorization') :
694
- request_headers['Authorization'] = token
727
+ if token :
728
+ if not request_headers.get('Authorization') :
729
+ request_headers['Authorization'] = token
695
730
  kwargs['headers'] = request_headers
696
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
+ ''')
697
741
  for i in range(retries) :
698
742
  if request.ok :
699
743
  break
700
744
  if not request.ok :
701
745
  sleep(1)
702
- self.hold_for_token_refresh()
746
+ self.hold_for_token_refresh(url)
703
747
  request = super().request(method, url, **kwargs )
704
748
  self._wsnoc.logger.debug(f'[Try-{i}] [{method}] : {url}- {request.status_code}')
705
- self._wsnoc.logger.info(f'[{method}] : {url}- {request.status_code}')
749
+ self._wsnoc.logger.info(f'[{method}] : {url} - [{request.status_code}]')
706
750
  return request
707
751
 
708
752
 
@@ -1,21 +1,23 @@
1
- Metadata-Version: 2.1
2
- Name: easy-utils-dev
3
- Version: 2.138
1
+ Metadata-Version: 2.4
2
+ Name: easy_utils_dev
3
+ Version: 2.140
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
@@ -1,4 +1,4 @@
1
- easy_utils_dev/EasySsh.py,sha256=PbFK1vSBcnt_SfkZXZGh5wg6Sg9R2WlXeGdvjZluJ9E,3592
1
+ easy_utils_dev/EasySsh.py,sha256=9pQaHHQGng8X3fsy4Vvto5RIEObm-iOt52VLiO6m1g0,3591
2
2
  easy_utils_dev/Events.py,sha256=MdI53gAyXX_2jmChpayayQM0ZitgjtkyUNrQYGEEZnw,2978
3
3
  easy_utils_dev/FastQueue.py,sha256=Drt8B_hEdmg9eAt7OWSgTyoJ3rUHkeJHk9xdaehtEsY,5622
4
4
  easy_utils_dev/NameObject.py,sha256=Z4Qp3qfMcQeMPw35PV_xOu8kigRtfSRZ4h7woR0t3Gg,270
@@ -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/temp_memory.py,sha256=0Dx_vNUSFQRMtJZNT8tUZXubcG7T6jxevw2WYuGmVe8,1658
27
28
  easy_utils_dev/uiserver.py,sha256=d9jImwgSURMH4MIU4Zkro2b20cAbuV8rO_If42FVyZ4,6826
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=z18kdPJJeNYAaomqH1t6KfTWm77BNPx6aPAW9P0PSOc,30813
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.138.dist-info/METADATA,sha256=uN_YPuZajKHryq24B8n7zfC3qVYpw9oXaDabo_Vfo1E,510
35
- easy_utils_dev-2.138.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
36
- easy_utils_dev-2.138.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
37
- easy_utils_dev-2.138.dist-info/RECORD,,
35
+ easy_utils_dev-2.140.dist-info/METADATA,sha256=11wRCPmmDl1s7jptVfwy-_ru_jnkTn2nJMcJ-dM07uM,572
36
+ easy_utils_dev-2.140.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ easy_utils_dev-2.140.dist-info/top_level.txt,sha256=7vBsrpq7NmilkdU3YUvfd5iVDNBaT07u_-ut4F7zc7A,15
38
+ easy_utils_dev-2.140.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