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 CHANGED
@@ -1,6 +1,6 @@
1
1
  from .utils import *
2
2
  from .guielements import *
3
- from .users import User, handle
3
+ from .users import User, handle, context_user, context_screen
4
4
  from .server import start
5
5
  from .tables import *
6
6
  from .containers import *
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('autotest')
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
- for key, value in kwargs.items():
13
- setattr(self, key, value)
12
+ self.__dict__.update(kwargs)
14
13
 
15
14
  class ReceivedMessage:
16
15
  def __init__(self, data):
17
- self.screen = data.get('screen')
18
- self.block = data.get('block')
19
- self.element = data.get('element')
20
- self.event = data.get('event')
21
- self.value = data.get('value')
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
@@ -103,8 +103,7 @@ class Dialog:
103
103
 
104
104
  class Screen:
105
105
  def __init__(self, name, **kwargs):
106
- self.name = name
106
+ self.name = name
107
+ self.__dict__.update(kwargs)
107
108
  self.type = 'screen'
108
- for key, value in kwargs.items():
109
- setattr(self, key, value)
110
109
 
unisi/guielements.py CHANGED
@@ -9,8 +9,7 @@ class Gui:
9
9
  self.add(kwargs)
10
10
 
11
11
  def add(self, kwargs):
12
- for key, value in kwargs.items():
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(gui, ustr):
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
- user.log(traceback.format_exc())
86
+ except BaseException as e:
87
+ if not isinstance(e, ConnectionResetError):
88
+ user.log(traceback.format_exc())
94
89
 
95
- uss = User.sessions
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 is not None:
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
- def sync_send(self, obj):
31
- asyncio.run(self.send(obj))
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(toJson(self.prepare_result(msg_object)))
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.screen.order)
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 == 'autotest'
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
- dialog = self.active_dialog
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
- handler = dialog.changed
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 (await handler(elem, message.value)) if asyncio.iscoroutinefunction(handler)\
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 = (await handler(elem, message.value)) if asyncio.iscoroutinefunction(handler)\
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
- if type == 'error':
243
- logging.error(str)
244
- else:
245
- logging.warning(str)
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 = request.headers.get('session')
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
- logging.error(error)
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.11
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 GUI
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 | Redraw whole screen |
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 place.
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
- Progree window is automatically closed when the handler is finished.
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.11.dist-info/METADATA,sha256=jZe5qOclohUz6WKqpAxwXDXNRxyolhto6eBrHcGnh5I,21428
2
- unisi-0.1.11.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
3
- unisi-0.1.11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- unisi/__init__.py,sha256=bQ7yTDcgybkbIGTjxkajTbBJXtgyryrCvt3nXg8QxlQ,175
5
- unisi/autotest.py,sha256=QHNVBD4sMx9cn94RIbwQhwuX9H72hB69pZwS7yqhiZw,9487
6
- unisi/common.py,sha256=tdvHvEgZ5vzvLVjGGGIeZq5xHBYNBOydKjzDWoZWCSA,643
7
- unisi/containers.py,sha256=Me28aZkhchWJA5OExtXoFzl2u5bc6p0m2Cww7CfKIbA,4200
8
- unisi/guielements.py,sha256=svPMIEMbhW5uH4jz5rsjTeVVFl_P7VrMniTFFQvpmzs,5998
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=UKRm6ZUQb9gvlRMdH6EWtfih3B00uSvKGNj3QNl8TAw,4979
17
+ unisi/server.py,sha256=tAbDjVqOHRbRVR2JcSt--ctuywKduWiOOCHJwIFSa7M,4118
17
18
  unisi/tables.py,sha256=v7Fio5iIN7u1t7cE4Yvl4ewn7jTmmNPyWigoKW1Mj8U,4239
18
- unisi/users.py,sha256=dYNOx5X1Yvj6475zqqVKQjyAe1pHtKvm_rz_7Qgnp0Y,11100
19
- unisi/utils.py,sha256=hxwCCPZla1iSz9Y1CseDpE5K5JOdOyDlXtXuxpEAviw,2987
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.11.dist-info/RECORD,,
43
+ unisi-0.1.12.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.1.8)
2
+ Generator: pdm-backend (2.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any