easy-utils-dev 2.142__tar.gz → 2.144__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.142 → easy_utils_dev-2.144}/PKG-INFO +3 -1
  2. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/uiserver.py +149 -4
  3. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/utils.py +15 -1
  4. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/wsnoclib.py +39 -68
  5. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev.egg-info/PKG-INFO +3 -1
  6. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev.egg-info/requires.txt +2 -0
  7. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/setup.py +3 -1
  8. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/MANIFEST.in +0 -0
  9. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/EasySsh.py +0 -0
  10. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/Events.py +0 -0
  11. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/FastQueue.py +0 -0
  12. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/NameObject.py +0 -0
  13. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/__init__.py +0 -0
  14. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/abortable.py +0 -0
  15. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/brevosmtp.py +0 -0
  16. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/check_license.py +0 -0
  17. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/cplib.py +0 -0
  18. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/custom_env.py +0 -0
  19. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/debugger-C-PF4PAMMP.py +0 -0
  20. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/debugger.py +0 -0
  21. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/easy_oracle.py +0 -0
  22. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/encryptor.py +0 -0
  23. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/ept.py +0 -0
  24. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  25. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  26. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/exceptions.py +0 -0
  27. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/filescompressor.py +0 -0
  28. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/generate_license.py +0 -0
  29. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/keycloakapi.py +0 -0
  30. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/lralib.py +0 -0
  31. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/ne1830PSS.py +0 -0
  32. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/nsp_kafka.py +0 -0
  33. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/openid_server.py +0 -0
  34. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/optics_utils.py +0 -0
  35. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/require_auth.py +0 -0
  36. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/simple_sqlite.py +0 -0
  37. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/temp_memory.py +0 -0
  38. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/winserviceapi.py +0 -0
  39. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev/wsselib.py +0 -0
  40. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev.egg-info/SOURCES.txt +0 -0
  41. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  42. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/easy_utils_dev.egg-info/top_level.txt +0 -0
  43. {easy_utils_dev-2.142 → easy_utils_dev-2.144}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.142
3
+ Version: 2.144
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -16,6 +16,8 @@ Requires-Dist: flask_socketio
16
16
  Requires-Dist: python-dotenv
17
17
  Requires-Dist: gevent
18
18
  Requires-Dist: pyzipper
19
+ Requires-Dist: shutil
20
+ Requires-Dist: bs4
19
21
  Requires-Dist: pyjwt
20
22
  Requires-Dist: authlib
21
23
  Requires-Dist: kafka-python
@@ -1,19 +1,21 @@
1
1
  import copy
2
2
  import gc
3
3
  import time
4
+ from tkinter import NO
4
5
  from werkzeug.serving import ThreadedWSGIServer
5
6
  from easy_utils_dev.utils import getRandomKey , generateToken , getTimestamp
6
7
  from flask_socketio import SocketIO
7
8
  from engineio.async_drivers import gevent
8
9
  from flask_cors import CORS
9
10
  import logging , os
10
- from flask import jsonify, request , current_app
11
+ from flask import jsonify, request , current_app , copy_current_request_context
11
12
  from flask import Flask
12
13
  from threading import Thread
13
14
  import threading
14
15
  from easy_utils_dev.custom_env import cenv
15
16
  from easy_utils_dev.utils import kill_thread
16
17
  from multiprocessing import Process
18
+ import traceback
17
19
  from werkzeug.serving import make_ssl_devcert
18
20
  from time import sleep
19
21
  from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs
@@ -53,20 +55,48 @@ class AbortRequest :
53
55
  self.thread = None
54
56
  self.cache = None
55
57
  self.start_ts = getTimestamp()
58
+ self.result = None
59
+ self.async_request = False
60
+ self.internalid = None
61
+ self.in_progress = False
62
+ self.start_ts = None
63
+ self.end_ts = None
64
+ self.execution = None
65
+ self.success = None
66
+ self.traceback = None
67
+ self.delete_async_request_ts = None
68
+ self.killed = False
69
+
56
70
 
57
71
  def abort(self) :
58
72
  kill_thread(self.thread)
59
- self.cache.delete(self.abort_id)
73
+ self.killed = True
74
+ self.in_progress = False
75
+ self.success = False
76
+ self.traceback = None
77
+ self.result = None
78
+ self.delete_async_request_ts = getTimestamp(after_seconds=3600)
79
+ self.end_ts = getTimestamp()
80
+ self.execution = round(self.end_ts - self.start_ts, 2)
81
+ if not self.async_request :
82
+ self.cache.delete(self.abort_id)
60
83
  try :
