easy-utils-dev 2.167__tar.gz → 2.169__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.167 → easy_utils_dev-2.169}/PKG-INFO +1 -1
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/generate_license.py +2 -2
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/uiserver.py +21 -5
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/PKG-INFO +1 -1
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/SOURCES.txt +0 -1
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/setup.py +1 -1
- easy_utils_dev-2.167/easy_utils_dev/uiserver-VM026441.py +0 -598
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/MANIFEST.in +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/debugger.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/exceptions.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/ne1830PSS.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/temp_memory.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/utils.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/wsnoclib.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/requires.txt +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.167 → easy_utils_dev-2.169}/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,9 +99,13 @@ 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'))
|
|
106
|
+
|
|
107
|
+
def _emit_redirect(self , url , options={} ) :
|
|
108
|
+
self.socket.emit('/stream/redirect' , { 'url' : url , '_options' : options } , to=self.request.headers.get('sid'))
|
|
105
109
|
|
|
106
110
|
def ok(self , result=[] , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
107
111
|
role = None
|
|
@@ -110,11 +114,15 @@ class Response :
|
|
|
110
114
|
elif toast :
|
|
111
115
|
role = 'toast'
|
|
112
116
|
timestamp = getTimestamp()
|
|
113
|
-
r = {'status' : 200 , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
|
|
117
|
+
r = {'status' : 200 , 'ok' : True , 'message' : message , 'result' : result , **kwargs, 'timestamp' : timestamp}
|
|
114
118
|
if role :
|
|
115
119
|
self._emit( r , role, options)
|
|
116
120
|
return r
|
|
117
121
|
|
|
122
|
+
def redirect(self , url , options={} ) :
|
|
123
|
+
self._emit_redirect( url , options )
|
|
124
|
+
return {'status' : 401 , 'message' : 'redirected' , 'url' : url }
|
|
125
|
+
|
|
118
126
|
def error(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
119
127
|
role = None
|
|
120
128
|
if alert :
|
|
@@ -122,7 +130,7 @@ class Response :
|
|
|
122
130
|
elif toast :
|
|
123
131
|
role = 'toast'
|
|
124
132
|
timestamp = getTimestamp()
|
|
125
|
-
r = {'status' : 400 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
133
|
+
r = {'status' : 400 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
126
134
|
if role :
|
|
127
135
|
self._emit( r , role , options)
|
|
128
136
|
return r
|
|
@@ -134,7 +142,7 @@ class Response :
|
|
|
134
142
|
elif toast :
|
|
135
143
|
role = 'toast'
|
|
136
144
|
timestamp = getTimestamp()
|
|
137
|
-
r = {'status' : 500 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
145
|
+
r = {'status' : 500 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
138
146
|
if role :
|
|
139
147
|
self._emit( r , role , options)
|
|
140
148
|
return r
|
|
@@ -146,10 +154,15 @@ class Response :
|
|
|
146
154
|
elif toast :
|
|
147
155
|
role = 'toast'
|
|
148
156
|
timestamp = getTimestamp()
|
|
149
|
-
r = {'status' : 404 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
157
|
+
r = {'status' : 404 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
150
158
|
if role :
|
|
151
159
|
self._emit( r , role , options )
|
|
152
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
|
|
153
166
|
|
|
154
167
|
def unauthorized(self , message=None , alert=False, toast=False , options={} , **kwargs) :
|
|
155
168
|
role = None
|
|
@@ -158,7 +171,7 @@ class Response :
|
|
|
158
171
|
elif toast :
|
|
159
172
|
role = 'toast'
|
|
160
173
|
timestamp = getTimestamp()
|
|
161
|
-
r = {'status' : 401 , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
174
|
+
r = {'status' : 401 , 'ok' : False , 'message' : message , **kwargs , 'timestamp' : timestamp}
|
|
162
175
|
if role :
|
|
163
176
|
self._emit( r , role , options)
|
|
164
177
|
return r
|
|
@@ -543,6 +556,9 @@ class UISERVER :
|
|
|
543
556
|
def test_connection():
|
|
544
557
|
return f"Status=200<br> ID={self.id}<br> one-time-token={getRandomKey(20)}"
|
|
545
558
|
|
|
559
|
+
@self.app.route('/api/v1/_error/<key>' , methods=['GET'])
|
|
560
|
+
def get_error_message(key):
|
|
561
|
+
return self.Response.ok(message=self.Response.cache.get(key))
|
|
546
562
|
|
|
547
563
|
if self.httpProtocol == 'http' :
|
|
548
564
|
con = None
|
|
@@ -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
|