unisi 0.2.6__py3-none-any.whl → 0.2.8__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
@@ -6,4 +6,5 @@ from .tables import *
6
6
  from .containers import *
7
7
  from .proxy import *
8
8
  from .dbunits import *
9
+ from .graphs import *
9
10
  from .kdb import Database, Dbtable
unisi/autotest.py CHANGED
@@ -96,6 +96,8 @@ test_name = Edit('Name test file', '', focus = True)
96
96
  rewrite = Switch('Overwrite existing', False, type = 'check')
97
97
 
98
98
  def button_clicked(_,__):
99
+ if not os.path.exists(testdir):
100
+ os.makedirs(testdir)
99
101
  test_name.value = User.last_user.screen.name
100
102
  test_name.complete = smart_complete(os.listdir(testdir))
101
103
  return Dialog('Create autotest..', ask_create_test, test_name, rewrite)
@@ -176,11 +178,7 @@ def check_module(module):
176
178
  errors.insert(0, f"\nErrors in screen {screen.name}, file name {module.__file__}:")
177
179
  return errors
178
180
 
179
- def run_tests():
180
- if not os.path.exists(testdir):
181
- os.makedirs(testdir)
182
- user = User.type(testdir)
183
- user.load()
181
+ def run_tests(user):
184
182
  errors = []
185
183
  for module in user.screens:
186
184
  errors += check_module(module)
@@ -200,8 +198,7 @@ def run_tests():
200
198
  if not test(file,user):
201
199
  ok = False
202
200
  if process and ok:
203
- print('\n-----Autotests successfully passed.-----\n')
204
- User.last_user = None
201
+ print('\n-----Autotests successfully passed.-----\n')
205
202
  User.toolbar.append(button)
206
203
 
207
204
 
unisi/common.py CHANGED
@@ -48,6 +48,12 @@ class ReceivedMessage(ArgObject):
48
48
  self.__dict__.update(kwargs)
49
49
  def __str__(self):
50
50
  return f'{self.block}/{self.element}->{self.event}({self.value})'
51
+ @property
52
+ def screen_type(self):
53
+ return self.block == 'root' and self.element is None
54
+ @property
55
+ def voice_type(self):
56
+ return self.block == 'voice' and self.element is None
51
57
 
52
58
  def toJson(obj):
53
59
  return jsonpickle.encode(obj,unpicklable = False)
@@ -147,6 +153,21 @@ def Answer(type, message, result):
147
153
  ms.message = message
148
154
  return ms
149
155
 
156
+ def delete_unit(units, name):
157
+ """Deletes a unit with the given name from a nested list of units.
158
+ Returns True if the unit was found and deleted, False otherwise.
159
+ """
160
+ for i in range(len(units)):
161
+ if isinstance(units[i], list | tuple):
162
+ if delete_unit(units[i], name):
163
+ if not units[i]: # if the sublist became empty after deletion
164
+ units.pop(i) # remove sublist also
165
+ return True
166
+ elif units[i].name == name:
167
+ units.pop(i)
168
+ return True
169
+ return False
170
+
150
171
 
151
172
 
152
173
 
unisi/containers.py CHANGED
@@ -1,186 +1,6 @@
1
1
  from .units import *
2
- from .tables import Table
3
- from .common import pretty4, flatten
2
+ from .common import pretty4, flatten, delete_unit
4
3
  from numbers import Number
