easy-utils-dev 2.143__tar.gz → 2.145__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.
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/PKG-INFO +3 -1
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/temp_memory.py +1 -2
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/uiserver.py +149 -4
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/utils.py +30 -1
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/wsnoclib.py +72 -80
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev.egg-info/PKG-INFO +3 -1
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev.egg-info/requires.txt +2 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/setup.py +3 -1
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/MANIFEST.in +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/debugger-C-PF4PAMMP.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/debugger.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/exceptions.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/generate_license.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/ne1830PSS.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev.egg-info/SOURCES.txt +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.143 → easy_utils_dev-2.145}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easy_utils_dev
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.145
|
|
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
|
|
@@ -35,10 +35,9 @@ class TemporaryMemory :
|
|
|
35
35
|
|
|
36
36
|
def save(self, item , custom_key=None ,auto_destroy_period=60 , store_deleted_key=True) :
|
|
37
37
|
now = getTimestamp()
|
|
38
|
+
later = None
|
|
38
39
|
if auto_destroy_period :
|
|
39
40
|
later = getTimestamp(after_seconds=auto_destroy_period)
|
|
40
|
-
else :
|
|
41
|
-
later = None
|
|
42
41
|
if not custom_key :
|
|
43
42
|
custom_key = f"{getTimestamp()}-{generateToken(iter=4)}".upper()
|
|
44
43
|
self.store[custom_key] = {
|
|
@@ -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.
|
|
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=
|
|
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,34 @@ 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
|
+
def can_be_int(string) :
|
|
480
|
+
"""
|
|
481
|
+
Check if a string can be converted to an integer.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
string (str): String to check.
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
bool: True if string can be converted to an integer, False otherwise.
|
|
488
|
+
"""
|
|
489
|
+
try :
|
|
490
|
+
int(string)
|
|
491
|
+
return True
|
|
492
|
+
except :
|
|
493
|
+
return False
|
|
494
|
+
|
|
466
495
|
if __name__ == "__main__":
|
|
467
496
|
pass
|
|
@@ -1,14 +1,14 @@
|
|
|
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
|
|
8
9
|
from threading import Thread
|
|
9
10
|
from easy_utils_dev.Events import EventEmitter
|
|
10
11
|
from .EasySsh import CREATESSH
|
|
11
|
-
import traceback
|
|
12
12
|
import xmltodict
|
|
13
13
|
from .FastQueue import FastQueue
|
|
14
14
|
import tempfile , os
|
|
@@ -66,7 +66,6 @@ class WSNOCLIB :
|
|
|
66
66
|
self.api_count_limit = 999999999999
|
|
67
67
|
self.temp_dir = os.path.join(tmp_dir , 'wsnoclib' )
|
|
68
68
|
self.baseUrl = self.createBaseUrl()
|
|
69
|
-
self.session = requests.Session()
|
|
70
69
|
self.numberOfRequests=0
|
|
71
70
|
self.request_max_count = request_max_count
|
|
72
71
|
self.onGoingRequests=0
|
|
@@ -84,6 +83,8 @@ class WSNOCLIB :
|
|
|
84
83
|
self.refresh_thread = None
|
|
85
84
|
self.token_refresh_count = 0
|
|
86
85
|
self.session = WSNOCSession(self)
|
|
86
|
+
self.max_concurrent_requests = 40
|
|
87
|
+
self.current_requests = 0
|
|
87
88
|
self.connected = False
|
|
88
89
|
self.pm_hadoop = PmHadoopClient(self)
|
|
89
90
|
if register_atexit :
|
|
@@ -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
|
-
|
|
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() :
|
|
@@ -722,15 +662,28 @@ class WSNOCSession(requests.Session):
|
|
|
722
662
|
def rebuild_auth(self, prepared_request, response):
|
|
723
663
|
return
|
|
724
664
|
|
|
665
|
+
def _refactor_url(self , url) :
|
|
666
|
+
if str(url).startswith('/') :
|
|
667
|
+
url = f"{self._wsnoc.baseUrl}/{url}"
|
|
668
|
+
return url
|
|
669
|
+
|
|
725
670
|
def hold_for_token_refresh(self, url=None) :
|
|
726
671
|
while self._wsnoc.refresh_inprogress :
|
|
727
672
|
self._wsnoc.logger.info(f'Waiting for token refresh. {url if url else "No URL"}')
|
|
728
673
|
sleep(.5)
|
|
729
674
|
|
|
675
|
+
def hold_for_max_concurrent_requests(self, url=None) :
|
|
676
|
+
while self._wsnoc.current_requests >= self._wsnoc.max_concurrent_requests :
|
|
677
|
+
self._wsnoc.logger.info(f'Max concurrent requests reached. Waiting for a request to complete. {url if url else "No URL"}')
|
|
678
|
+
self._wsnoc.logger.debug(f'Max concurrent requests reached. Waiting for a request to complete. {url if url else "No URL"} [current_requests={self._wsnoc.current_requests}] [max_concurrent_requests={self._wsnoc.max_concurrent_requests}]')
|
|
679
|
+
sleep(1)
|
|
680
|
+
|
|
730
681
|
def request(self, method, url , retries=0 , skip_hold_for_token_refresh=False , debug_this_request=False , **kwargs):
|
|
682
|
+
url = self._refactor_url(url)
|
|
731
683
|
self._wsnoc.logger.debug(f'[{method}] : {url}')
|
|
732
684
|
if not skip_hold_for_token_refresh :
|
|
733
685
|
self.hold_for_token_refresh(url)
|
|
686
|
+
self.hold_for_max_concurrent_requests(url)
|
|
734
687
|
self._wsnoc.api_count += 1
|
|
735
688
|
token = self._wsnoc.getLatestToken().get('bearer_token')
|
|
736
689
|
request_headers = kwargs.get('headers' , {})
|
|
@@ -738,6 +691,8 @@ class WSNOCSession(requests.Session):
|
|
|
738
691
|
if not request_headers.get('Authorization') :
|
|
739
692
|
request_headers['Authorization'] = token
|
|
740
693
|
kwargs['headers'] = request_headers
|
|
694
|
+
start_ts = getTimestamp()
|
|
695
|
+
self._wsnoc.current_requests += 1
|
|
741
696
|
request = super().request(method, url, **kwargs )
|
|
742
697
|
if debug_this_request :
|
|
743
698
|
self._wsnoc.logger.info(f'''
|
|
@@ -747,6 +702,7 @@ class WSNOCSession(requests.Session):
|
|
|
747
702
|
[DEBUG] Response: {request.text}
|
|
748
703
|
[DEBUG] OK: {request.ok}
|
|
749
704
|
[DEBUG] Method: {request.request.method}
|
|
705
|
+
[DEBUG] StartTs: {start_ts}
|
|
750
706
|
''')
|
|
751
707
|
for i in range(retries) :
|
|
752
708
|
if request.ok :
|
|
@@ -756,7 +712,13 @@ class WSNOCSession(requests.Session):
|
|
|
756
712
|
self.hold_for_token_refresh(url)
|
|
757
713
|
request = super().request(method, url, **kwargs )
|
|
758
714
|
self._wsnoc.logger.debug(f'[Try-{i}] [{method}] : {url}- {request.status_code}')
|
|
759
|
-
|
|
715
|
+
end_ts = getTimestamp()
|
|
716
|
+
execution_secs = round(end_ts - start_ts, 2)
|
|
717
|
+
self._wsnoc.logger.info(f'[{method}] : {url} - [{request.status_code}][{execution_secs}sec]')
|
|
718
|
+
request.start_ts = start_ts
|
|
719
|
+
request.end_ts = end_ts
|
|
720
|
+
request.execution = execution_secs
|
|
721
|
+
self._wsnoc.current_requests -= 1
|
|
760
722
|
return request
|
|
761
723
|
|
|
762
724
|
class PmHadoopClient :
|
|
@@ -776,12 +738,17 @@ class PmHadoopClient :
|
|
|
776
738
|
self.CURRENT = 1
|
|
777
739
|
self.ARCHIVE = 2
|
|
778
740
|
self.logger : DEBUGGER = self.wsnoc.logger
|
|
741
|
+
self.FREE_STORAGE_STRICT = True
|
|
742
|
+
self.FREE_STORAGE_THRESHOLD_MB = 30000
|
|
743
|
+
self.jhost = False
|
|
744
|
+
self.jhost_obj = None
|
|
779
745
|
|
|
780
746
|
|
|
781
747
|
def connect(self) :
|
|
782
748
|
if not self.wsnoc.connected :
|
|
783
749
|
raise Exception('WSNOC is not connected')
|
|
784
750
|
try :
|
|
751
|
+
self.logger.info(f"Connecting to PM Hadoop at {self.ip}:{self.hdfs_port}" , source='PmHadoopClient')
|
|
785
752
|
self.client = HdfsClient(self.ip, self.hdfs_port, use_trash=self.hdfs_use_trash)
|
|
786
753
|
except Exception as e:
|
|
787
754
|
self.logger.warning('PM Hadoop client is using WSNOC port 8020/custom port. Please check if it is not blocked by firewall.' , source='PmHadoopClient')
|
|
@@ -797,7 +764,7 @@ class PmHadoopClient :
|
|
|
797
764
|
dt = datetime.strptime(date_str, "%Y%m%d")
|
|
798
765
|
return int(dt.timestamp())
|
|
799
766
|
|
|
800
|
-
def pm_list(self , mode , target_pm , date_range=[]) :
|
|
767
|
+
def pm_list(self , mode , target_pm , date_range=[] , date_range_in_days=None) :
|
|
801
768
|
'''
|
|
802
769
|
mode : must be one of the following:
|
|
803
770
|
- self.PM24H
|
|
@@ -808,8 +775,12 @@ class PmHadoopClient :
|
|
|
808
775
|
- self.ARCHIVE
|
|
809
776
|
date_range : must be a list of two integers in the format of [start_timestamp, end_timestamp]
|
|
810
777
|
- for example: [1718217600, 1718221200]
|
|
778
|
+
date_range_in_days : if provided, date_range will be set to last X days
|
|
779
|
+
- for example: 30 days ago to now
|
|
780
|
+
- if date_range_in_days is not provided, date_range will be used as is
|
|
811
781
|
- if date_range is not provided, all available PM dates will be returned
|
|
812
782
|
'''
|
|
783
|
+
self.logger.info(f"Getting available PM dates Original Args: {mode}/{target_pm}" , source='PmHadoopClient')
|
|
813
784
|
if mode == self.PM24H :
|
|
814
785
|
_mode = 'ONE_DAY'
|
|
815
786
|
elif mode == self.PM15M :
|
|
@@ -827,24 +798,39 @@ class PmHadoopClient :
|
|
|
827
798
|
raise Exception("Invalid TARGET_PM")
|
|
828
799
|
self.logger.info(f"Getting available PM dates for {_target}/{_mode}" , source='PmHadoopClient')
|
|
829
800
|
dirs = list(self.client.ls([f'/{_target}/{_mode}']))
|
|
830
|
-
|
|
801
|
+
if date_range_in_days :
|
|
802
|
+
ts_now = getTimestamp() # this is in seconds
|
|
803
|
+
self.logger.info(f"Setting date range to last {date_range_in_days} days" , source='PmHadoopClient')
|
|
804
|
+
date_range = [ts_now - date_range_in_days * 24 * 60 * 60, ts_now]
|
|
805
|
+
self.logger.info(f"Date range: {tuple(date_range)}" , source='PmHadoopClient')
|
|
831
806
|
for index , dir in enumerate(dirs) :
|
|
832
807
|
self.logger.info(f"Processing {dir.get('path')}" , source='PmHadoopClient')
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
808
|
+
if 'ds' in dir.get('path' , '') :
|
|
809
|
+
date_str = dir.get('path').split('=')[-1]
|
|
810
|
+
dir['pm_date'] = int(date_str)
|
|
811
|
+
dir['mode'] = _mode
|
|
812
|
+
dt = datetime.strptime(date_str, "%Y%m%d")
|
|
813
|
+
dir['pm_date_timestamp'] = int(dt.timestamp())
|
|
814
|
+
if len(date_range) > 0 :
|
|
815
|
+
if dir['pm_date_timestamp'] < date_range[0] :
|
|
816
|
+
del dirs[index]
|
|
817
|
+
if dir['pm_date_timestamp'] > date_range[1] :
|
|
818
|
+
del dirs[index]
|
|
843
819
|
return dirs
|
|
820
|
+
|
|
821
|
+
def use_jump_host(self , jhost , use=True) :
|
|
822
|
+
'''
|
|
823
|
+
not yet implemented
|
|
824
|
+
'''
|
|
825
|
+
self.jhost = use
|
|
826
|
+
if use :
|
|
827
|
+
self.jhost_obj = None
|
|
828
|
+
else :
|
|
829
|
+
self.jhost_obj = None
|
|
830
|
+
|
|
844
831
|
|
|
845
832
|
def _download_dir(self , hdfs_path, local_path):
|
|
846
833
|
for entry in self.client.ls([hdfs_path]):
|
|
847
|
-
# print(entry)
|
|
848
834
|
entry_path = entry['path']
|
|
849
835
|
entry_type = entry['file_type']
|
|
850
836
|
if entry_type == 'd':
|
|
@@ -852,13 +838,19 @@ class PmHadoopClient :
|
|
|
852
838
|
mkdirs(subdir)
|
|
853
839
|
self._download_dir(entry_path, subdir)
|
|
854
840
|
else: # FILE
|
|
855
|
-
self.logger.
|
|
841
|
+
self.logger.info(f"Downloading {entry_path} → {local_path}" , source='PmHadoopClient')
|
|
856
842
|
self.client.copyToLocal([entry_path], local_path)
|
|
857
843
|
|
|
858
844
|
def download(self , obj , destination_path) :
|
|
859
845
|
self.wsnoc.logger.info(f'Downloading {obj.get("pm_date")} to {destination_path}')
|
|
860
846
|
destination_path = f"{destination_path}/{obj.get('mode')}/{obj.get('pm_date')}"
|
|
847
|
+
self.logger.debug(f"FREE_STORAGE_THRESHOLD_MB={self.FREE_STORAGE_THRESHOLD_MB} FREE_STORAGE_STRICT={self.FREE_STORAGE_STRICT}")
|
|
861
848
|
mkdirs(destination_path)
|
|
849
|
+
free_space = get_free_space(destination_path)
|
|
850
|
+
if self.FREE_STORAGE_STRICT :
|
|
851
|
+
if free_space < self.FREE_STORAGE_THRESHOLD_MB :
|
|
852
|
+
self.logger.error(f'Free storage is less than {self.FREE_STORAGE_THRESHOLD_MB} MB. Skipping download. [free_space={free_space} MB]')
|
|
853
|
+
raise Exception(f'No enough space to download PM data.')
|
|
862
854
|
self._download_dir(obj.get('path'), destination_path)
|
|
863
855
|
|
|
864
856
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easy_utils_dev
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.145
|
|
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,6 +1,6 @@
|
|
|
1
1
|
from setuptools import setup, find_packages
|
|
2
2
|
|
|
3
|
-
VERSION = '2.
|
|
3
|
+
VERSION = '2.145'
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|