unisi 0.1.11__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unisi/__init__.py +1 -1
- unisi/autotest.py +1 -24
- unisi/common.py +6 -7
- unisi/containers.py +2 -3
- unisi/guielements.py +3 -4
- unisi/multimon.py +116 -0
- unisi/server.py +6 -19
- unisi/users.py +79 -32
- unisi/utils.py +46 -1
- {unisi-0.1.11.dist-info → unisi-0.1.12.dist-info}/METADATA +13 -10
- {unisi-0.1.11.dist-info → unisi-0.1.12.dist-info}/RECORD +13 -12
- {unisi-0.1.11.dist-info → unisi-0.1.12.dist-info}/WHEEL +1 -1
- {unisi-0.1.11.dist-info → unisi-0.1.12.dist-info}/licenses/LICENSE +0 -0
unisi/__init__.py
CHANGED
unisi/autotest.py
CHANGED
@@ -9,32 +9,9 @@ from .jsoncomparison import Compare, NO_DIFF
|
|
9
9
|
def obj2json(obj):
|
10
10
|
return json.loads(toJson(obj))
|
11
11
|
|
12
|
-
#setting config variables
|
13
|
-
testdir = 'autotest'
|
14
|
-
if not hasattr(config, testdir):
|
15
|
-
config.autotest = False
|
16
|
-
if not hasattr(config, 'port'):
|
17
|
-
config.port = 8000
|
18
|
-
if not hasattr(config, 'upload_dir'):
|
19
|
-
config.upload_dir = 'web'
|
20
|
-
if not hasattr(config, 'logfile'):
|
21
|
-
config.logfile = None
|
22
|
-
if not hasattr(config, 'hot_reload'):
|
23
|
-
config.hot_reload = False
|
24
|
-
if not hasattr(config, 'appname'):
|
25
|
-
config.appname = 'Unisi app'
|
26
|
-
if not hasattr(config, 'mirror'):
|
27
|
-
config.mirror = False
|
28
|
-
|
29
12
|
if not os.path.exists(config.upload_dir):
|
30
13
|
os.makedirs(config.upload_dir)
|
31
14
|
|
32
|
-
#start logging
|
33
|
-
format = "%(asctime)s - %(levelname)s - %(message)s"
|
34
|
-
logfile = config.logfile
|
35
|
-
handlers = [logging.FileHandler(logfile), logging.StreamHandler()] if logfile else []
|
36
|
-
logging.basicConfig(level = logging.WARNING, format = format, handlers = handlers)
|
37
|
-
|
38
15
|
comparator = Compare(rules = {'toolbar': '*'}).check
|
39
16
|
|
40
17
|
class Recorder:
|
@@ -202,7 +179,7 @@ def check_module(module):
|
|
202
179
|
def run_tests():
|
203
180
|
if not os.path.exists(testdir):
|
204
181
|
os.makedirs(testdir)
|
205
|
-
user = User.type(
|
182
|
+
user = User.type(testdir)
|
206
183
|
user.load()
|
207
184
|
errors = []
|
208
185
|
for module in user.screens:
|
unisi/common.py
CHANGED
@@ -9,16 +9,15 @@ def flatten(*arr):
|
|
9
9
|
|
10
10
|
class ArgObject:
|
11
11
|
def __init__(self, **kwargs):
|
12
|
-
|
13
|
-
setattr(self, key, value)
|
12
|
+
self.__dict__.update(kwargs)
|
14
13
|
|
15
14
|
class ReceivedMessage:
|
16
15
|
def __init__(self, data):
|
17
|
-
self.
|
18
|
-
self.
|
19
|
-
self.
|
20
|
-
|
21
|
-
self.
|
16
|
+
self.__dict__.update(data)
|
17
|
+
self.screen = data.get('screen')
|
18
|
+
self.value = data.get('value')
|
19
|
+
def __str__(self):
|
20
|
+
return f'{self.block}/{self.element}->{self.event}({self.value})'
|
22
21
|
|
23
22
|
def toJson(obj):
|
24
23
|
return jsonpickle.encode(obj,unpicklable = False)
|
unisi/containers.py
CHANGED
unisi/guielements.py
CHANGED
@@ -9,8 +9,7 @@ class Gui:
|
|
9
9
|
self.add(kwargs)
|
10
10
|
|
11
11
|
def add(self, kwargs):
|
12
|
-
|
13
|
-
setattr(self, key, value)
|
12
|
+
self.__dict__.update(kwargs)
|
14
13
|
|
15
14
|
def mutate(self, obj):
|
16
15
|
self.__dict__ = obj.__dict__
|
@@ -25,12 +24,12 @@ Line = Gui("Line", type = 'line')
|
|
25
24
|
|
26
25
|
def smart_complete(lst, min_input_length = 0, max_output_length = 20):
|
27
26
|
di = {it: it.lower() for it in lst}
|
28
|
-
def complete(
|
27
|
+
def complete(_, ustr):
|
29
28
|
if len(ustr) < min_input_length:
|
30
29
|
return []
|
31
30
|
ustr = ustr.lower()
|
32
31
|
arr = [(itlow.find(ustr), it, itlow) for it, itlow in di.items() if itlow.find(ustr) != -1]
|
33
|
-
arr.sort(key=lambda e: (e[0], e[2]))
|
32
|
+
arr.sort(key = lambda e: (e[0], e[2]))
|
34
33
|
if len(arr) > max_output_length:
|
35
34
|
arr = arr[: max_output_length]
|
36
35
|
return [e[1] for e in arr]
|
unisi/multimon.py
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
import multiprocessing, time, asyncio, logging
|
2
|
+
from .utils import start_logging
|
3
|
+
from config import froze_time, monitor_tick, profile, pool
|
4
|
+
|
5
|
+
def write_string_to(shared_array, input_string):
|
6
|
+
input_bytes = input_string.encode()
|
7
|
+
shared_array[:len(input_bytes)] = input_bytes
|
8
|
+
|
9
|
+
def read_string_from(shared_array):
|
10
|
+
return shared_array[:].decode().rstrip('\x00')
|
11
|
+
|
12
|
+
_multiprocessing_pool = None
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
def multiprocessing_pool():
|
17
|
+
global _multiprocessing_pool
|
18
|
+
if not _multiprocessing_pool:
|
19
|
+
_multiprocessing_pool = multiprocessing.Pool(pool)
|
20
|
+
return _multiprocessing_pool
|
21
|
+
|
22
|
+
# Define an asynchronous function that will run the synchronous function in a separate process
|
23
|
+
""" argument example
|
24
|
+
def long_running_task(queue):
|
25
|
+
for i in range(5):
|
26
|
+
time.sleep(2) # emulate long calculation
|
27
|
+
queue.put(f"Task is {i*20}% complete")
|
28
|
+
queue.put(None)
|
29
|
+
|
30
|
+
async def callback(string):
|
31
|
+
await context_user().progress(str)
|
32
|
+
"""
|
33
|
+
async def run_external_process(long_running_task, *args, callback = False):
|
34
|
+
if callback:
|
35
|
+
queue = multiprocessing.Manager().Queue()
|
36
|
+
args = *args, queue
|
37
|
+
result = multiprocessing_pool().apply_async(long_running_task, args)
|
38
|
+
if callback:
|
39
|
+
while not result.ready():
|
40
|
+
if not queue.empty():
|
41
|
+
message = queue.get()
|
42
|
+
if message is None:
|
43
|
+
break
|
44
|
+
await callback(message)
|
45
|
+
await asyncio.sleep(0.1)
|
46
|
+
return result.get()
|
47
|
+
|
48
|
+
logging_lock = multiprocessing.Lock()
|
49
|
+
|
50
|
+
splitter = '~'
|
51
|
+
|
52
|
+
def monitor_process(monitor_shared_arr):
|
53
|
+
timer = None
|
54
|
+
session_status = {}
|
55
|
+
sname = None
|
56
|
+
start_logging()
|
57
|
+
while True:
|
58
|
+
#Wait for data in the shared array
|
59
|
+
while monitor_shared_arr[0] == b'\x00':
|
60
|
+
time.sleep(0.005)
|
61
|
+
if timer is not None:
|
62
|
+
timer -= monitor_tick
|
63
|
+
if timer < 0:
|
64
|
+
timer = None
|
65
|
+
|
66
|
+
arr = list(session_status.items())
|
67
|
+
arr.sort(key = lambda s: s[1][1], reverse=True)
|
68
|
+
ct = time.time()
|
69
|
+
message = "Hangout is detected! Sessions in a queue and time waiting:" +\
|
70
|
+
''.join(f'\n {s[0]}, {s[1][0]}, {ct - s[1][1]} s' for s in arr)
|
71
|
+
with logging_lock:
|
72
|
+
logging.warning(message)
|
73
|
+
timer = None
|
74
|
+
|
75
|
+
# Read and process the data
|
76
|
+
status = read_string_from(monitor_shared_arr).split(splitter)
|
77
|
+
#free
|
78
|
+
monitor_shared_arr[0] = b'\x00'
|
79
|
+
sname = status[1]
|
80
|
+
match status[0]:
|
81
|
+
case '+' | 'e': #exit external process
|
82
|
+
session_status[sname] = [status[2], time.time()]
|
83
|
+
timer = froze_time
|
84
|
+
case '-':
|
85
|
+
event, tstart = session_status.get(sname, (None, 0))
|
86
|
+
if event:
|
87
|
+
duration = time.time() - tstart
|
88
|
+
if profile and duration > profile:
|
89
|
+
with logging_lock:
|
90
|
+
logging.warning(f'Event handler {event} was executed for {duration} seconds!')
|
91
|
+
del session_status[sname]
|
92
|
+
timer = None
|
93
|
+
case 'p': #call external process
|
94
|
+
session_status[sname] = [status[2], time.time()]
|
95
|
+
timer = None
|
96
|
+
|
97
|
+
if froze_time or profile:
|
98
|
+
# Create a shared memory array
|
99
|
+
monitor_shared_arr = multiprocessing.Array('c', 200)
|
100
|
+
monitor_shared_arr[0] != b'\x00'
|
101
|
+
|
102
|
+
async def notify_monitor(status, session, event):
|
103
|
+
s = f'{status}{splitter}{session}{splitter}{event}'
|
104
|
+
# Wait for the shared array to be empty
|
105
|
+
while monitor_shared_arr[0] != b'\x00':
|
106
|
+
await asyncio.sleep(monitor_tick)
|
107
|
+
write_string_to(monitor_shared_arr, s)
|
108
|
+
|
109
|
+
monitor_process = multiprocessing.Process(target=monitor_process, args=(monitor_shared_arr,))
|
110
|
+
monitor_process.start()
|
111
|
+
else:
|
112
|
+
notify_monitor = None
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
|
unisi/server.py
CHANGED
@@ -80,31 +80,18 @@ async def websocket_handler(request):
|
|
80
80
|
if message:
|
81
81
|
if recorder.record_file:
|
82
82
|
recorder.accept(message, user.prepare_result (result))
|
83
|
-
await user.reflect(message, result)
|
84
|
-
""" if user.reflections and not is_screen_switch(message):
|
85
|
-
if result:
|
86
|
-
await broadcast(result, user)
|
87
|
-
msg_object = user.find_element(message)
|
88
|
-
if not isinstance(result, Message) or not result.contains(msg_object):
|
89
|
-
await broadcast(toJson(user.prepare_result(msg_object)), user) """
|
83
|
+
await user.reflect(message, result)
|
90
84
|
elif msg.type == WSMsgType.ERROR:
|
91
85
|
user.log('ws connection closed with exception %s' % ws.exception())
|
92
|
-
except:
|
93
|
-
|
86
|
+
except BaseException as e:
|
87
|
+
if not isinstance(e, ConnectionResetError):
|
88
|
+
user.log(traceback.format_exc())
|
94
89
|
|
95
|
-
|
96
|
-
if uss and uss.get(user.session):
|
97
|
-
del uss[user.session]
|
98
|
-
|
99
|
-
if user.reflections: #reflections is common array
|
100
|
-
if len(user.reflections) == 2:
|
101
|
-
user.reflections.clear() #1 element in user.reflections has no sense
|
102
|
-
else:
|
103
|
-
user.reflections.remove(user)
|
90
|
+
await user.delete()
|
104
91
|
return ws #?<->
|
105
92
|
|
106
93
|
def start(appname = None, user_type = User, http_handlers = []):
|
107
|
-
if appname
|
94
|
+
if appname:
|
108
95
|
config.appname = appname
|
109
96
|
|
110
97
|
User.type = user_type
|
unisi/users.py
CHANGED
@@ -2,6 +2,7 @@ from .utils import *
|
|
2
2
|
from .guielements import *
|
3
3
|
from .common import *
|
4
4
|
from .containers import Dialog, Screen
|
5
|
+
from .multimon import notify_monitor, logging_lock, run_external_process
|
5
6
|
import sys, asyncio, logging, importlib
|
6
7
|
|
7
8
|
class User:
|
@@ -25,13 +26,21 @@ class User:
|
|
25
26
|
self.screens = []
|
26
27
|
self.reflections = []
|
27
28
|
self.screen_module = None
|
28
|
-
self.__handlers__ = {}
|
29
|
+
self.__handlers__ = {}
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
self.monitor(session, share)
|
32
|
+
|
33
|
+
async def run_process(self, long_running_task, *args, callback = False):
|
34
|
+
if callback and notify_monitor and callback != self.progress: #progress notifies the monitor
|
35
|
+
async def new_callback(value):
|
36
|
+
asyncio.gather(notify_monitor('e', self.session, self.last_message), callback(value))
|
37
|
+
callback = new_callback
|
38
|
+
return await run_external_process(long_running_task, *args, callback = callback)
|
32
39
|
|
33
40
|
async def broadcast(self, message):
|
34
41
|
screen = self.screen_module
|
42
|
+
if type(message) != str:
|
43
|
+
message = toJson(self.prepare_result(message))
|
35
44
|
await asyncio.gather(*[user.send(message)
|
36
45
|
for user in self.reflections
|
37
46
|
if user is not self and screen is user.screen_module])
|
@@ -43,13 +52,15 @@ class User:
|
|
43
52
|
if message:
|
44
53
|
msg_object = self.find_element(message)
|
45
54
|
if not isinstance(result, Message) or not result.contains(msg_object):
|
46
|
-
await self.broadcast(
|
55
|
+
await self.broadcast(msg_object)
|
47
56
|
|
48
57
|
async def progress(self, str, *updates):
|
49
58
|
"""open or update progress window if str != null else close it """
|
50
59
|
if not self.testing:
|
51
60
|
msg = TypeMessage('progress', str, *updates, user = self)
|
52
61
|
await asyncio.gather(self.send(msg), self.reflect(None, msg))
|
62
|
+
if notify_monitor:
|
63
|
+
await notify_monitor('e', self.session, self.last_message)
|
53
64
|
|
54
65
|
def load_screen(self, file):
|
55
66
|
screen_vars = {
|
@@ -65,9 +76,8 @@ class User:
|
|
65
76
|
path = f'{screens_dir}{divpath}{file}'
|
66
77
|
spec = importlib.util.spec_from_file_location(name,path)
|
67
78
|
module = importlib.util.module_from_spec(spec)
|
79
|
+
module.user = self
|
68
80
|
|
69
|
-
module.user = self
|
70
|
-
|
71
81
|
spec.loader.exec_module(module)
|
72
82
|
screen = Screen(getattr(module, 'name', ''))
|
73
83
|
#set system vars
|
@@ -78,9 +88,22 @@ class User:
|
|
78
88
|
screen.toolbar += User.toolbar
|
79
89
|
else:
|
80
90
|
screen.toolbar = User.toolbar
|
81
|
-
|
82
|
-
module.screen = screen
|
91
|
+
module.screen = screen
|
83
92
|
return module
|
93
|
+
|
94
|
+
async def delete(self):
|
95
|
+
uss = User.sessions
|
96
|
+
if uss and uss.get(self.session):
|
97
|
+
del uss[self.session]
|
98
|
+
|
99
|
+
if self.reflections: #reflections is common array
|
100
|
+
if len(self.reflections) == 2:
|
101
|
+
self.reflections.clear() #1 element in user.reflections has no sense
|
102
|
+
else:
|
103
|
+
self.reflections.remove(self)
|
104
|
+
|
105
|
+
if notify_monitor:
|
106
|
+
await notify_monitor('-', self.session, self.last_message)
|
84
107
|
|
85
108
|
def set_clean(self):
|
86
109
|
#remove user modules from sys
|
@@ -99,7 +122,7 @@ class User:
|
|
99
122
|
self.screens.append(module)
|
100
123
|
|
101
124
|
if self.screens:
|
102
|
-
self.screens.sort(key=lambda s: s.
|
125
|
+
self.screens.sort(key=lambda s: s.order)
|
103
126
|
main = self.screens[0]
|
104
127
|
if 'prepare' in dir(main):
|
105
128
|
main.prepare()
|
@@ -115,7 +138,7 @@ class User:
|
|
115
138
|
|
116
139
|
@property
|
117
140
|
def testing(self):
|
118
|
-
return self.session ==
|
141
|
+
return self.session == testdir
|
119
142
|
|
120
143
|
@property
|
121
144
|
def screen(self):
|
@@ -125,16 +148,14 @@ class User:
|
|
125
148
|
return asyncio.run(self.process(ArgObject(block = 'root', element = None, value = name)))
|
126
149
|
|
127
150
|
async def result4message(self, message):
|
128
|
-
result = None
|
129
|
-
|
130
|
-
if dialog:
|
131
|
-
if message.element is None: #button pressed
|
151
|
+
result = None
|
152
|
+
self.last_message = message
|
153
|
+
if dialog := self.active_dialog:
|
154
|
+
if message.element is None: #dialog command button is pressed
|
132
155
|
self.active_dialog = None
|
133
156
|
if self.reflections:
|
134
|
-
await self.broadcast(TypeMessage('action', 'close'))
|
135
|
-
|
136
|
-
result = (await handler(dialog, message.value)) if asyncio.iscoroutinefunction(handler)\
|
137
|
-
else handler(dialog, message.value)
|
157
|
+
await self.broadcast(TypeMessage('action', 'close'))
|
158
|
+
result = await self.eval_handler(dialog.changed, dialog, message.value)
|
138
159
|
else:
|
139
160
|
el = self.find_element(message)
|
140
161
|
if el:
|
@@ -145,6 +166,15 @@ class User:
|
|
145
166
|
self.active_dialog = result
|
146
167
|
return result
|
147
168
|
|
169
|
+
async def eval_handler(self, handler, gui, value):
|
170
|
+
if notify_monitor:
|
171
|
+
await notify_monitor('+', self.session, self.last_message)
|
172
|
+
result = (await handler(gui, value)) if asyncio.iscoroutinefunction(handler)\
|
173
|
+
else handler(gui, value)
|
174
|
+
if notify_monitor:
|
175
|
+
await notify_monitor('-', self.session, None)
|
176
|
+
return result
|
177
|
+
|
148
178
|
@property
|
149
179
|
def blocks(self):
|
150
180
|
return [self.active_dialog] if self.active_dialog and \
|
@@ -188,8 +218,7 @@ class User:
|
|
188
218
|
raw = Message(*raw, user = self)
|
189
219
|
return raw
|
190
220
|
|
191
|
-
async def process(self, message):
|
192
|
-
self.last_message = message
|
221
|
+
async def process(self, message):
|
193
222
|
screen_change_message = getattr(message, 'screen',None) and self.screen.name != message.screen
|
194
223
|
if is_screen_switch(message) or screen_change_message:
|
195
224
|
for s in self.screens:
|
@@ -214,18 +243,16 @@ class User:
|
|
214
243
|
return Error(error)
|
215
244
|
|
216
245
|
async def process_element(self, elem, message):
|
217
|
-
event = message.event
|
246
|
+
event = message.event
|
218
247
|
query = event == 'complete' or event == 'append'
|
219
248
|
|
220
249
|
handler = self.__handlers__.get((elem, event), None)
|
221
250
|
if handler:
|
222
|
-
return
|
223
|
-
else handler(elem, message.value)
|
251
|
+
return await self.eval_handler(handler, elem, message.value)
|
224
252
|
|
225
253
|
handler = getattr(elem, event, False)
|
226
254
|
if handler:
|
227
|
-
result =
|
228
|
-
else handler(elem, message.value)
|
255
|
+
result = await self.eval_handler(handler, elem, message.value)
|
229
256
|
if query:
|
230
257
|
result = Answer(event, message, result)
|
231
258
|
return result
|
@@ -235,14 +262,27 @@ class User:
|
|
235
262
|
error = f"{message.block}/{message.element} doesn't contain '{event}' method type!"
|
236
263
|
self.log(error)
|
237
264
|
return Error(error)
|
265
|
+
|
266
|
+
def monitor(self, session, share):
|
267
|
+
if config.share and session != testdir:
|
268
|
+
self.log(f'User is connected, session: {session}, share: {share.session if share else None}', type = 'info')
|
269
|
+
|
270
|
+
def sync_send(self, obj):
|
271
|
+
asyncio.run(self.send(obj))
|
238
272
|
|
239
273
|
def log(self, str, type = 'error'):
|
240
274
|
scr = self.screen.name if self.screens else 'void'
|
241
275
|
str = f"session: {self.session}, screen: {scr}, message: {self.last_message}\n {str}"
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
276
|
+
with logging_lock:
|
277
|
+
if type == 'error':
|
278
|
+
logging.error(str)
|
279
|
+
elif type == 'warning':
|
280
|
+
logging.warning(str)
|
281
|
+
else:
|
282
|
+
func = logging.getLogger().setLevel
|
283
|
+
func(level = logging.INFO)
|
284
|
+
logging.info(str)
|
285
|
+
func(level = logging.WARNING)
|
246
286
|
|
247
287
|
User.type = User
|
248
288
|
User.last_user = None
|
@@ -250,15 +290,22 @@ User.toolbar = []
|
|
250
290
|
User.sessions = {}
|
251
291
|
User.count = 0
|
252
292
|
|
293
|
+
def context_user():
|
294
|
+
return context_object(User)
|
295
|
+
|
296
|
+
def context_screen():
|
297
|
+
user = context_user()
|
298
|
+
return user.screen if user else None
|
299
|
+
|
253
300
|
def make_user(request):
|
254
301
|
session = f'{request.remote}-{User.count}'
|
255
302
|
User.count += 1
|
256
|
-
requested_connect
|
257
|
-
if requested_connect:
|
303
|
+
if requested_connect := request.headers.get('session') if config.share else None:
|
258
304
|
user = User.sessions.get(requested_connect, None)
|
259
305
|
if not user:
|
260
306
|
error = f'Session id "{requested_connect}" is unknown. Connection refused!'
|
261
|
-
|
307
|
+
with logging_lock:
|
308
|
+
logging.error(error)
|
262
309
|
return None, Error(error)
|
263
310
|
user = User.type(session, user)
|
264
311
|
ok = user.screens
|
unisi/utils.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
import os, platform, requests
|
1
|
+
import os, platform, requests, inspect, logging
|
2
2
|
|
3
3
|
blocks_dir = 'blocks'
|
4
4
|
screens_dir = 'screens'
|
5
5
|
UpdateScreen = True
|
6
6
|
Redesign = 2
|
7
7
|
public_dirs = 'public_dirs'
|
8
|
+
testdir = 'autotest'
|
8
9
|
|
9
10
|
divpath = '\\' if platform.system() == 'Windows' else '/'
|
10
11
|
libpath = os.path.dirname(os.path.realpath(__file__))
|
@@ -26,6 +27,41 @@ appname = 'Unisi app'
|
|
26
27
|
import config
|
27
28
|
print("Config with default parameters is created!")
|
28
29
|
|
30
|
+
#setting config variables
|
31
|
+
defaults = {
|
32
|
+
testdir: False,
|
33
|
+
'appname' : 'Unisi app',
|
34
|
+
'upload_dir' : 'web',
|
35
|
+
'logfile': None,
|
36
|
+
'hot_reload' : False,
|
37
|
+
'mirror' : False,
|
38
|
+
'share' : False,
|
39
|
+
'profile' : 0,
|
40
|
+
'rag' : None,
|
41
|
+
'froze_time': None,
|
42
|
+
'monitor_tick' : 0.005,
|
43
|
+
'pool' : None
|
44
|
+
}
|
45
|
+
for param, value in defaults.items():
|
46
|
+
if not hasattr(config, param):
|
47
|
+
setattr(config, param, value)
|
48
|
+
#froze_time can not be 0
|
49
|
+
if config.froze_time == 0:
|
50
|
+
config.froze_time = None
|
51
|
+
|
52
|
+
def context_object(target_type):
|
53
|
+
"""
|
54
|
+
Finds the first argument of a specific type in the current function call stack.
|
55
|
+
"""
|
56
|
+
frame = inspect.currentframe()
|
57
|
+
while frame:
|
58
|
+
args, _, _, values = inspect.getargvalues(frame)
|
59
|
+
if args and isinstance(values[args[0]], target_type):
|
60
|
+
return values[args[0]]
|
61
|
+
# Move to the previous frame in the call stack
|
62
|
+
frame = frame.f_back
|
63
|
+
return None
|
64
|
+
|
29
65
|
def is_screen_switch(message):
|
30
66
|
return message and message.block == 'root' and message.element is None
|
31
67
|
|
@@ -105,4 +141,13 @@ def Answer(type, message, result):
|
|
105
141
|
ms.message = message
|
106
142
|
return ms
|
107
143
|
|
144
|
+
def start_logging():
|
145
|
+
format = "%(asctime)s - %(levelname)s - %(message)s"
|
146
|
+
logfile = config.logfile
|
147
|
+
handlers = [logging.FileHandler(logfile), logging.StreamHandler()] if logfile else []
|
148
|
+
logging.basicConfig(level = logging.WARNING, format = format, handlers = handlers)
|
149
|
+
|
150
|
+
start_logging()
|
151
|
+
|
152
|
+
|
108
153
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: unisi
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.12
|
4
4
|
Summary: Unified System Interface, GUI and Remote API
|
5
5
|
Author-Email: UNISI Tech <g.dernovoy@gmail.com>
|
6
6
|
License: Apache-2.0
|
@@ -16,7 +16,7 @@ Requires-Dist: websocket-client
|
|
16
16
|
Description-Content-Type: text/markdown
|
17
17
|
|
18
18
|
# UNISI #
|
19
|
-
UNified System Interface and
|
19
|
+
UNified System Interface, GUI and Remote API
|
20
20
|
|
21
21
|
### Purpose ###
|
22
22
|
UNISI technology provides a unified system interface and advanced program functionality, eliminating the need for front-end and most back-end programming. It automates common tasks, as well as unique ones, significantly reducing the necessity for manual programming and effort.
|
@@ -31,6 +31,7 @@ UNISI technology provides a unified system interface and advanced program functi
|
|
31
31
|
- Integral autotesting
|
32
32
|
- Protocol schema auto validation
|
33
33
|
- Shared sessions
|
34
|
+
- Monitoring and profiling
|
34
35
|
|
35
36
|
### Installing ###
|
36
37
|
```
|
@@ -92,10 +93,13 @@ Connect a browser to localhast:8000 which are by default and will see:
|
|
92
93
|
### Handling events ###
|
93
94
|
All handlers are functions which have a signature
|
94
95
|
```
|
95
|
-
def handler_x(gui_object, value_x)
|
96
|
+
def handler_x(gui_object, value_x) #or
|
97
|
+
async def handler_x(gui_object, value_x)
|
96
98
|
```
|
97
99
|
where gui_object is a Python object the user interacted with and value for the event.
|
98
100
|
|
101
|
+
#### UNISI supports synchronous and asynchronous handlers automatically adopting them for using. ####
|
102
|
+
|
99
103
|
All Gui objects except Button have a field ‘value’.
|
100
104
|
For an edit field the value is a string or number, for a switch or check button the value is boolean, for table is row id or index, e.t.c.
|
101
105
|
When a user changes the value of the Gui object or presses Button, the server calls the ‘changed’ function handler.
|
@@ -113,10 +117,9 @@ clean_button = Button('Clean the table’, clean_table)
|
|
113
117
|
| Gui object array or tuple | Objects to update |
|
114
118
|
| None | Nothing to update, Ok |
|
115
119
|
| Error(...), Warning(...), Info(...) | Show to user info about a state. |
|
116
|
-
| True |
|
117
|
-
|
120
|
+
| True | Update whole screen |
|
121
|
+
| Redesign | Update and redesign whole screen |
|
118
122
|
| Dialog(..) | Open a dialog with parameters |
|
119
|
-
| user.set_screen(screen_name) | switch to another screen |
|
120
123
|
|
121
124
|
Unisi synchronizes GUI state on frontend-end automatically after calling a handler.
|
122
125
|
|
@@ -408,16 +411,16 @@ Error(error_message, *someGUIforUpdades)
|
|
408
411
|
```
|
409
412
|
They are returned by handlers and cause appearing on the top screen colored rectangles window for 3 second. someGUIforUpdades is optional GUI enumeration for updating.
|
410
413
|
|
411
|
-
For long time processes it is possible to create Progress window. It is just call user.progress in any
|
414
|
+
For long time processes it is possible to create Progress window. It is just call user.progress in any async handler.
|
412
415
|
Open window
|
413
416
|
```
|
414
|
-
user.progress("Analyze .. Wait..")
|
417
|
+
await user.progress("Analyze .. Wait..")
|
415
418
|
```
|
416
419
|
Update window message
|
417
420
|
```
|
418
|
-
user.progress(" 1% is done..")
|
421
|
+
await user.progress(" 1% is done..")
|
419
422
|
```
|
420
|
-
|
423
|
+
Progress window is automatically closed when the handler is finished.
|
421
424
|
|
422
425
|
### Milti-user support. ###
|
423
426
|
Unisi automatically creates and serves an environment for every user.
|
@@ -1,22 +1,23 @@
|
|
1
|
-
unisi-0.1.
|
2
|
-
unisi-0.1.
|
3
|
-
unisi-0.1.
|
4
|
-
unisi/__init__.py,sha256=
|
5
|
-
unisi/autotest.py,sha256=
|
6
|
-
unisi/common.py,sha256=
|
7
|
-
unisi/containers.py,sha256=
|
8
|
-
unisi/guielements.py,sha256=
|
1
|
+
unisi-0.1.12.dist-info/METADATA,sha256=qUwdMcBAUP61NnJq4uu87ynQgTfsnMJdBHPnnttD_vM,21624
|
2
|
+
unisi-0.1.12.dist-info/WHEEL,sha256=7sv5iXvIiTVJSnAxCz2tGBm9DHsb2vPSzeYeT7pvGUY,90
|
3
|
+
unisi-0.1.12.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
|
+
unisi/__init__.py,sha256=ZG79jkpNc3wvOYbO058Jh850ozD4rswMuPXwVPvcKy8,205
|
5
|
+
unisi/autotest.py,sha256=oYDYRc5gF01lYF2q1a8EFjz0pz9IsedLmG_9Y1op2_A,8731
|
6
|
+
unisi/common.py,sha256=D1khg0Dkvyn3Zi9bK2ZZQpjXmvOotNGDjJdzRiwZ6XY,624
|
7
|
+
unisi/containers.py,sha256=gY6oV3Wz35CZ7vrlEvfJVsTuJ3bgGAqLFItkX2Oiiuo,4165
|
8
|
+
unisi/guielements.py,sha256=d8g5UKPmqGIDwVRhikTrSW3NvysMqI3R4ti0BeHf5Oo,5955
|
9
9
|
unisi/jsoncomparison/__init__.py,sha256=lsWkYEuL6v3Qol-lwSUvB6y63tm6AKcCMUd4DZDx3Cg,350
|
10
10
|
unisi/jsoncomparison/compare.py,sha256=qPDaxd9n0GgiNd2SgrcRWvRDoXGg7NStViP9PHk2tlQ,6152
|
11
11
|
unisi/jsoncomparison/config.py,sha256=LbdLJE1KIebFq_tX7zcERhPvopKhnzcTqMCnS3jN124,381
|
12
12
|
unisi/jsoncomparison/errors.py,sha256=wqphE1Xn7K6n16uvUhDC45m2BxbsMUhIF2olPbhqf4o,1192
|
13
13
|
unisi/jsoncomparison/ignore.py,sha256=xfF0a_BBEyGdZBoq-ovpCpawgcX8SRwwp7IrGnu1c2w,2634
|
14
|
+
unisi/multimon.py,sha256=NJhSXmLp1fnDuaAa6-PCUSPCEjl_vf6TFfQRLQHklFI,4200
|
14
15
|
unisi/proxy.py,sha256=meZ4-26KXefuaZ04Gfrexij7rreBPKLPJSwPCIqftx0,7660
|
15
16
|
unisi/reloader.py,sha256=B0f9GU452epFvdih8sH7_NiIJrXnC5i27uw2imGQxMg,6585
|
16
|
-
unisi/server.py,sha256=
|
17
|
+
unisi/server.py,sha256=tAbDjVqOHRbRVR2JcSt--ctuywKduWiOOCHJwIFSa7M,4118
|
17
18
|
unisi/tables.py,sha256=v7Fio5iIN7u1t7cE4Yvl4ewn7jTmmNPyWigoKW1Mj8U,4239
|
18
|
-
unisi/users.py,sha256=
|
19
|
-
unisi/utils.py,sha256=
|
19
|
+
unisi/users.py,sha256=tznpwkmTb89EtZe86t_nPgACAnwjcrFKg7nPt9qHd2U,13117
|
20
|
+
unisi/utils.py,sha256=Sxu3kpIpddsVLUjz2c0GjxwyehV6qGkX1u8kyueLtLo,4260
|
20
21
|
unisi/web/css/662.64dbc68c.css,sha256=ckeNz4l2QkIp60LdV_-cEXf9orp0ZVklEbrQfjkAG5M,2691
|
21
22
|
unisi/web/css/app.31d6cfe0.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
23
|
unisi/web/css/vendor.9ed7638d.css,sha256=p_6HvcTaHu2zmOmfGxEGiGu5TIFZ75_XKHJZWA0eGCE,220597
|
@@ -39,4 +40,4 @@ unisi/web/js/430.591e9a73.js,sha256=7S1CJTwGdE2EKXzeFRcaYuTrn0QteoVI03Hykz8qh3s,
|
|
39
40
|
unisi/web/js/662.5957b792.js,sha256=9zsZsZXeiTYgvB6RbF7guuqOXcb5DAQp31Djl52Y-ow,56727
|
40
41
|
unisi/web/js/app.636fcf8d.js,sha256=THmpgdBjzB_6O3LqK0biyFfeqKg3UNwP32R7Tu7oifE,5923
|
41
42
|
unisi/web/js/vendor.d6797c01.js,sha256=2aKM3Lfxc0pHSirrcUReL1LcvDYWnfeBj2c67XsSELk,1279477
|
42
|
-
unisi-0.1.
|
43
|
+
unisi-0.1.12.dist-info/RECORD,,
|
File without changes
|