5
- from collections import defaultdict
6
- from collections.abc import Iterable
7
-
8
- class Node:
9
- def __init__(self, name , color = '', size = 0, image = ''):
10
- if name:
11
- self.name = name
12
- if image:
13
- self.type = 'image'
14
- self.image = image
15
- else:
16
- self.type = ''
17
- if color:
18
- self.color = color
19
- if size:
20
- self.size = size
21
-
22
- class Edge:
23
- def __init__(self, source, target, name = '', color = '', size = 0, property = None):
24
- self.source = source
25
- self.target = target
26
- if name:
27
- self.name = name
28
- if color:
29
- self.color = color
30
- if size:
31
- self.size = size
32
- if property is not None:
33
- self.property = property
34
- def __str__(self):
35
- return f"Edge({self.source}->{self.target})"
36
- def __repr__(self):
37
- return f"Edge({self.source}->{self.target})"
38
-
39
- graph_default_value = {'nodes' : [], 'edges' : []}
40
-
41
- class Graph(Unit):
42
- '''has to contain nodes, edges, see Readme'''
43
- def __init__(self, name, *args, **kwargs):
44
- super().__init__(name, *args, **kwargs)
45
- set_defaults(self, dict(type ='graph', value = graph_default_value, nodes = [], edges = []))
46
-
47
- Topology = lambda: defaultdict(lambda: defaultdict(lambda: {}))
48
-
49
- def unit2image(unit):
50
- match unit:
51
- case Block():
52
- return 'https://img.icons8.com/fluency/48/object.png'
53
- case Button():
54
- return 'https://img.icons8.com/ios-filled/50/doorbell.png'
55
- case Edit() | Text():
56
- return 'https://img.icons8.com/fluency-systems-filled/50/123.png' if unit.type == 'number'\
57
- else 'https://img.icons8.com/sf-regular/48/abc.png'
58
- case Switch():
59
- return 'https://img.icons8.com/ios/50/toggle-on--v1.png'
60
- case TextArea():
61
- return 'https://img.icons8.com/color/48/align-cell-content-left.png'
62
- case Table():
63
- return 'https://img.icons8.com/color/48/day-view.png' if unit.type == 'table'\
64
- else 'https://img.icons8.com/ultraviolet/40/combo-chart.png'
65
- case Tree():
66
- return 'https://img.icons8.com/external-flatart-icons-outline-flatarticons/64/external-tree-nature-flatart-icons-outline-flatarticons-3.png'
67
- case Select():
68
- return 'https://img.icons8.com/cotton/64/list--v2.png'
69
- case Graph():
70
- return 'https://img.icons8.com/external-vitaliy-gorbachev-blue-vitaly-gorbachev/50/external-nodes-cryptocurrency-vitaliy-gorbachev-blue-vitaly-gorbachev.png'
71
- case Range():
72
- return 'https://img.icons8.com/ios/50/slider-control.png'
73
- case Unit():
74
- return 'https://img.icons8.com/ios-filled/50/link--v1.png'
75
- case _:
76
- return ''
77
-
78
- class Net(Graph):
79
- """Graph of Units"""
80
- def __init__(self, name, topology = Topology(), *args, **kwargs):
81
- super().__init__(name, *args, **kwargs)
82
- self.type = 'graph'
83
- self.topology = topology
84
- changed_handler = getattr(self, 'changed', None)
85
-
86
- def changed_converter(_, value):
87
- mark_changed = self._mark_changed
88
- self._mark_changed = None #turn off for 'value' diff reaction
89
- self._value = value
90
- narray = self._narray
91
- value = dict(nodes = [self._narray[i] for i in value['nodes']], edges =
92
- [Edge(narray[self._edges[i].source], narray[self._edges[i].target]) for i in value['edges']])
93
- if changed_handler:
94
- result = changed_handler(_, value)
95
- else:
96
- result = None
97
- self.value = value
98
- self._mark_changed = mark_changed #turn on
99
- return result
100
- self.changed = changed_converter
101
-
102
- def specific_changed_register(self, property = None, value = None):
103
- """ mark serial info as invalid """
104
- if property:
105
- if property.startswith('_'):
106
- return False
107
- else:
108
- self.delattr('_nodes')
109
- self.delattr('_value')
110
- return True
111
-
112
- def elements(self, stubs=True):
113
- if not hasattr(self, '_nodes'):
114
- self.__getstate__()
115
- return self.narray
116
-
117
- def __getstate__(self):
118
- if not hasattr(self, '_nodes'):
119
- nodes = []
120
- narray = []
121
- earray = []
122
- for sunit, links in self.topology.items():
123
- sindex = index_of(narray,sunit)
124
- if sindex == -1:
125
- sindex = len(narray)
126
- narray.append(sunit)
127
- nodes.append(Node(sunit.name, image = unit2image(sunit),
128
- color = 'white', size = 15))
129
- for dunit in links:
130
- dindex = index_of(narray,dunit)
131
- if dindex == -1:
132
- dindex = len(narray)
133
- narray.append(dunit)
134
- nodes.append(Node(dunit.name, image = unit2image(dunit),
135
- color = 'white', size = 15))
136
- earray.append(Edge(sindex, dindex))
137
- self._nodes = nodes
138
- self._edges = earray
139
- self._narray = narray
140
-
141
- if not hasattr(self, '_value'):
142
- self._value = dict(nodes = [index_of(self._narray,unit) for unit in self.value['nodes']],
143
- edges = [Edge(index_of(self._narray, e.source), index_of(self._narray, e.target)) for e in self.value['edges']])
144
- return dict(name = self.name, type = self.type, nodes = self._nodes, edges = self._edges, value = self._value)
145
-
146
- def make_topology(self, unit: Unit | Iterable):
147
- topo = Topology()
148
- def dive(unit):
149
- match unit:
150
- case Iterable():
151
- node = Unit('Union', type = 'union')
152
- for obj in unit:
153
- if obj:
154
- topo[node][dive(obj)] = {}
155
- return node
156
- case Block():
157
- for obj in unit.value:
158
- if obj:
159
- topo[unit][dive(obj)] = {}
160
- case _: ...
161
- return unit
162
- dive(unit)
163
- self.topology = topo
164
- self.specific_changed_register()
165
-
166
- class ContentScaler(Range):
167
- def __init__(self, *args, **kwargs):
168
- name = args[0] if args else 'Scale content'
169
- super().__init__(name, *args, **kwargs)
170
- if 'options' not in kwargs:
171
- self.options = [0.25, 3.0, 0.25]
172
- self.changed = self.scaler
173
-
174
- def scaler(self, _, val):
175
- prev = self.value
176
- elements = self.elements()
177
- self.value = val
178
- if elements:
179
- prev /= val
180
- for element in elements:
181
- element.width /= prev
182
- element.height /= prev
183
- return elements
184
4
 
