easy-utils-dev 2.168__tar.gz → 2.170__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.168 → easy_utils_dev-2.170}/PKG-INFO +1 -1
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/generate_license.py +2 -2
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/uiserver.py +29 -16
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/PKG-INFO +1 -1
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/SOURCES.txt +0 -1
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/setup.py +1 -1
- easy_utils_dev-2.168/easy_utils_dev/uiserver-VM026441.py +0 -598
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/MANIFEST.in +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/debugger.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/exceptions.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/ne1830PSS.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/temp_memory.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/utils.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/wsnoclib.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/requires.txt +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.168 → easy_utils_dev-2.170}/setup.cfg +0 -0
|
@@ -45,9 +45,9 @@ def generate_license(appname, uuid=None, expire_date=None, level="basic",write_f
|
|
|
45
45
|
hashed = hashlib.sha256(combined_string.encode()).digest()
|
|
46
46
|
license_key = base64.b64encode(hashed).decode()
|
|
47
47
|
if method == 1 :
|
|
48
|
-
license_key = license_key+'||' + expire_date
|
|
48
|
+
license_key = license_key+'||' + expire_date + "||" + enc.en_base64(appname)
|
|
49
49
|
elif method == 2 :
|
|
50
|
-
license_key = license_key+'||' + expire_date + "||" + ",".join(_features)
|
|
50
|
+
license_key = license_key+'||' + expire_date + "||" + ",".join(_features) + "||" + enc.en_base64(appname)
|
|
51
51
|
if write_file :
|
|
52
52
|
with open('./license.dat' , 'w') as f :
|
|
53
53
|
f.write(license_key)
|
|
@@ -99,6 +99,7 @@ class Response :
|
|
|
99
99
|
self.failure = self.error
|
|
100
100
|
self.socket = socket
|
|
101
101
|
self.request = request
|
|
102
|
+
self.cache = TemporaryMemory()
|
|
102
103
|
|
|
103
104
|
def _emit(self , data, role , options={} ) :
|
|
104
105
|
self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
|
|
@@ -113,7 +114,7 @@ class Response :
|
|
|
113
114
|
elif toast :
|
|
114
115
|
role = 'toast'
|
|
115
116
|
timestamp = getTimestamp()
|
|
116
|
-
r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
|
|
117
|
+
r = {'status' : 200 , 'ok' : True , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
|
|
117
118
|
if role :
|
|
118
119
|
self._emit( r , role, options)
|
|
119
120
|
return r
|
|
@@ -129,7 +130,7 @@ class Response :
|
|
|
129
130
|
elif toast :
|
|
130
131
|
role = 'toast'
|
|
131
132
|
timestamp = getTimestamp()
|
|
132
|
-
r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
133
|
+
r = {'status' : 400 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
133
134
|
if role :
|
|
134
135
|
self._emit( r , role , options)
|
|
135
136
|
return r
|
|
@@ -141,7 +142,7 @@ class Response :
|
|
|
141
142
|
elif toast :
|
|
142
143
|
role = 'toast'
|
|
143
144
|
timestamp = getTimestamp()
|
|
144
|
-
r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
145
|
+
r = {'status' : 500 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
145
146
|
if role :
|
|
146
147
|
self._emit( r , role , options)
|
|
147
148
|
return r
|
|
@@ -153,10 +154,15 @@ class Response :
|
|
|
153
154
|
elif toast :
|
|
154
155
|
role = 'toast'
|
|
155
156
|
timestamp = getTimestamp()
|
|
156
|
-
r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
157
|
+
r = {'status' : 404 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
157
158
|
if role :
|
|
158
159
|
self._emit( r , role , options )
|
|
159
160
|
return r
|
|
161
|
+
|
|
162
|
+
def store_error_message(self , key , message ) :
|
|
163
|
+
key = generateToken(128)
|
|
164
|
+
self.cache.set(key , message)
|
|
165
|
+
return key
|
|
160
166
|
|
|
161
167
|
def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
162
168
|
role = None
|
|
@@ -165,7 +171,7 @@ class Response :
|
|
|
165
171
|
elif toast :
|
|
166
172
|
role = 'toast'
|
|
167
173
|
timestamp = getTimestamp()
|
|
168
|
-
r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
174
|
+
r = {'status' : 401 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
169
175
|
if role :
|
|
170
176
|
self._emit( r , role , options)
|
|
171
177
|
return r
|
|
@@ -229,7 +235,8 @@ class UISERVER :
|
|
|
229
235
|
ssl_crt=None,
|
|
230
236
|
ssl_key=None,
|
|
231
237
|
template_folder='templates/' ,
|
|
232
|
-
static_folder = 'templates/assets'
|
|
238
|
+
static_folder = 'templates/assets',
|
|
239
|
+
socketio_async_mode = "threading"
|
|
233
240
|
,**kwargs
|
|
234
241
|
) -> None:
|
|
235
242
|
self.id = id
|
|
@@ -252,22 +259,24 @@ class UISERVER :
|
|
|
252
259
|
self.return_exception_as_code_400 = True
|
|
253
260
|
self.request_reply_base_url= '/request/result'
|
|
254
261
|
self.sessions = {}
|
|
262
|
+
self.socketio_async_mode = socketio_async_mode
|
|
255
263
|
if https :
|
|
256
264
|
self.httpProtocol = 'https'
|
|
257
265
|
else :
|
|
258
266
|
self.httpProtocol = 'http'
|
|
259
267
|
self.socketio = SocketIO(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
app , cors_allowed_origins="*" ,
|
|
269
|
+
async_mode=self.socketio_async_mode ,
|
|
270
|
+
engineio_logger=False ,
|
|
271
|
+
always_connect=True ,
|
|
272
|
+
manage_session=True,
|
|
273
|
+
**kwargs
|
|
274
|
+
)
|
|
267
275
|
cenv[id] = self
|
|
268
276
|
self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
|
|
269
277
|
self.cache = TemporaryMemory()
|
|
270
278
|
start_thread(target=self.delete_very_old_requests)
|
|
279
|
+
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
|
|
271
280
|
self.secret_key_execlude_urls = []
|
|
272
281
|
self.socketio_rooms = {}
|
|
273
282
|
self.log_url_requests = True
|
|
@@ -280,6 +289,8 @@ class UISERVER :
|
|
|
280
289
|
name='easy_utils_dev_uiserver',
|
|
281
290
|
homePath=TMP_PATH
|
|
282
291
|
)
|
|
292
|
+
self.start_before_request()
|
|
293
|
+
|
|
283
294
|
def update_cert(self , crt, ssl ) :
|
|
284
295
|
self.ssl_crt=crt
|
|
285
296
|
self.ssl_key=ssl
|
|
@@ -378,7 +389,7 @@ class UISERVER :
|
|
|
378
389
|
|
|
379
390
|
@self.app.before_request
|
|
380
391
|
def before_request() :
|
|
381
|
-
if self.log_url_requests and self.logger :
|
|
392
|
+
if self.log_url_requests and self.logger and request.method != 'OPTIONS' :
|
|
382
393
|
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
383
394
|
|
|
384
395
|
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
@@ -482,7 +493,7 @@ class UISERVER :
|
|
|
482
493
|
response.headers['abortable'] = True
|
|
483
494
|
except :
|
|
484
495
|
response.headers['abortable'] = False
|
|
485
|
-
if self.log_url_requests and self.logger :
|
|
496
|
+
if self.log_url_requests and self.logger and request.method != 'OPTIONS':
|
|
486
497
|
self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
|
|
487
498
|
return response
|
|
488
499
|
|
|
@@ -550,6 +561,9 @@ class UISERVER :
|
|
|
550
561
|
def test_connection():
|
|
551
562
|
return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
|
|
552
563
|
|
|
564
|
+
@self.app.route('/api/v1/_error/<key>' , methods=['GET'])
|
|
565
|
+
def get_error_message(key):
|
|
566
|
+
return self.Response.ok(message=self.Response.cache.get(key))
|
|
553
567
|
|
|
554
568
|
if self.httpProtocol == 'http' :
|
|
555
569
|
con = None
|
|
@@ -576,7 +590,6 @@ class UISERVER :
|
|
|
576
590
|
|
|
577
591
|
|
|
578
592
|
def startUi(self , daemon=False , suppress_prints=True , block=False) :
|
|
579
|
-
self.start_before_request()
|
|
580
593
|
self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
|
|
581
594
|
self.flaskprocess.daemon = daemon
|
|
582
595
|
self.flaskprocess.start()
|
|
@@ -26,7 +26,6 @@ easy_utils_dev/optics_utils.py
|
|
|
26
26
|
easy_utils_dev/require_auth.py
|
|
27
27
|
easy_utils_dev/simple_sqlite.py
|
|
28
28
|
easy_utils_dev/temp_memory.py
|
|
29
|
-
easy_utils_dev/uiserver-VM026441.py
|
|
30
29
|
easy_utils_dev/uiserver.py
|
|
31
30
|
easy_utils_dev/utils.py
|
|
32
31
|
easy_utils_dev/winserviceapi.py
|
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
import gc
|
|
2
|
-
import json
|
|
3
|
-
import time
|
|
4
|
-
from flask.ctx import F
|
|
5
|
-
from werkzeug.serving import ThreadedWSGIServer
|
|
6
|
-
from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
|
|
7
|
-
from flask_socketio import SocketIO
|
|
8
|
-
from engineio.async_drivers import gevent
|
|
9
|
-
from engineio.async_drivers import threading as threading_engineio
|
|
10
|
-
from flask_cors import CORS
|
|
11
|
-
import logging , os
|
|
12
|
-
from flask import jsonify, request , current_app , copy_current_request_context
|
|
13
|
-
from flask import Flask
|
|
14
|
-
from threading import Thread
|
|
15
|
-
import threading
|
|
16
|
-
from easy_utils_dev.custom_env import cenv
|
|
17
|
-
from easy_utils_dev.utils import kill_thread
|
|
18
|
-
from multiprocessing import Process
|
|
19
|
-
import traceback
|
|
20
|
-
from werkzeug.serving import make_ssl_devcert
|
|
21
|
-
from time import sleep
|
|
22
|
-
from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs , lget
|
|
23
|
-
from easy_utils_dev.temp_memory import TemporaryMemory
|
|
24
|
-
from easy_utils_dev.debugger import DEBUGGER
|
|
25
|
-
import signal
|
|
26
|
-
import sys
|
|
27
|
-
from tempfile import gettempdir
|
|
28
|
-
from urllib.parse import urlparse, parse_qs
|
|
29
|
-
|
|
30
|
-
TMP_PATH = gettempdir()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def extract_buid(url: str , key ) -> str | None:
|
|
34
|
-
"""
|
|
35
|
-
Extracts the 'buid' query parameter from a URL.
|
|
36
|
-
Returns None if not found.
|
|
37
|
-
"""
|
|
38
|
-
parsed = urlparse(url)
|
|
39
|
-
query_params = parse_qs(parsed.query)
|
|
40
|
-
return query_params.get( key , [None])[0]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def getClassById( id ) :
|
|
44
|
-
return cenv[id]
|
|
45
|
-
|
|
46
|
-
def create_ssl(host,output) :
|
|
47
|
-
'''
|
|
48
|
-
host : is the IP/Adress of the server which servers the web-server
|
|
49
|
-
output: the output locaiton to generate the ssl certificate. it should end with filename without extension
|
|
50
|
-
'''
|
|
51
|
-
return make_ssl_devcert( output , host=host)
|
|
52
|
-
|
|
53
|
-
def clone_request(request):
|
|
54
|
-
"""Return a plain dict clone of Flask request data."""
|
|
55
|
-
return {
|
|
56
|
-
"method": request.method,
|
|
57
|
-
"path": request.path,
|
|
58
|
-
"url": request.url,
|
|
59
|
-
"headers": dict(request.headers),
|
|
60
|
-
"args": request.args.to_dict(flat=False),
|
|
61
|
-
"form": request.form.to_dict(flat=False),
|
|
62
|
-
"json": request.get_json(silent=True),
|
|
63
|
-
"data": request.get_data(), # raw body bytes
|
|
64
|
-
"files": {k: v.filename for k, v in request.files.items()},
|
|
65
|
-
"remote_addr": request.remote_addr,
|
|
66
|
-
"cookies": request.cookies,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
class Stream :
|
|
70
|
-
def __init__(self) :
|
|
71
|
-
self.rows = []
|
|
72
|
-
|
|
73
|
-
def register_row(self , row , options={}) :
|
|
74
|
-
_row_stream_id = getRandomKeysAndStr(n=20)
|
|
75
|
-
row['_row_stream_id'] = _row_stream_id
|
|
76
|
-
for key , value in options.items() :
|
|
77
|
-
if not key.startswith('_') :
|
|
78
|
-
raise ValueError(f"Option key '{key}' must start with '_'")
|
|
79
|
-
row[key] = value
|
|
80
|
-
return row
|
|
81
|
-
|
|
82
|
-
def send_cell_update(self , row , cell , value , sid=None ) :
|
|
83
|
-
_row_stream_id = row['_row_stream_id']
|
|
84
|
-
self.socketio.emit(f'/stream/{_row_stream_id}/{cell}' , value , to=sid)
|
|
85
|
-
|
|
86
|
-
def send_new_row(self , tid , row , sid=None , insert_ontop=False ) :
|
|
87
|
-
if not row.get('_row_stream_id') :
|
|
88
|
-
row = self.register_table_row_stream(row)
|
|
89
|
-
self.socketio.emit(f'/stream/table/{tid}/row' , {
|
|
90
|
-
'row' : row,
|
|
91
|
-
'options' : {
|
|
92
|
-
'insert_ontop' : insert_ontop
|
|
93
|
-
}
|
|
94
|
-
} , to=sid )
|
|
95
|
-
|
|
96
|
-
class Response :
|
|
97
|
-
def __init__(self, socket : SocketIO , request : request ) :
|
|
98
|
-
self.success = self.ok
|
|
99
|
-
self.failure = self.error
|
|
100
|
-
self.socket = socket
|
|
101
|
-
self.request = request
|
|
102
|
-
|
|
103
|
-
def _emit(self , data, role , options={} ) :
|
|
104
|
-
self.socket.emit('/stream/notify' , { **data , '_role' : role , '_options' : options } , to=self.request.headers.get('sid'))
|
|
105
|
-
|
|
106
|
-
def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
107
|
-
role = None
|
|
108
|
-
if alert :
|
|
109
|
-
role = 'alert'
|
|
110
|
-
elif toast :
|
|
111
|
-
role = 'toast'
|
|
112
|
-
timestamp = getTimestamp()
|
|
113
|
-
r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
|
|
114
|
-
if role :
|
|
115
|
-
self._emit( r , role, options)
|
|
116
|
-
return r
|
|
117
|
-
|
|
118
|
-
def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
119
|
-
role = None
|
|
120
|
-
if alert :
|
|
121
|
-
role = 'alert'
|
|
122
|
-
elif toast :
|
|
123
|
-
role = 'toast'
|
|
124
|
-
timestamp = getTimestamp()
|
|
125
|
-
r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
126
|
-
if role :
|
|
127
|
-
self._emit( r , role , options)
|
|
128
|
-
return r
|
|
129
|
-
|
|
130
|
-
def internal_error(self , message=None , alert=False, toast=False , options={} , **kwargs ) :
|
|
131
|
-
role = None
|
|
132
|
-
if alert :
|
|
133
|
-
role = 'alert'
|
|
134
|
-
elif toast :
|
|
135
|
-
role = 'toast'
|
|
136
|
-
timestamp = getTimestamp()
|
|
137
|
-
r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
138
|
-
if role :
|
|
139
|
-
self._emit( r , role , options)
|
|
140
|
-
return r
|
|
141
|
-
|
|
142
|
-
def not_found(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
143
|
-
role = None
|
|
144
|
-
if alert :
|
|
145
|
-
role = 'alert'
|
|
146
|
-
elif toast :
|
|
147
|
-
role = 'toast'
|
|
148
|
-
timestamp = getTimestamp()
|
|
149
|
-
r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
150
|
-
if role :
|
|
151
|
-
self._emit( r , role , options )
|
|
152
|
-
return r
|
|
153
|
-
|
|
154
|
-
def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
155
|
-
role = None
|
|
156
|
-
if alert :
|
|
157
|
-
role = 'alert'
|
|
158
|
-
elif toast :
|
|
159
|
-
role = 'toast'
|
|
160
|
-
timestamp = getTimestamp()
|
|
161
|
-
r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
162
|
-
if role :
|
|
163
|
-
self._emit( r , role , options)
|
|
164
|
-
return r
|
|
165
|
-
|
|
166
|
-
class AbortRequest :
|
|
167
|
-
def __init__(self, request ) :
|
|
168
|
-
self.request = clone_request(request)
|
|
169
|
-
self.abort_id = None
|
|
170
|
-
self.abortable = False
|
|
171
|
-
self.thread = None
|
|
172
|
-
self.cache = None
|
|
173
|
-
self.start_ts = getTimestamp()
|
|
174
|
-
self.result = None
|
|
175
|
-
self.async_request = False
|
|
176
|
-
self.internalid = None
|
|
177
|
-
self.in_progress = False
|
|
178
|
-
self.start_ts = None
|
|
179
|
-
self.end_ts = None
|
|
180
|
-
self.execution = None
|
|
181
|
-
self.success = None
|
|
182
|
-
self.traceback = None
|
|
183
|
-
self.delete_async_request_ts = None
|
|
184
|
-
self.killed = False
|
|
185
|
-
|
|
186
|
-
def abort(self) :
|
|
187
|
-
kill_thread(self.thread)
|
|
188
|
-
self.killed = True
|
|
189
|
-
self.in_progress = False
|
|
190
|
-
self.success = False
|
|
191
|
-
self.traceback = None
|
|
192
|
-
self.result = None
|
|
193
|
-
self.delete_async_request_ts = getTimestamp(after_seconds=3600)
|
|
194
|
-
self.end_ts = getTimestamp()
|
|
195
|
-
self.execution = round(self.end_ts - self.start_ts, 2)
|
|
196
|
-
if not self.async_request :
|
|
197
|
-
self.cache.delete(self.abort_id)
|
|
198
|
-
try :
|
|
199
|
-
gc.collect()
|
|
200
|
-
except :
|
|
201
|
-
pass
|
|
202
|
-
|
|
203
|
-
class SocketClientObject :
|
|
204
|
-
def __init__(self ) :
|
|
205
|
-
self.client : SocketIO
|
|
206
|
-
self.internalid : str = None
|
|
207
|
-
self.request = {}
|
|
208
|
-
self.sid = None
|
|
209
|
-
self.rooms = None
|
|
210
|
-
self.csid = None
|
|
211
|
-
self.browserid = None
|
|
212
|
-
|
|
213
|
-
class UISERVER :
|
|
214
|
-
def __init__(self ,
|
|
215
|
-
logger : DEBUGGER = None,
|
|
216
|
-
id=getRandomKey(n=15),
|
|
217
|
-
secretkey=generateToken(),
|
|
218
|
-
serve_with_secret_key=False,
|
|
219
|
-
address='localhost',
|
|
220
|
-
port=5312 ,
|
|
221
|
-
https=False ,
|
|
222
|
-
ssl_crt=None,
|
|
223
|
-
ssl_key=None,
|
|
224
|
-
template_folder='templates/' ,
|
|
225
|
-
static_folder = 'templates/assets'
|
|
226
|
-
,**kwargs
|
|
227
|
-
) -> None:
|
|
228
|
-
self.id = id
|
|
229
|
-
self.static_folder = static_folder
|
|
230
|
-
self.app = app = Flask(self.id , template_folder=template_folder , static_folder=self.static_folder )
|
|
231
|
-
app.config['SECRET_KEY'] = secretkey
|
|
232
|
-
CORS(app,resources={r"/*":{"origins":"*"}})
|
|
233
|
-
self.address= address
|
|
234
|
-
self.port = port
|
|
235
|
-
self.thread = None
|
|
236
|
-
self.ssl_crt=ssl_crt
|
|
237
|
-
self.ssl_key=ssl_key
|
|
238
|
-
self.serve_with_secret_key=serve_with_secret_key
|
|
239
|
-
self.secretkey=secretkey
|
|
240
|
-
self.enable_test_url=True
|
|
241
|
-
self.abort_requests = {}
|
|
242
|
-
self.bg_requests = {}
|
|
243
|
-
self.socketio_clients = {}
|
|
244
|
-
self.abort_base_url = '/request/abort'
|
|
245
|
-
self.return_exception_as_code_400 = True
|
|
246
|
-
self.request_reply_base_url= '/request/result'
|
|
247
|
-
self.sessions = {}
|
|
248
|
-
if https :
|
|
249
|
-
self.httpProtocol = 'https'
|
|
250
|
-
else :
|
|
251
|
-
self.httpProtocol = 'http'
|
|
252
|
-
self.socketio = SocketIO(
|
|
253
|
-
app , cors_allowed_origins="*" ,
|
|
254
|
-
async_mode='threading' ,
|
|
255
|
-
engineio_logger=False ,
|
|
256
|
-
always_connect=True ,
|
|
257
|
-
manage_session=True,
|
|
258
|
-
**kwargs
|
|
259
|
-
)
|
|
260
|
-
cenv[id] = self
|
|
261
|
-
self.fullAddress = f"{self.httpProtocol}://{self.address}:{self.port}"
|
|
262
|
-
self.cache = TemporaryMemory()
|
|
263
|
-
start_thread(target=self.delete_very_old_requests)
|
|
264
|
-
self.secret_key_execlude_urls = []
|
|
265
|
-
self.socketio_rooms = {}
|
|
266
|
-
self.log_url_requests = True
|
|
267
|
-
self.logger = logger
|
|
268
|
-
self.stream = Stream()
|
|
269
|
-
self.simulate_network_delay = False
|
|
270
|
-
self.Response = Response(self.socketio , request )
|
|
271
|
-
if not self.logger :
|
|
272
|
-
self.logger = DEBUGGER(
|
|
273
|
-
name='easy_utils_dev_uiserver',
|
|
274
|
-
homePath=TMP_PATH
|
|
275
|
-
)
|
|
276
|
-
def update_cert(self , crt, ssl ) :
|
|
277
|
-
self.ssl_crt=crt
|
|
278
|
-
self.ssl_key=ssl
|
|
279
|
-
|
|
280
|
-
def register_abortable_request(self , request , abort_id = None ) :
|
|
281
|
-
path = request.path
|
|
282
|
-
Abort = AbortRequest(request)
|
|
283
|
-
if not path.startswith(self.abort_base_url) :
|
|
284
|
-
if not abort_id :
|
|
285
|
-
if not request.headers.get('abortid') :
|
|
286
|
-
abort_id = getRandomKeysAndStr(n=20)
|
|
287
|
-
else :
|
|
288
|
-
abort_id = request.headers.get('abortid')
|
|
289
|
-
|
|
290
|
-
Abort.abort_id = abort_id
|
|
291
|
-
current_thread = threading.current_thread()
|
|
292
|
-
Abort.thread = current_thread
|
|
293
|
-
Abort.cache = self.cache
|
|
294
|
-
Abort.start_ts = getTimestamp()
|
|
295
|
-
self.cache.set( Abort , custom_key=abort_id , auto_destroy_period=120 , store_deleted_key=False )
|
|
296
|
-
return Abort
|
|
297
|
-
|
|
298
|
-
def delete_very_old_requests(self) :
|
|
299
|
-
while True :
|
|
300
|
-
sleep(320)
|
|
301
|
-
now = getTimestamp()
|
|
302
|
-
for key, value in list(self.bg_requests.items()) :
|
|
303
|
-
value : AbortRequest = value
|
|
304
|
-
if value.delete_async_request_ts :
|
|
305
|
-
if value.delete_async_request_ts > now :
|
|
306
|
-
del self.bg_requests[key]
|
|
307
|
-
gc.collect()
|
|
308
|
-
|
|
309
|
-
def create_room(self , room_id : str , members : list[str] ) :
|
|
310
|
-
if room_id in list(self.socketio_rooms.keys()) :
|
|
311
|
-
return
|
|
312
|
-
self.socketio_rooms[room_id] = members
|
|
313
|
-
|
|
314
|
-
def add_member_to_room(self , room_id : str , member : str ) :
|
|
315
|
-
self.socketio_rooms[room_id].append(member)
|
|
316
|
-
|
|
317
|
-
def remove_member_from_room(self , room_id : str , member : str ) :
|
|
318
|
-
self.socketio_rooms[room_id].remove(member)
|
|
319
|
-
|
|
320
|
-
def get_room_members(self , room_id : str ) :
|
|
321
|
-
return self.socketio_rooms[room_id]
|
|
322
|
-
|
|
323
|
-
def start_before_request(self) :
|
|
324
|
-
|
|
325
|
-
@self.app.route(f'{self.abort_base_url}/<id>' , methods=['DELETE'])
|
|
326
|
-
def abort_request(id : str ) :
|
|
327
|
-
abort : AbortRequest = self.cache.get(id)
|
|
328
|
-
timestamp = getTimestamp()
|
|
329
|
-
if abort :
|
|
330
|
-
abort.abort()
|
|
331
|
-
for i in range(30) :
|
|
332
|
-
th = abort.thread
|
|
333
|
-
alive = th.is_alive()
|
|
334
|
-
if not alive :
|
|
335
|
-
break
|
|
336
|
-
time.sleep(.25)
|
|
337
|
-
return self.Response.ok(message='Request aborted' , abort_timestamp=timestamp , abort_id=id , alive=alive , url=abort.request.get('path'))
|
|
338
|
-
|
|
339
|
-
else :
|
|
340
|
-
return self.Response.not_found(message='Request not found or request is not abortable. Check request headers for abortable flag.')
|
|
341
|
-
|
|
342
|
-
@self.app.route(f"/request/traceback/<key>" , methods=['GET'])
|
|
343
|
-
def get_traceback(key : str ) :
|
|
344
|
-
traceback = self.cache.get(key)
|
|
345
|
-
if traceback :
|
|
346
|
-
return self.Response.ok(message='Traceback found' , traceback=traceback.get('traceback'))
|
|
347
|
-
else :
|
|
348
|
-
return self.Response.not_found(message='Traceback not found or expired')
|
|
349
|
-
|
|
350
|
-
@self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
|
|
351
|
-
def get_result_of_async_request(id : str ) :
|
|
352
|
-
request : AbortRequest = self.bg_requests.get(id)
|
|
353
|
-
if request :
|
|
354
|
-
return self.Response.ok(
|
|
355
|
-
message='Result of async request found' ,
|
|
356
|
-
result=request.result ,
|
|
357
|
-
in_progress=request.in_progress ,
|
|
358
|
-
async_request=request.async_request ,
|
|
359
|
-
internalid=request.internalid ,
|
|
360
|
-
start_ts=request.start_ts ,
|
|
361
|
-
end_ts=request.end_ts ,
|
|
362
|
-
execution=request.execution ,
|
|
363
|
-
success=request.success ,
|
|
364
|
-
killed=request.killed
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
@self.app.before_request
|
|
369
|
-
def before_request() :
|
|
370
|
-
if self.log_url_requests and self.logger :
|
|
371
|
-
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
372
|
-
|
|
373
|
-
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
374
|
-
return self.Response.unauthorized(message='Secret key is invalid')
|
|
375
|
-
|
|
376
|
-
@copy_current_request_context
|
|
377
|
-
def run_async_job_results( target_func , abort : AbortRequest ) :
|
|
378
|
-
abort.in_progress = True
|
|
379
|
-
abort.async_request = True
|
|
380
|
-
abort.internalid = request.internalid
|
|
381
|
-
abort.start_ts = getTimestamp()
|
|
382
|
-
try :
|
|
383
|
-
result = target_func(*request.args, **request.form)
|
|
384
|
-
abort.success = True
|
|
385
|
-
except Exception as e :
|
|
386
|
-
abort.success = False
|
|
387
|
-
abort.result = str(e)
|
|
388
|
-
abort.traceback = traceback.format_exc()
|
|
389
|
-
raise
|
|
390
|
-
abort.result = result
|
|
391
|
-
abort.end_ts = getTimestamp()
|
|
392
|
-
abort.execution = round(abort.end_ts - abort.start_ts, 2)
|
|
393
|
-
abort.in_progress = False
|
|
394
|
-
abort.delete_async_request_ts = getTimestamp(after_seconds=3600)
|
|
395
|
-
|
|
396
|
-
abortable = request.headers.get('abortable')
|
|
397
|
-
requestId = getRandomKeysAndStr(n=10)
|
|
398
|
-
request.start_ts = getTimestamp()
|
|
399
|
-
request.internalid = requestId
|
|
400
|
-
if abortable :
|
|
401
|
-
abort = self.register_abortable_request(request)
|
|
402
|
-
request.abortable = True
|
|
403
|
-
request.abort_id = abort.abort_id
|
|
404
|
-
# check here if async in the headers
|
|
405
|
-
# if yes . i will trigger the function in thread
|
|
406
|
-
# start_tread(#how to get the target function here ? )
|
|
407
|
-
# now i want to return response to UI { status : 200 , message : 'request now in running bg' , abort_id : abort.abort_id }
|
|
408
|
-
# the flask function should not be called again
|
|
409
|
-
if request.headers.get('async') == 'false' :
|
|
410
|
-
target_func = current_app.view_functions.get(request.endpoint)
|
|
411
|
-
if not target_func:
|
|
412
|
-
return self.Response.not_found(message='Route not found')
|
|
413
|
-
th = start_thread(target=run_async_job_results, args=[target_func , abort ])
|
|
414
|
-
abort.thread = th
|
|
415
|
-
self.bg_requests[requestId] = abort
|
|
416
|
-
return self.Response.ok(message='Request now in running bg' , abort_id=abort.abort_id)
|
|
417
|
-
|
|
418
|
-
if self.return_exception_as_code_400 :
|
|
419
|
-
@self.app.errorhandler(Exception)
|
|
420
|
-
def handle_exception(e):
|
|
421
|
-
|
|
422
|
-
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
423
|
-
key = getRandomKeysAndStr(n=10)
|
|
424
|
-
tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
|
|
425
|
-
# Example: file, line, function, text
|
|
426
|
-
error_file = tb_last.filename
|
|
427
|
-
error_line = tb_last.lineno
|
|
428
|
-
error_func = tb_last.name
|
|
429
|
-
error_code = tb_last.line
|
|
430
|
-
|
|
431
|
-
# Log the full traceback (optional)
|
|
432
|
-
traceback.print_exc()
|
|
433
|
-
# Customize the error response
|
|
434
|
-
t = getTimestamp()
|
|
435
|
-
response = {
|
|
436
|
-
"status" : 400 ,
|
|
437
|
-
"key" : key,
|
|
438
|
-
"error": str(e),
|
|
439
|
-
"message": str(e),
|
|
440
|
-
"type": type(e).__name__ ,
|
|
441
|
-
"request_full_url" : request.url ,
|
|
442
|
-
"request_method" : request.method ,
|
|
443
|
-
"endpoint" : request.endpoint ,
|
|
444
|
-
"request_path" : request.path ,
|
|
445
|
-
"error_file" : os.path.basename(error_file).replace('.py' , ''),
|
|
446
|
-
"error_line" : error_line,
|
|
447
|
-
"error_func" : error_func,
|
|
448
|
-
"error_code" : error_code,
|
|
449
|
-
'timestamp' : t ,
|
|
450
|
-
"date" : convertTimestampToDate(t) ,
|
|
451
|
-
'traceback_url' : f"/request/traceback/{key}"
|
|
452
|
-
}
|
|
453
|
-
self.logger.error(f'error: {json.dumps(response , indent=4)}')
|
|
454
|
-
self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
|
|
455
|
-
return self.Response.error( **response )
|
|
456
|
-
|
|
457
|
-
@self.app.after_request
|
|
458
|
-
def after_request(response) :
|
|
459
|
-
|
|
460
|
-
if self.simulate_network_delay :
|
|
461
|
-
time.sleep(self.simulate_network_delay)
|
|
462
|
-
now = getTimestamp()
|
|
463
|
-
x = round(now - request.start_ts, 2)
|
|
464
|
-
try :
|
|
465
|
-
response.headers['internalid'] = request.internalid
|
|
466
|
-
response.headers['start_ts'] = request.start_ts
|
|
467
|
-
response.headers['end_ts'] = now
|
|
468
|
-
response.headers['execution'] = x
|
|
469
|
-
if request.abortable :
|
|
470
|
-
response.headers['abortid'] = request.abort_id
|
|
471
|
-
response.headers['abortable'] = True
|
|
472
|
-
except :
|
|
473
|
-
response.headers['abortable'] = False
|
|
474
|
-
if self.log_url_requests and self.logger :
|
|
475
|
-
self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
|
|
476
|
-
return response
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
# socketio client connected.
|
|
480
|
-
@self.socketio.on('connect')
|
|
481
|
-
def handle_client_connect():
|
|
482
|
-
sid = request.sid
|
|
483
|
-
client = SocketClientObject()
|
|
484
|
-
client.internalid = getRandomKeysAndStr(n=20)
|
|
485
|
-
client.sid = sid
|
|
486
|
-
client.request = request
|
|
487
|
-
client.browserid = extract_buid( request.url , 'buid')
|
|
488
|
-
self.create_room(client.browserid , [client.sid])
|
|
489
|
-
client.rooms = self.socketio_rooms
|
|
490
|
-
self.socketio_clients[sid] = client
|
|
491
|
-
self.socketio.emit('/internal/connect', {
|
|
492
|
-
'status': 200,
|
|
493
|
-
'message': 'client connected' ,
|
|
494
|
-
'internalid' : client.internalid,
|
|
495
|
-
'sid' : sid,
|
|
496
|
-
},
|
|
497
|
-
to=sid
|
|
498
|
-
)
|
|
499
|
-
self.logger.info(f'Connected : {sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
|
|
500
|
-
|
|
501
|
-
# socketio client connected.
|
|
502
|
-
@self.socketio.on('disconnect')
|
|
503
|
-
def handle_client_disconnect():
|
|
504
|
-
if request.sid in list(self.socketio_clients.keys()) :
|
|
505
|
-
del self.socketio_clients[request.sid]
|
|
506
|
-
self.logger.info(f'Disconnected : {request.sid} | Clients:{len(self.socketio_clients.keys())}' , source="Event")
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
def getSocketio( self ) -> SocketIO:
|
|
510
|
-
return self.socketio
|
|
511
|
-
|
|
512
|
-
def getFlask( self ) -> Flask:
|
|
513
|
-
return self.app
|
|
514
|
-
|
|
515
|
-
def shutdownUi(self) :
|
|
516
|
-
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
517
|
-
try:
|
|
518
|
-
self.wsgi_server.shutdown()
|
|
519
|
-
self.wsgi_server.server_close()
|
|
520
|
-
except:
|
|
521
|
-
pass
|
|
522
|
-
kill_thread(self.thread)
|
|
523
|
-
|
|
524
|
-
def _wait_th(self , t ) :
|
|
525
|
-
# t.join()
|
|
526
|
-
while True :
|
|
527
|
-
time.sleep(36000)
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def thrStartUi(self , suppress_prints=True) :
|
|
531
|
-
if self.enable_test_url :
|
|
532
|
-
if not suppress_prints :
|
|
533
|
-
print(f'TEST URL GET-METHOD /connection/test/internal')
|
|
534
|
-
@self.app.route('/connection/test/internal' , methods=['GET'])
|
|
535
|
-
def test_connection():
|
|
536
|
-
return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if self.httpProtocol == 'http' :
|
|
540
|
-
con = None
|
|
541
|
-
elif self.httpProtocol == 'https' :
|
|
542
|
-
con=(self.ssl_crt , self.ssl_key)
|
|
543
|
-
self.wsgi_server = wsgi_server = ThreadedWSGIServer(
|
|
544
|
-
host = self.address ,
|
|
545
|
-
ssl_context=con,
|
|
546
|
-
# ssl_context=('ssl.crt', 'ssl.key'),
|
|
547
|
-
port = self.port,
|
|
548
|
-
app = self.app )
|
|
549
|
-
if not suppress_prints :
|
|
550
|
-
print(f"web-socket: {self.fullAddress}")
|
|
551
|
-
print(f"UI URL : {self.fullAddress}")
|
|
552
|
-
log = logging.getLogger('werkzeug')
|
|
553
|
-
log.setLevel(logging.ERROR)
|
|
554
|
-
wsgi_server.serve_forever()
|
|
555
|
-
|
|
556
|
-
def on_ctrl_c(self , sig=None, frame=None):
|
|
557
|
-
self.stopUi()
|
|
558
|
-
pid = os.getpid()
|
|
559
|
-
os.kill(pid, signal.SIGTERM)
|
|
560
|
-
sys.exit(0)
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
def startUi(self , daemon=False , suppress_prints=True , block=False) :
|
|
564
|
-
self.start_before_request()
|
|
565
|
-
self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
|
|
566
|
-
self.flaskprocess.daemon = daemon
|
|
567
|
-
self.flaskprocess.start()
|
|
568
|
-
start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
|
|
569
|
-
signal.signal(signal.SIGINT, self.on_ctrl_c )
|
|
570
|
-
if block:
|
|
571
|
-
self.wait()
|
|
572
|
-
return self.thread
|
|
573
|
-
|
|
574
|
-
def wait(self):
|
|
575
|
-
"""
|
|
576
|
-
Block the main thread to keep it alive for signal handling (Ctrl+C).
|
|
577
|
-
This allows Ctrl+C to be properly detected. Call this after startUi()
|
|
578
|
-
if you want signal handling to work.
|
|
579
|
-
"""
|
|
580
|
-
try:
|
|
581
|
-
while self.flaskprocess.is_alive():
|
|
582
|
-
self.flaskprocess.join(timeout=0.1)
|
|
583
|
-
except KeyboardInterrupt:
|
|
584
|
-
if not hasattr(self, '_shutting_down'):
|
|
585
|
-
self._shutting_down = True
|
|
586
|
-
self.on_ctrl_c()
|
|
587
|
-
except SystemExit:
|
|
588
|
-
# Re-raise SystemExit to allow proper program termination
|
|
589
|
-
raise
|
|
590
|
-
|
|
591
|
-
def stopUi(self) :
|
|
592
|
-
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
593
|
-
try:
|
|
594
|
-
self.wsgi_server.shutdown()
|
|
595
|
-
except:
|
|
596
|
-
pass
|
|
597
|
-
kill_thread(self.thread)
|
|
598
|
-
return True
|
|
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
|
|
File without changes
|