61
84
  gc.collect()
62
85
  except :
63
86
  pass
64
87
 
88
+ class SocketClientObject :
89
+ def __init__(self ) :
90
+ self.client : SocketIO
91
+ self.internalid : str = None
92
+ self.request = {}
93
+ self.rooms = None
65
94
 
66
95
  class UISERVER :
67
96
  def __init__(self ,
68
97
  id=getRandomKey(n=15),
69
98
  secretkey=generateToken(),
99
+ serve_with_secret_key=False,
70
100
  address='localhost',
71
101
  port=5312 ,
72
102
  https=False ,
@@ -86,9 +116,14 @@ class UISERVER :
86
116
  self.thread = None
87
117
  self.ssl_crt=ssl_crt
88
118
  self.ssl_key=ssl_key
119
+ self.serve_with_secret_key=serve_with_secret_key
120
+ self.secretkey=secretkey
89
121
  self.enable_test_url=True
90
122
  self.abort_requests = {}
123
+ self.bg_requests = {}
124
+ self.socketio_clients = []
91
125
  self.abort_base_url = '/request/abort'
126
+ self.request_reply_base_url= '/request/result'
92
127
  if https :
93
128
  self.httpProtocol = 'https'
94
129
  else :
@@ -97,6 +132,9 @@ class UISERVER :
97
132
  cenv[id] = self
98
133
  self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
99
134
  self.cache = TemporaryMemory()
135
+ start_thread(target=self.delete_very_old_requests)
136
+ self.secret_key_execlude_urls = []
137
+ self.socketio_rooms = {}
100
138
 
101
139
  def update_cert(self , crt, ssl ) :
102
140
  self.ssl_crt=crt
@@ -120,6 +158,29 @@ class UISERVER :
120
158
  self.cache.set( Abort , custom_key=abort_id , auto_destroy_period=120 , store_deleted_key=False )
121
159
  return Abort
122
160
 
161
+ def delete_very_old_requests(self) :
162
+ while True :
163
+ now = getTimestamp()
164
+ for key, value in list(self.bg_requests.items()) :
165
+ value : AbortRequest = value
166
+ if value.delete_async_request_ts :
167
+ if value.delete_async_request_ts > now :
168
+ del self.bg_requests[key]
169
+ gc.collect()
170
+ sleep(320)
171
+
172
+ def create_room(self , room_id : str , members : list[str] ) :
173
+ self.socketio_rooms[room_id] = members
174
+
175
+ def add_member_to_room(self , room_id : str , member : str ) :
176
+ self.socketio_rooms[room_id].append(member)
177
+
178
+ def remove_member_from_room(self , room_id : str , member : str ) :
179
+ self.socketio_rooms[room_id].remove(member)
180
+
181
+ def get_room_members(self , room_id : str ) :
182
+ return self.socketio_rooms[room_id]
183
+
123
184
  def start_before_request(self) :
124
185
 
125
186
  @self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
@@ -137,10 +198,55 @@ class UISERVER :
137
198
  return { 'status' : 200 , 'message' : 'Request aborted' , 'abort_timestamp' : timestamp , 'abort_id' : id , 'alive' : alive , 'url' : abort.request.get('path')}
138
199
  else :
139
200
  return { 'status' : 404 , 'message' : 'Request not found or request is not abortable. Check request headers for abortable flag.'}
201
+
202
+ @self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
203
+ def get_result_of_async_request(id : str ) :
204
+ request : AbortRequest = self.bg_requests.get(id)
205
+ if request :
206
+ return {
207
+ 'status' : 200 ,
208
+ 'internalid' : id ,
209
+ 'result' : request.result ,
210
+ 'in_progress' : request.in_progress ,
211
+ 'async_request' : request.async_request ,
212
+ 'internalid' : request.internalid ,
213
+ 'start_ts' : request.start_ts ,
214
+ 'end_ts' : request.end_ts ,
215
+ 'execution' : request.execution,
216
+ 'success' : request.success,
217
+ 'killed' : request.killed
218
+ }
140
219
 
141
220
  @self.app.before_request
142
221
  def before_request() :
222
+ if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
223
+ return jsonify({"error": "Secret key is invalid"}), 401
224
+
225
+ @copy_current_request_context
226
+ def run_async_job_results( target_func , abort : AbortRequest ) :
227
+ abort.in_progress = True
228
+ abort.async_request = True
229
+ abort.internalid = request.internalid
230
+ abort.start_ts = getTimestamp()
231
+ try :
232
+ result = target_func(*request.args, **request.form)
233
+ abort.success = True
234
+ except Exception as e :
235
+ abort.success = False
236
+ abort.result = str(e)
237
+ abort.traceback = traceback.format_exc()
238
+ raise
239
+ abort.result = result
240
+ abort.end_ts = getTimestamp()
241
+ abort.execution = round(abort.end_ts - abort.start_ts, 2)
242
+ abort.in_progress = False
243
+ abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
244
+
245
+
143
246
  abortable = request.headers.get('abortable')
247
+ requestId = getRandomKeysAndStr(n=10)
248
+ request.start_ts = getTimestamp()
249
+ request.internalid = requestId
144
250
  if abortable :
145
251
  abort = self.register_abortable_request(request)
146
252
  request.abortable = True
@@ -154,13 +260,20 @@ class UISERVER :
154
260
  target_func = current_app.view_functions.get(request.endpoint)
155
261
  if not target_func:
156
262
  return jsonify({"error": "Route not found"}), 404
157
- th = start_thread(target=target_func, args=request.args, kwargs=request.form)
263
+ th = start_thread(target=run_async_job_results, args=[target_func , abort ])
158
264
  abort.thread = th
265
+ self.bg_requests[requestId] = abort
159
266
  return {"status": 200, "message": "Request now in running bg", "abort_id": abort.abort_id} , 200
267
+
160
268
 
161
269
  @self.app.after_request
162
270
  def after_request(response) :
163
271
  try :
272
+ now = getTimestamp()
273
+ response.headers['internalid'] = request.internalid
274
+ response.headers['start_ts'] = request.start_ts
275
+ response.headers['end_ts'] = now
276
+ response.headers['execution'] = round(now - request.start_ts, 2)
164
277
  if request.abortable :
165
278
  response.headers['abortid'] = request.abort_id
166
279
  response.headers['abortable'] = True
@@ -168,6 +281,35 @@ class UISERVER :
168
281
  response.headers['abortable'] = False
169
282
  return response
170
283
 
284
+ # socketio client connected.
285
+ @self.socketio.on('connect')
286
+ def handle_client_connect():
287
+ sid = request.sid
288
+ client = SocketClientObject()
289
+ client.internalid = getRandomKeysAndStr(n=20)
290
+ client.sid = sid
291
+ client.request = request
292
+ client.rooms = self.socketio_rooms
293
+ self.socketio_clients.append(client)
294
+ self.socketio.emit('/internal/connect', {
295
+ 'status': 200,
296
+ 'message': 'client connected' ,
297
+ 'internalid' : client.internalid,
298
+ 'sid' : sid,
299
+ },
300
+ to=sid
301
+ )
302
+ print(f'client connected : {sid} | Clients : {len(self.socketio_clients)}')
303
+
304
+ # socketio client connected.
305
+ @self.socketio.on('disconnect')
306
+ def handle_client_disconnect():
307
+ for i , c in enumerate(list(self.socketio_clients)) :
308
+ print(f'c.sid : {c.sid} | request.sid : {request.sid}')
309
+ if c.sid == request.sid :
310
+ del self.socketio_clients[i]
311
+ print(f'client disconnected : {request.sid} | Clients : {len(self.socketio_clients)}')
312
+ break
171
313
 
172
314
  def getInstance(self) :
173
315
  return self.getFlask() , self.getSocketio() , self.getWsgi()
@@ -188,7 +330,8 @@ class UISERVER :
188
330
 
189
331
  def _wait_th(self , t ) :
190
332
  t.join()
191
-
333
+
334
+
192
335
  def thrStartUi(self , suppress_prints=True) :
193
336
  if self.enable_test_url :
194
337
  if not suppress_prints :
@@ -196,6 +339,8 @@ class UISERVER :
196
339
  @self.app.route('/connection/test/internal' , methods=['GET'])
197
340
  def test_connection():
198
341
  return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
342
+
343
+
199
344
  if self.httpProtocol == 'http' :
200
345
  con = None
201
346
  elif self.httpProtocol == 'https' :
@@ -1,4 +1,4 @@
1
- import datetime , string , subprocess , psutil ,secrets , os ,ping3 , time , sys , argparse , ctypes , math , threading , random , jwt , socket
1
+ import datetime , string , subprocess , psutil , shutil ,secrets , os ,ping3 , time , sys , argparse , ctypes , math , threading , random , jwt , socket
2
2
 
3
3
 
4
4
  def getRandomKey(n=10, numbers=True) :
@@ -463,5 +463,19 @@ def getMachineAddresses() :
463
463
  ip_addresses = socket.gethostbyname_ex(socket.gethostname())[2]
464
464
  return ip_addresses
465
465
 
466
+
467
+ def get_free_space(path: str , in_mb = True) -> int:
468
+ """
469
+ Return available free space in bytes for the given path.
470
+ Works on Linux, macOS, and Windows.
471
+ """
472
+ # Expand ~ and handle drive letters
473
+ path = os.path.abspath(os.path.expanduser(path))
474
+ total, used, free = shutil.disk_usage(path)
475
+ if in_mb :
476
+ return convert_bytes_to_mb(free)
477
+ return free # in bytes
478
+
479
+
466
480
  if __name__ == "__main__":
467
481
  pass
@@ -1,7 +1,8 @@
1
+ from tkinter import NO
1
2
  from easy_utils_dev.debugger import DEBUGGER
2
3
  import requests , json , subprocess
3
4
  from requests.auth import HTTPBasicAuth as BAuth
4
- from .utils import pingAddress , fixTupleForSql , start_thread , mkdirs , getTimestamp
5
+ from .utils import get_free_space, pingAddress , fixTupleForSql , start_thread , mkdirs , getTimestamp
5
6
  from time import sleep
6
7
  from urllib3.exceptions import InsecureRequestWarning
7
8
  from urllib3 import disable_warnings
@@ -378,7 +379,7 @@ class WSNOCLIB :
378
379
  return self.kafka
379
380
 
380
381
  def change_kafka_refresh_period(self , period : int =3000) :
381
- print('Deprecated function change_kafka_refresh_period. Kafka refresh period is now managed by WSNOC API SLEEP PERIOD')
382
+ self.logger.warning('Deprecated, Kafka refresh period is now managed by WSNOC API SLEEP PERIOD. Nothing is applied now.')
382
383
 
383
384
  def renewSubscription(self) :
384
385
  self.logger.info('Renewing subscription ...')
@@ -390,67 +391,6 @@ class WSNOCLIB :
390
391
  if not response.ok :
391
392
  self.logger.error(f'failed to renew subscription. {response.text}')
392
393
 
393
- # def renewSubscription(self) :
394
- # while True :
395
- # try :
396
- # sleep(self.kafka.kafka_refresh_period)
397
- # if self.loggedOut or self.killed:
398
- # break
399
- # self.logger.info('Renewing subscription ...')
400
- # self.kafka.refresh_inprogress = True
401
- # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{self.kafka.subscriptionId}/renewals"
402
- # response = self.session.post(URL , retries=3)
403
- # self.logger.debug(f'Renewing subscription Response: [{response.text}]')
404
- # except Exception as error :
405
- # self.logger.error(f'failed to renew subscription. {error}')
406
- # self.logger.debug(traceback.format_exc())
407
- # self.kafka.refresh_inprogress = False
408
-
409
- # def deleteKafkaSubscription(self , subscriptionId=None) :
410
- # self.logger.info(f'Deleting subscription subscriptionId:{subscriptionId}')
411
- # if not subscriptionId :
412
- # self.logger.info(f'Deleting subscription subscriptionId:{self.kafka.subscriptionId}')
413
- # subscriptionId=self.kafka.subscriptionId
414
- # self.kafka.kafka_subscription_deleted= True
415
- # URL = f"{self.kafka.base_url}:{self.kafka.kafka_port}/nbi-notification/api/v1/notifications/subscriptions/{subscriptionId}"
416
- # response = self.session.delete(URL , retries=3)
417
- # return response
418
-
419
- # def handle_beautify_alarm(self , alarm ) :
420
- # oalarm = alarm
421
- # alarm = alarm['data']['ietf-restconf:notification']
422
- # if 'create' in str(list(alarm.keys())) :
423
- # alarmData = alarm['nsp-fault:alarm-create']
424
- # oalarm['dataEnh'] = {
425
- # 'newAlarm' : True,
426
- # 'alarmChange' : False,
427
- # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
428
- # 'neId' : alarmData['neId'],
429
- # 'neName ' : alarmData['neName'],
430
- # 'alarmName' : alarmData['alarmName'],
431
- # 'cleared' : False,
432
- # 'aknowledged' : False,
433
- # **alarmData ,
434
- # }
435
- # elif 'change' in str(list(alarm.keys())) :
436
- # alarmData = alarm['nsp-fault:alarm-change']
437
- # cleared = False
438
- # aknowledged = False
439
- # if 'severity' in list(alarmData.keys()) :
440
- # if alarmData['severity']['new-value'] == 'cleared' :
441
- # cleared = True
442
- # if 'acknowledged' in list(alarmData.keys()) :
443
- # aknowledged = alarmData['acknowledged']['new-value']
444
- # oalarm['dataEnh'] = {
445
- # 'newAlarm' : False,
446
- # 'alarmChange' : True,
447
- # 'alarmId' : int(alarmData['objectId'].split(':')[-1]),
448
- # 'cleared' : cleared,
449
- # 'aknowledged' : aknowledged,
450
- # **alarmData ,
451
- # }
452
- # return oalarm
453
-
454
394
 
455
395
  def kafka_listen(self) :
456
396
  def hold_if_kafka_refresh_inprogress() :
@@ -773,13 +713,18 @@ class PmHadoopClient :
773
713
  self.PM24H = 1
774
714
  self.PM15M = 2
775
715
  self.KPIAGGR = 3
716
+ self.CURRENT = 1
717
+ self.ARCHIVE = 2
776
718
  self.logger : DEBUGGER = self.wsnoc.logger
719
+ self.FREE_STORAGE_STRICT = True
720
+ self.FREE_STORAGE_THRESHOLD_MB = 30000
777
721
 
778
722
 
779
723
  def connect(self) :
780
724
  if not self.wsnoc.connected :
781
725
  raise Exception('WSNOC is not connected')
782
726
  try :
727
+ self.logger.info(f"Connecting to PM Hadoop at {self.ip}:{self.hdfs_port}" , source='PmHadoopClient')
783
728
  self.client = HdfsClient(self.ip, self.hdfs_port, use_trash=self.hdfs_use_trash)
784
729
  except Exception as e:
785
730
  self.logger.warning('PM Hadoop client is using WSNOC port 8020/custom port. Please check if it is not blocked by firewall.' , source='PmHadoopClient')
@@ -795,16 +740,23 @@ class PmHadoopClient :
795
740
  dt = datetime.strptime(date_str, "%Y%m%d")
796
741
  return int(dt.timestamp())
797
742
 
798
- def pm_list(self , mode , date_range=[]) :
743
+ def pm_list(self , mode , target_pm , date_range=[] , date_range_in_days=None) :
799
744
  '''
800
745
  mode : must be one of the following:
801
746
  - self.PM24H
802
747
  - self.PM15M
803
748
  - self.KPIAGGR
749
+ target_pm : must be on the following :
750
+ - self.CURRENT
751
+ - self.ARCHIVE
804
752
  date_range : must be a list of two integers in the format of [start_timestamp, end_timestamp]
805
753
  - for example: [1718217600, 1718221200]
754
+ date_range_in_days : if provided, date_range will be set to last X days
755
+ - for example: 30 days ago to now
756
+ - if date_range_in_days is not provided, date_range will be used as is
806
757
  - if date_range is not provided, all available PM dates will be returned
807
758
  '''
759
+ self.logger.info(f"Getting available PM dates Original Args: {mode}/{target_pm}" , source='PmHadoopClient')
808
760
  if mode == self.PM24H :
809
761
  _mode = 'ONE_DAY'
810
762
  elif mode == self.PM15M :
@@ -813,9 +765,20 @@ class PmHadoopClient :
813
765
  _mode = 'KPIAGGR'
814
766
  else :
815
767
  raise Exception(f'Invalid mode: {mode}')
816
- self.logger.info(f"Getting available PM dates for {_mode}" , source='PmHadoopClient')
817
- dirs = list(self.client.ls([f'/PMDATA/{_mode}']))
768
+ if target_pm == self.CURRENT :
769
+ _target = 'PMDATA'
770
+ elif target_pm == self.ARCHIVE :
771
+ _target = "ARC_PMDATA"
772
+ else :
773
+ self.logger.error(f"target_pm arg must be self.CURRENT or self.ARCHIVE.")
774
+ raise Exception("Invalid TARGET_PM")
775
+ self.logger.info(f"Getting available PM dates for {_target}/{_mode}" , source='PmHadoopClient')
776
+ dirs = list(self.client.ls([f'/{_target}/{_mode}']))
818
777
  ts_now = getTimestamp()
778
+ if date_range_in_days :
779
+ self.logger.info(f"Setting date range to last {date_range_in_days} days" , source='PmHadoopClient')
780
+ date_range = [ts_now - date_range_in_days * 24 * 60 * 60, ts_now]
781
+ self.logger.info(f"Date range: {tuple(date_range)}" , source='PmHadoopClient')
819
782
  for index , dir in enumerate(dirs) :
820
783
  self.logger.info(f"Processing {dir.get('path')}" , source='PmHadoopClient')
821
784
  date_str = dir.get('path').split('=')[-1]
@@ -829,10 +792,12 @@ class PmHadoopClient :
829
792
  if dir['pm_date_timestamp'] > date_range[1] :
830
793
  del dirs[index]
831
794
  return dirs
795
+
796
+ def use_jump_host(self , jhost) :
797
+ ...
832
798
 
833
799
  def _download_dir(self , hdfs_path, local_path):
834
800
  for entry in self.client.ls([hdfs_path]):
835
- # print(entry)
836
801
  entry_path = entry['path']
837
802
  entry_type = entry['file_type']
838
803
  if entry_type == 'd':
@@ -840,13 +805,19 @@ class PmHadoopClient :
840
805
  mkdirs(subdir)
841
806
  self._download_dir(entry_path, subdir)
842
807
  else: # FILE
843
- self.logger.debug(f"Downloading {entry_path} → {local_path}" , source='PmHadoopClient')
808
+ self.logger.info(f"Downloading {entry_path} → {local_path}" , source='PmHadoopClient')
844
809
  self.client.copyToLocal([entry_path], local_path)
845
810
 
846
811
  def download(self , obj , destination_path) :
847
812
  self.wsnoc.logger.info(f'Downloading {obj.get("pm_date")} to {destination_path}')
848
813
  destination_path = f"{destination_path}/{obj.get('mode')}/{obj.get('pm_date')}"
814
+ self.logger.debug(f"FREE_STORAGE_THRESHOLD_MB={self.FREE_STORAGE_THRESHOLD_MB} FREE_STORAGE_STRICT={self.FREE_STORAGE_STRICT}")
849
815
  mkdirs(destination_path)
816
+ free_space = get_free_space(destination_path)
817
+ if self.FREE_STORAGE_STRICT :
818
+ if free_space < self.FREE_STORAGE_THRESHOLD_MB :
819
+ self.logger.error(f'Free storage is less than {self.FREE_STORAGE_THRESHOLD_MB} MB. Skipping download. [free_space={free_space} MB]')
820
+ raise Exception(f'No enough space to download PM data.')
850
821
  self._download_dir(obj.get('path'), destination_path)
851
822
 
852
823
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.142
3
+ Version: 2.144
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -16,6 +16,8 @@ Requires-Dist: flask_socketio
16
16
  Requires-Dist: python-dotenv
17
17
  Requires-Dist: gevent
18
18
  Requires-Dist: pyzipper
19
+ Requires-Dist: shutil
20
+ Requires-Dist: bs4
19
21
  Requires-Dist: pyjwt
20
22
  Requires-Dist: authlib
21
23
  Requires-Dist: kafka-python
@@ -11,6 +11,8 @@ flask_socketio
11
11
  python-dotenv
12
12
  gevent
13
13
  pyzipper
14
+ shutil
15
+ bs4
14
16
  pyjwt
15
17
  authlib
16
18
  kafka-python
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '2.142'
3
+ VERSION = '2.144'
4
4
 
5
5
  # Setting up
6
6
  setup(
@@ -22,6 +22,8 @@ setup(
22
22
  'python-dotenv',
23
23
  'gevent',
24
24
  'pyzipper',
25
+ "shutil",
26
+ "bs4",
25
27
  'pyjwt',
26
28
  'authlib',
27
29
  'kafka-python'
File without changes