185
5
  class Block(Unit):
186
6
  def __init__(self, name, *elems, **options):
@@ -213,7 +33,7 @@ class Block(Unit):
213
33
  elif isinstance(elem.llm, dict):
214
34
  if elem.type != 'table':
215
35
  raise AttributeError(f'{elem.name} llm parameter is a dictionary only for tables, not for {elem.type}!')
216
- elem.__llm_dependencies__ = {fld: (deps if isinstance(deps, list | bool) else [deps]) for fld, deps in elem.llm.items()}
36
+ elem._llm_dependencies = {fld: (deps if isinstance(deps, list | bool) else [deps]) for fld, deps in elem.llm.items()}
217
37
  elem.llm = True
218
38
  continue
219
39
  else:
@@ -222,12 +42,19 @@ class Block(Unit):
222
42
  elem.llm = exactly
223
43
  for dependency in dependencies:
224
44
  dependency.add_changed_handler(elem.emit)
225
- elem.__llm_dependencies__ = dependencies
45
+ elem._llm_dependencies = dependencies
226
46
  else:
227
47
  elem.llm = None
228
48
  print(f'Empty dependency list for llm calculation for {elem.name} {elem.type}!')
229
-
230
- self.set_reactivity(Unishare.context_user())
49
+
50
+ user = Unishare.context_user()
51
+ if hasattr(self,'closable'):
52
+ def close(*_):
53
+ delete_unit(user.screen.blocks, self.name)
54
+ return Redesign
55
+ self.close = close
56
+
57
+ self.set_reactivity(user)
231
58
 
232
59
  def set_reactivity(self, user, override = False):
233
60
  if user:
unisi/graphs.py ADDED
@@ -0,0 +1,168 @@
1
+ from .units import *
2
+ from .tables import Table
3
+ from .containers import Block
4
+ from collections import defaultdict
5
+ from collections.abc import Iterable
6
+
7
+ class Node:
8
+ def __init__(self, name , color = '', size = 0, image = ''):
9
+ if name:
10
+ self.name = name
11
+ if image:
12
+ self.type = 'image'
13
+ self.image = image
14
+ else:
15
+ self.type = ''
16
+ if color:
17
+ self.color = color
18
+ if size:
19
+ self.size = size
20
+
21
+ class Edge:
22
+ def __init__(self, source, target, name = '', color = '', size = 0, property = None):
23
+ self.source = source
24
+ self.target = target
25
+ if name:
26
+ self.name = name
27
+ if color:
28
+ self.color = color
29
+ if size:
30
+ self.size = size
31
+ if property is not None:
32
+ self.property = property
33
+ def __str__(self):
34
+ return f"Edge({self.source}->{self.target})"
35
+ def __repr__(self):
36
+ return f"Edge({self.source}->{self.target})"
37
+
38
+ graph_default_value = {'nodes' : [], 'edges' : []}
39
+
40
+ class Graph(Unit):
41
+ '''has to contain nodes, edges, see Readme'''
42
+ def __init__(self, name, *args, **kwargs):
43
+ super().__init__(name, *args, **kwargs)
44
+ set_defaults(self, dict(type ='graph', value = graph_default_value, nodes = [], edges = []))
45
+
46
+ Topology = lambda: defaultdict(lambda: defaultdict(lambda: None))
47
+
48
+ def unit2image(unit):
49
+ match unit:
50
+ case Block():
51
+ return 'https://img.icons8.com/fluency/48/object.png'
52
+ case Button():
53
+ return 'https://img.icons8.com/ios-filled/50/doorbell.png'
54
+ case Edit() | Text():
55
+ return 'https://img.icons8.com/fluency-systems-filled/50/123.png' if unit.type == 'number'\
56
+ else 'https://img.icons8.com/sf-regular/48/abc.png'
57
+ case Switch():
58
+ return 'https://img.icons8.com/ios/50/toggle-on--v1.png'
59
+ case TextArea():
60
+ return 'https://img.icons8.com/color/48/align-cell-content-left.png'
61
+ case Table():
62
+ return 'https://img.icons8.com/color/48/day-view.png' if unit.type == 'table'\
63
+ else 'https://img.icons8.com/ultraviolet/40/combo-chart.png'
64
+ case Tree():
65
+ return 'https://img.icons8.com/external-flatart-icons-outline-flatarticons/64/external-tree-nature-flatart-icons-outline-flatarticons-3.png'
66
+ case Select():
67
+ return 'https://img.icons8.com/cotton/64/list--v2.png'
68
+ case Graph():
69
+ return 'https://img.icons8.com/external-vitaliy-gorbachev-blue-vitaly-gorbachev/50/external-nodes-cryptocurrency-vitaliy-gorbachev-blue-vitaly-gorbachev.png'
70
+ case Range():
71
+ return 'https://img.icons8.com/ios/50/slider-control.png'
72
+ case Unit():
73
+ return 'https://img.icons8.com/ios-filled/50/link--v1.png'
74
+ case _:
75
+ return ''
76
+
77
+ class Net(Graph):
78
+ """Graph of Units"""
79
+ replace4state = dict(nodes = '_nodes', edges = '_edges', value = '_value')
80
+ def __init__(self, name, value = graph_default_value, topology = Topology(), **kwargs):
81
+ Unit.__init__(self, name, **kwargs)
82
+ self.type = 'graph'
83
+ self.value = value
84
+ self.topology = topology
85
+ self._inside_converter = False
86
+ changed_handler = getattr(self, 'changed', None)
87
+
88
+ def changed_converter(_, value):
89
+ self._inside_converter = True
90
+ self._value = value
91
+ narray = self._narray
92
+ value = dict(nodes = [self._narray[i] for i in value['nodes']], edges =
93
+ [Edge(narray[self._edges[i].source], narray[self._edges[i].target]) for i in value['edges']])
94
+ if changed_handler:
95
+ result = changed_handler(_, value)
96
+ else:
97
+ result = None
98
+ self.value = value
99
+ self._inside_converter = False
100
+ return result
101
+ self.changed = changed_converter
102
+
103
+ def specific_changed_register(self, property = None, value = None):
104
+ """ mark serial info as invalid """
105
+ if property:
106
+ if property.startswith('_'):
107
+ return False
108
+ elif property == 'value' and self._inside_converter:
109
+ return False
110
+ else:
111
+ self.delattr('_nodes')
112
+ self.delattr('_value')
113
+ return True
114
+
115
+ def elements(self, stubs=True):
116
+ if not hasattr(self, '_nodes'):
117
+ self.__getstate__()
118
+ return self.narray
119
+
120
+ def __getstate__(self):
121
+ if not hasattr(self, '_nodes'):
122
+ nodes = []
123
+ narray = []
124
+ earray = []
125
+ for sunit, links in self.topology.items():
126
+ sindex = index_of(narray,sunit)
127
+ if sindex == -1:
128
+ sindex = len(narray)
129
+ narray.append(sunit)
130
+ nodes.append(Node(sunit.name, image = unit2image(sunit),
131
+ color = 'white', size = 15))
132
+ for dunit in links:
133
+ dindex = index_of(narray,dunit)
134
+ if dindex == -1:
135
+ dindex = len(narray)
136
+ narray.append(dunit)
137
+ nodes.append(Node(dunit.name, image = unit2image(dunit),
138
+ color = 'white', size = 15))
139
+ earray.append(Edge(sindex, dindex))
140
+ self._nodes = nodes
141
+ self._edges = earray
142
+ self._narray = narray
143
+
144
+ if not hasattr(self, '_value'):
145
+ self._value = dict(nodes = [index_of(self._narray,unit) for unit in self.value['nodes']],
146
+ edges = [Edge(index_of(self._narray, e.source), index_of(self._narray, e.target)) for e in self.value['edges']])
147
+ return {name: getattr(self,Net.replace4state.get(name, name)) for name in ['nodes', 'edges', *self.__dict__.keys()]
148
+ if name != 'topology' and name[0] != '_'}
149
+
150
+ def make_topology(self, unit: Unit | Iterable):
151
+ topo = Topology()
152
+ def dive(unit):
153
+ match unit:
154
+ case Iterable():
155
+ node = Unit('Union', type = 'union')
156
+ for obj in unit:
157
+ if obj:
158
+ topo[node][dive(obj)] = {}
159
+ return node
160
+ case Block():
161
+ for obj in unit.value:
162
+ if obj:
163
+ topo[unit][dive(obj)] = {}
164
+ case _: ...
165
+ return unit
166
+ dive(unit)
167
+ self.topology = topo
168
+ self.specific_changed_register()
unisi/server.py CHANGED
@@ -105,10 +105,8 @@ def start(appname = None, user_type = User, http_handlers = []):
105
105
  if appname:
106
106
  config.appname = appname
107
107
 
108
- User.type = user_type
109
-
110
- if config.autotest:
111
- run_tests()
108
+ User.type = user_type
109
+ run_tests(User.init_user())
112
110
 
113
111
  http_handlers.insert(0, web.get('/ws', websocket_handler))
114
112
  http_handlers += [web.static(f'/{config.upload_dir}', upload_dir),
unisi/tables.py CHANGED
@@ -199,7 +199,7 @@ class Table(Unit):
199
199
  tasks = []
200
200
  for index in self.selected_list:
201
201
  values = {field: value for field, value in zip(self.headers, self.rows[index]) if value}
202
- for fld, deps in self.__llm_dependencies__.items():
202
+ for fld, deps in self._llm_dependencies.items():
203
203
  if fld not in values:
204
204
  if deps is True:
205
205
  context = values
unisi/units.py CHANGED
@@ -112,9 +112,9 @@ class Unit:
112
112
  async def emit(self, *_ ):
113
113
  """calcute value by system llm, can be used as a handler"""
114
114
  if Unishare.llm_model and (exactly := getattr(self, 'llm', None)) is not None:
115
- elems = [e.compact_view for e in self.__llm_dependencies__ if e.value != '' and e.value is not None]
115
+ elems = [e.compact_view for e in self._llm_dependencies if e.value != '' and e.value is not None]
116
116
  #exactly is requirment that all elements have to have valid value
117
- if not exactly or len(elems) == len(self.__llm_dependencies__):
117
+ if not exactly or len(elems) == len(self._llm_dependencies):
118
118
  context = ','.join(elems)
119
119
  self.value = await get_property(self.name, context, self.type, options = getattr(self, 'options', None))
120
120
  return self
@@ -125,6 +125,9 @@ class Unit:
125
125
  def changed_handler(obj, value):
126
126
  obj.value = value
127
127
  self.changed = compose_handlers(changed_handler, handler)
128
+
129
+ def __getstate__(self):
130
+ return {n: v for n, v in self.__dict__.items() if n[0] != '_'}
128
131
 
129
132
  def __str__(self):
130
133
  return f'{type(self).__name__}({self.name})'
@@ -177,6 +180,25 @@ class Range(Unit):
177
180
  if 'options' not in kwargs:
178
181
  self.options = [self.value - 10, self.value + 10, 1]
179
182
 
183
+ class ContentScaler(Range):
184
+ def __init__(self, *args, **kwargs):
185
+ name = args[0] if args else 'Scale content'
186
+ super().__init__(name, *args, **kwargs)
187
+ if 'options' not in kwargs:
188
+ self.options = [0.25, 3.0, 0.25]
189
+ self.changed = self.scaler
190
+
191
+ def scaler(self, _, val):
192
+ prev = self.value
193
+ elements = self.elements()
194
+ self.value = val
195
+ if elements:
196
+ prev /= val
197
+ for element in elements:
198
+ element.width /= prev
199
+ element.height /= prev
200
+ return elements
201
+
180
202
  class Button(Unit):
181
203
  def __init__(self, name, handler = None, **kwargs):
182
204
  self._mark_changed = None
unisi/users.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from .utils import *
2
2
  from .units import *
3
3
  from .common import *
4
+ from .voicecom import VoiceCom
4
5
  from .containers import Dialog, Screen
5
6
  from .multimon import notify_monitor, logging_lock, run_external_process
6
7
  from .kdb import Database
@@ -17,6 +18,7 @@ class User:
17
18
  self.active_dialog = None
18
19
  self.last_message = None
19
20
  self.changed_units = set()
21
+ self.voice = None
20
22
 
21
23
  if share:
22
24
  self.screens = share.screens
@@ -53,7 +55,7 @@ class User:
53
55
  if user is not self and screen is user.screen_module])
54
56
 
55
57
  async def reflect(self, message, result):
56
- if self.reflections and not is_screen_switch(message):
58
+ if self.reflections and not message.screen_type:
57
59
  if result:
58
60
  await self.broadcast(result)
59
61
  if message:
@@ -197,6 +199,8 @@ class User:
197
199
  else:
198
200
  for bl in flatten(self.blocks):
199
201
  if bl.name == blname:
202
+ if not elname:
203
+ return bl
200
204
  for c in flatten(bl.value):
201
205
  if c.name == elname:
202
206
  return c
@@ -214,8 +218,9 @@ class User:
214
218
 
215
219
  def prepare_result(self, raw):
216
220
  if raw is True or raw == Redesign:
217
- raw = self.screen
218
- raw.reload = raw == Redesign
221
+ out = self.screen
222
+ out.reload = raw == Redesign
223
+ raw = out
219
224
  else:
220
225
  match raw:
221
226
  case None:
@@ -240,12 +245,15 @@ class User:
240
245
 
241
246
  async def process(self, message):
242
247
  screen_change_message = message.screen and self.screen.name != message.screen
243
- if screen_change_message or is_screen_switch(message):
248
+ if screen_change_message or message.screen_type:
244
249
  for s in self.screens:
245
250
  if s.name == message.value:
246
- self.screen_module = s
251
+ self.screen_module = s
247
252
  if screen_change_message:
248
253
  break
254
+ if self.voice:
255
+ self.voice.set_screen(s)
256
+ self.voice.start()
249
257
  if getattr(s.screen,'prepare', None):
250
258
  s.screen.prepare()
251
259
  return True
@@ -253,14 +261,24 @@ class User:
253
261
  error = f'Unknown screen name: {message.value}'
254
262
  self.log(error)
255
263
  return Error(error)
256
-
257
- elem = self.find_element(message)
258
- if elem:
259
- return await self.process_element(elem, message)
260
-
261
- error = f'Element {message.block}/{message.element} does not exist!'
262
- self.log(error)
263
- return Error(error)
264
+ elif message.voice_type:
265
+ created = False
266
+ if not self.voice:
267
+ self.voice = VoiceCom(self)
268
+ created = True
269
+ if message.event == 'listen':
270
+ return self.voice.start() if message.value else self.voice.stop()
271
+ else:
272
+ self.voice.input_word(message.value)
273
+ if created:
274
+ return Redesign
275
+ else:
276
+ elem = self.find_element(message)
277
+ if elem:
278
+ return await self.process_element(elem, message)
279
+ error = f'Element {message.block}/{message.element} does not exist!'
280
+ self.log(error)
281
+ return Error(error)
264
282
 
265
283
  async def process_element(self, elem, message):
266
284
  event = message.event
@@ -285,7 +303,7 @@ class User:
285
303
  self.log(error)
286
304
  return Error(error)
287
305
 
288
- def monitor(self, session, share):
306
+ def monitor(self, session, share = None):
289
307
  if config.share and session != testdir:
290
308
  self.log(f'User is connected, session: {session}, share: {share.session if share else None}', type = 'info')
291
309
 
@@ -306,6 +324,14 @@ class User:
306
324
  logging.info(str)
307
325
  func(level = logging.WARNING)
308
326
 
327
+ def init_user():
328
+ """make initial user for autotest and evaluating dbsharing"""
329
+ user = User.type(testdir)
330
+ user.load()
331
+ #register shared db map once
332
+ user.calc_dbsharing()
333
+ return user
334
+
309
335
  def calc_dbsharing(self):
310
336
  """calc connections db and units"""
311
337
  dbshare.clear()
@@ -362,15 +388,18 @@ def make_user(request):
362
388
  return None, Error(error)
363
389
  user = User.type(session, user)
364
390
  ok = user.screens
365
- elif config.mirror and User.last_user:
391
+ elif config.mirror and User.count:
366
392
  user = User.type(session, User.last_user)
367
393
  ok = user.screens
394
+ elif not User.count:
395
+ user = User.last_user
396
+ user.session = session
397
+ user.monitor(session)
398
+ ok = True
368
399
  else:
369
400
  user = User.type(session)
370
401
  ok = user.load()
371
- #register shared db map once
372
- if not user.count:
373
- user.calc_dbsharing()
402
+
374
403
  User.count += 1
375
404
  Unishare.sessions[session] = user
376
405
  return user, ok