unisi 0.1.1__py3-none-any.whl → 0.1.3__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/autotest.py +66 -43
- unisi/jsoncomparison/__init__.py +21 -0
- unisi/jsoncomparison/compare.py +233 -0
- unisi/jsoncomparison/config.py +15 -0
- unisi/jsoncomparison/errors.py +53 -0
- unisi/jsoncomparison/ignore.py +91 -0
- unisi/proxy.py +2 -2
- unisi/reloader.py +2 -2
- unisi/users.py +1 -1
- {unisi-0.1.1.dist-info → unisi-0.1.3.dist-info}/METADATA +9 -10
- {unisi-0.1.1.dist-info → unisi-0.1.3.dist-info}/RECORD +13 -8
- {unisi-0.1.1.dist-info → unisi-0.1.3.dist-info}/WHEEL +0 -0
- {unisi-0.1.1.dist-info → unisi-0.1.3.dist-info}/licenses/LICENSE +0 -0
unisi/autotest.py
CHANGED
@@ -4,7 +4,7 @@ from .guielements import *
|
|
4
4
|
from .containers import Block, Dialog
|
5
5
|
from .users import User
|
6
6
|
from .common import *
|
7
|
-
from jsoncomparison import Compare, NO_DIFF
|
7
|
+
from .jsoncomparison import Compare, NO_DIFF
|
8
8
|
|
9
9
|
#setting config variables
|
10
10
|
testdir = 'autotest'
|
@@ -34,7 +34,7 @@ logfile = config.logfile
|
|
34
34
|
handlers = [logging.FileHandler(logfile), logging.StreamHandler()] if logfile else []
|
35
35
|
logging.basicConfig(level = logging.WARNING, format = format, handlers = handlers)
|
36
36
|
|
37
|
-
comparator = Compare().check
|
37
|
+
comparator = Compare(rules = {'toolbar': '*'}).check
|
38
38
|
|
39
39
|
def jsonString(obj):
|
40
40
|
pretty = config.pretty_print
|
@@ -44,15 +44,13 @@ class Recorder:
|
|
44
44
|
def __init__(self):
|
45
45
|
self.start(None)
|
46
46
|
|
47
|
-
def accept(self, msg, response):
|
48
|
-
if self.ignored_1message:
|
47
|
+
def accept(self, msg, response):
|
48
|
+
if not self.ignored_1message:
|
49
|
+
self.ignored_1message = True
|
50
|
+
else:
|
49
51
|
self.record_buffer.append(f"{jsonString(msg)},\
|
50
|
-
|
51
|
-
|
52
|
-
self.record_buffer.append(jsonString(ArgObject(block = 'root',
|
53
|
-
element = None, value = User.last_user.screen_module.name)))
|
54
|
-
self.ignored_1message = True
|
55
|
-
|
52
|
+
\n{'null' if response is None else jsonString(response)}\n")
|
53
|
+
|
56
54
|
def stop_recording(self, _, x):
|
57
55
|
button.spinner = None
|
58
56
|
button.changed = button_clicked
|
@@ -69,44 +67,69 @@ class Recorder:
|
|
69
67
|
Warning('Nothing to save!',button)
|
70
68
|
|
71
69
|
def start(self,fname):
|
72
|
-
self.record_file = fname
|
73
|
-
self.ignored_1message = False
|
70
|
+
self.record_file = fname
|
74
71
|
self.record_buffer = []
|
72
|
+
if fname:
|
73
|
+
self.ignored_1message = True
|
74
|
+
module = User.last_user.screen_module
|
75
|
+
self.accept(ArgObject(block = 'root', element = None,
|
76
|
+
event = 'changed', value = module.name), module.screen)
|
77
|
+
self.ignored_1message = False
|
75
78
|
|
76
79
|
recorder = Recorder()
|
77
80
|
|
78
|
-
def
|
81
|
+
def obj2json(obj):
|
79
82
|
return json.loads(jsonpickle.encode(obj,unpicklable=False))
|
80
83
|
|
81
84
|
def test(filename, user):
|
82
85
|
filepath = f'{testdir}{divpath}{filename}'
|
83
86
|
file = open(filepath, "r")
|
84
|
-
data = json.loads(file.read())
|
87
|
+
data = json.loads(file.read())
|
85
88
|
error = False
|
86
|
-
for
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
89
|
+
for i in range(0, len(data), 2):
|
90
|
+
message = data[i]
|
91
|
+
expected = data[i + 1]
|
92
|
+
|
93
|
+
result = user.result4message(ReceivedMessage(message))
|
94
|
+
responce = user.prepare_result(result)
|
95
|
+
jresponce = obj2json(responce)
|
96
|
+
|
97
|
+
diff = comparator(expected, jresponce)
|
98
|
+
if diff != NO_DIFF:
|
99
|
+
print(f"\nTest {filename} is failed on message {message}:")
|
100
|
+
err = diff.get('_message')
|
101
|
+
if err:
|
102
|
+
print(f" {err}")
|
103
|
+
else:
|
104
|
+
for key, obj in diff.items():
|
105
|
+
if key != '#name':
|
106
|
+
while True:
|
107
|
+
err = obj.get('_message')
|
108
|
+
if err:
|
109
|
+
print(f" {err} \n")
|
110
|
+
break
|
111
|
+
else:
|
112
|
+
content = obj.get('_content')
|
113
|
+
if content and len(obj) == 1:
|
114
|
+
obj = content
|
115
|
+
else:
|
116
|
+
for key, subobj in obj.items():
|
117
|
+
if key != '_content':
|
118
|
+
if isinstance(key, str):
|
119
|
+
name = obj.get('#name', '')
|
120
|
+
if name:
|
121
|
+
key = f' {name}: {key}'
|
122
|
+
print(f" {key}")
|
123
|
+
obj = subobj
|
124
|
+
break
|
125
|
+
error = True
|
103
126
|
return not error
|
104
127
|
|
105
128
|
test_name = Edit('Name test file', '', focus = True)
|
106
129
|
rewrite = Switch('Overwrite existing', False, type = 'check')
|
107
130
|
|
108
131
|
def button_clicked(_,__):
|
109
|
-
test_name.value =
|
132
|
+
test_name.value = User.last_user.screen.name
|
110
133
|
test_name.complete = smart_complete(os.listdir(testdir))
|
111
134
|
return Dialog('Create autotest..', ask_create_test, test_name, rewrite)
|
112
135
|
|
@@ -152,19 +175,19 @@ def check_block(self):
|
|
152
175
|
child_names.add(child.name)
|
153
176
|
return errors
|
154
177
|
|
155
|
-
def
|
156
|
-
|
178
|
+
def check_module(module):
|
179
|
+
screen = module.screen
|
157
180
|
errors = []
|
158
181
|
block_names = set()
|
159
|
-
if not hasattr(
|
182
|
+
if not hasattr(screen, 'name') or not screen.name:
|
160
183
|
errors.append(f"Screen file {module.__file__} does not contain name!")
|
161
|
-
|
162
|
-
elif not isinstance(
|
163
|
-
errors.append(f"The name in screen file {module.__file__} {
|
164
|
-
if not isinstance(
|
184
|
+
screen.name = 'Unknown'
|
185
|
+
elif not isinstance(screen.name, str):
|
186
|
+
errors.append(f"The name in screen file {module.__file__} {screen.name} is not a string!")
|
187
|
+
if not isinstance(screen.blocks, list):
|
165
188
|
errors.append(f"Screen file {module.__file__} does not contain 'blocks' list!")
|
166
189
|
else:
|
167
|
-
for bl in flatten(
|
190
|
+
for bl in flatten(screen.blocks):
|
168
191
|
if not isinstance(bl, Block):
|
169
192
|
errors.append(f'The screen contains invalid element {bl} instead of Block object!')
|
170
193
|
elif bl.name in block_names:
|
@@ -173,7 +196,7 @@ def check_screen(module):
|
|
173
196
|
block_names.add(bl.name)
|
174
197
|
errors += check_block(bl)
|
175
198
|
if errors:
|
176
|
-
errors.insert(0, f"\nErrors in screen {
|
199
|
+
errors.insert(0, f"\nErrors in screen {screen.name}, file name {module.__file__}:")
|
177
200
|
return errors
|
178
201
|
|
179
202
|
def run_tests():
|
@@ -184,9 +207,9 @@ def run_tests():
|
|
184
207
|
user.session = 'autotest'
|
185
208
|
errors = []
|
186
209
|
for module in user.screens:
|
187
|
-
errors +=
|
210
|
+
errors += check_module(module)
|
188
211
|
if errors:
|
189
|
-
errors.insert(0, f'\n!!----
|
212
|
+
errors.insert(0, f'\n!!----Detected errors in screens:')
|
190
213
|
print('\n'.join(errors), '\n')
|
191
214
|
elif user.screens:
|
192
215
|
print(f'\n----The screen definitions are correct.-----\n')
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from .compare import NO_DIFF, Compare
|
2
|
+
from .errors import (
|
3
|
+
KeyNotExist,
|
4
|
+
LengthsNotEqual,
|
5
|
+
TypesNotEqual,
|
6
|
+
UnexpectedKey,
|
7
|
+
ValueNotFound,
|
8
|
+
ValuesNotEqual,
|
9
|
+
)
|
10
|
+
|
11
|
+
__all__ = (
|
12
|
+
"Compare",
|
13
|
+
"NO_DIFF",
|
14
|
+
|
15
|
+
"ValuesNotEqual",
|
16
|
+
"TypesNotEqual",
|
17
|
+
"KeyNotExist",
|
18
|
+
"ValueNotFound",
|
19
|
+
"LengthsNotEqual",
|
20
|
+
"UnexpectedKey",
|
21
|
+
)
|
@@ -0,0 +1,233 @@
|
|
1
|
+
import copy
|
2
|
+
import json
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from .config import Config
|
6
|
+
from .errors import (
|
7
|
+
KeyNotExist,
|
8
|
+
LengthsNotEqual,
|
9
|
+
TypesNotEqual,
|
10
|
+
UnexpectedKey,
|
11
|
+
ValueNotFound,
|
12
|
+
ValuesNotEqual,
|
13
|
+
)
|
14
|
+
from .ignore import Ignore
|
15
|
+
|
16
|
+
NO_DIFF: dict = {}
|
17
|
+
NO_RULES: dict = {}
|
18
|
+
|
19
|
+
DEFAULT_CONFIG = {
|
20
|
+
'output': {
|
21
|
+
'console': False,
|
22
|
+
'file': {
|
23
|
+
'allow_nan': True,
|
24
|
+
'ensure_ascii': True,
|
25
|
+
'indent': 4,
|
26
|
+
'name': None,
|
27
|
+
'skipkeys': True,
|
28
|
+
},
|
29
|
+
},
|
30
|
+
'types': {
|
31
|
+
'float': {
|
32
|
+
'allow_round': 2,
|
33
|
+
},
|
34
|
+
'list': {
|
35
|
+
'check_length': True,
|
36
|
+
},
|
37
|
+
},
|
38
|
+
}
|
39
|
+
|
40
|
+
|
41
|
+
class Compare:
|
42
|
+
|
43
|
+
__slots__ = ("_config", "_rules")
|
44
|
+
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
config: Optional[dict] = None,
|
48
|
+
rules: Optional[dict] = None,
|
49
|
+
):
|
50
|
+
if not config:
|
51
|
+
config = DEFAULT_CONFIG
|
52
|
+
if not rules:
|
53
|
+
rules = NO_RULES
|
54
|
+
|
55
|
+
self._config = Config(config)
|
56
|
+
self._rules = rules
|
57
|
+
|
58
|
+
def check(self, expected, actual):
|
59
|
+
e = self.prepare(expected)
|
60
|
+
a = self.prepare(actual)
|
61
|
+
diff = self._diff(e, a)
|
62
|
+
self.report(diff)
|
63
|
+
return diff
|
64
|
+
|
65
|
+
def _diff(self, e, a):
|
66
|
+
t = type(e)
|
67
|
+
if not isinstance(a, t):
|
68
|
+
return TypesNotEqual(e, a).explain()
|
69
|
+
if t is int:
|
70
|
+
return self._int_diff(e, a)
|
71
|
+
if t is str:
|
72
|
+
return self._str_diff(e, a)
|
73
|
+
if t is bool:
|
74
|
+
return self._bool_diff(e, a)
|
75
|
+
if t is float:
|
76
|
+
return self._float_diff(e, a)
|
77
|
+
if t is dict:
|
78
|
+
return self._dict_diff(e, a)
|
79
|
+
if t is list:
|
80
|
+
return self._list_diff(e, a)
|
81
|
+
return NO_DIFF
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def _int_diff(cls, e, a):
|
85
|
+
if a == e:
|
86
|
+
return NO_DIFF
|
87
|
+
return ValuesNotEqual(e, a).explain()
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def _bool_diff(cls, e, a):
|
91
|
+
if a is e:
|
92
|
+
return NO_DIFF
|
93
|
+
return ValuesNotEqual(e, a).explain()
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def _str_diff(cls, e, a):
|
97
|
+
if a == e:
|
98
|
+
return NO_DIFF
|
99
|
+
return ValuesNotEqual(e, a).explain()
|
100
|
+
|
101
|
+
def _float_diff(self, e, a):
|
102
|
+
if a == e:
|
103
|
+
return NO_DIFF
|
104
|
+
if self._can_rounded_float():
|
105
|
+
p = self._float_precision()
|
106
|
+
e, a = round(e, p), round(a, p)
|
107
|
+
if a == e:
|
108
|
+
return NO_DIFF
|
109
|
+
return ValuesNotEqual(e, a).explain()
|
110
|
+
|
111
|
+
def _can_rounded_float(self):
|
112
|
+
p = self._float_precision()
|
113
|
+
return type(p) is int
|
114
|
+
|
115
|
+
def _float_precision(self):
|
116
|
+
path = 'types.float.allow_round'
|
117
|
+
return self._config.get(path)
|
118
|
+
|
119
|
+
def _dict_diff(self, e, a):
|
120
|
+
if not isinstance(a, dict):
|
121
|
+
d = {'type' : {'_message': 'Incompatible types'}}
|
122
|
+
else:
|
123
|
+
d = {}
|
124
|
+
for k in e:
|
125
|
+
if k not in a:
|
126
|
+
d[k] = KeyNotExist(k, None).explain()
|
127
|
+
else:
|
128
|
+
d[k] = self._diff(e[k], a[k])
|
129
|
+
|
130
|
+
for k in a:
|
131
|
+
if k not in e:
|
132
|
+
d[k] = UnexpectedKey(None, k).explain()
|
133
|
+
else:
|
134
|
+
d[k] = self._diff(e[k], a[k])
|
135
|
+
|
136
|
+
diffs = self._without_empties(d)
|
137
|
+
if diffs:
|
138
|
+
name = e.get('name')
|
139
|
+
if name:
|
140
|
+
diffs['#name'] = name
|
141
|
+
return diffs
|
142
|
+
|
143
|
+
def _list_diff(self, e, a):
|
144
|
+
if not isinstance(a, list):
|
145
|
+
d = {'type' : {'_message': 'Incompatible types'}}
|
146
|
+
else:
|
147
|
+
d = {}
|
148
|
+
if self._need_compare_length():
|
149
|
+
d['_length'] = self._list_len_diff(e, a)
|
150
|
+
d['_content'] = self._list_content_diff(e, a)
|
151
|
+
return self._without_empties(d)
|
152
|
+
|
153
|
+
def _need_compare_length(self):
|
154
|
+
path = 'types.list.check_length'
|
155
|
+
return self._config.get(path) is True
|
156
|
+
|
157
|
+
def _list_content_diff(self, e, a):
|
158
|
+
d = {}
|
159
|
+
len_a = len(a)
|
160
|
+
for i, v in enumerate(e):
|
161
|
+
if i < len_a:
|
162
|
+
t = type(v)
|
163
|
+
if t in (int, str, bool, float):
|
164
|
+
if v != a[i]:
|
165
|
+
d[i] = ValuesNotEqual(v, a[i]).explain()
|
166
|
+
elif t is dict:
|
167
|
+
d[i] = self._dict_diff(v, a[i])
|
168
|
+
elif t is list:
|
169
|
+
d[i] = self._list_diff(v, a[i])
|
170
|
+
return self._without_empties(d)
|
171
|
+
|
172
|
+
@classmethod
|
173
|
+
def _max_diff(cls, e, lst, method):
|
174
|
+
t = type(e)
|
175
|
+
d = method(e, t())
|
176
|
+
for i, v in enumerate(lst):
|
177
|
+
if type(v) is t:
|
178
|
+
dd = method(e, v)
|
179
|
+
if len(dd) <= len(d):
|
180
|
+
d = dd
|
181
|
+
return d
|
182
|
+
|
183
|
+
@classmethod
|
184
|
+
def _min_diff(cls, e, lst, method):
|
185
|
+
t = type(e)
|
186
|
+
d = method(e, t())
|
187
|
+
for i, v in enumerate(lst):
|
188
|
+
if type(v) is t:
|
189
|
+
dd = method(e, v)
|
190
|
+
if len(dd) <= len(d):
|
191
|
+
d = dd
|
192
|
+
break
|
193
|
+
return d
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def _list_len_diff(cls, e, a):
|
197
|
+
e, a = len(e), len(a)
|
198
|
+
if e == a:
|
199
|
+
return NO_DIFF
|
200
|
+
return LengthsNotEqual(e, a).explain()
|
201
|
+
|
202
|
+
@classmethod
|
203
|
+
def _without_empties(cls, d):
|
204
|
+
return {k: d[k] for k in d if d[k] != NO_DIFF}
|
205
|
+
|
206
|
+
def report(self, diff):
|
207
|
+
if self._need_write_to_console():
|
208
|
+
self._write_to_console(diff)
|
209
|
+
if self._need_write_to_file():
|
210
|
+
self._write_to_file(diff)
|
211
|
+
|
212
|
+
@classmethod
|
213
|
+
def _write_to_console(cls, d):
|
214
|
+
msg = json.dumps(d, indent=4)
|
215
|
+
print(msg)
|
216
|
+
|
217
|
+
def _write_to_file(self, d):
|
218
|
+
config = self._config.get('output.file')
|
219
|
+
with open(config.pop('name'), 'w') as fp:
|
220
|
+
json.dump(d, fp, **config)
|
221
|
+
|
222
|
+
def _need_write_to_console(self):
|
223
|
+
path = 'output.console'
|
224
|
+
return self._config.get(path) is True
|
225
|
+
|
226
|
+
def _need_write_to_file(self):
|
227
|
+
path = 'output.file.name'
|
228
|
+
file_name = self._config.get(path)
|
229
|
+
return type(file_name) is str
|
230
|
+
|
231
|
+
def prepare(self, x):
|
232
|
+
x = copy.deepcopy(x)
|
233
|
+
return Ignore.transform(x, self._rules)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Config:
|
2
|
+
def __init__(self, config: dict):
|
3
|
+
self.config = config
|
4
|
+
|
5
|
+
def get(self, path):
|
6
|
+
value = self.config
|
7
|
+
for key in path.split('.'):
|
8
|
+
try:
|
9
|
+
value = value.get(key, {})
|
10
|
+
except AttributeError:
|
11
|
+
return False
|
12
|
+
return value
|
13
|
+
|
14
|
+
def merge(self, config):
|
15
|
+
self.config.update(config)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
|
3
|
+
|
4
|
+
class Error(ABC):
|
5
|
+
expected = None
|
6
|
+
received = None
|
7
|
+
|
8
|
+
template = 'Expected: <{e}>, received: <{r}>'
|
9
|
+
|
10
|
+
def __init__(self, expected, received):
|
11
|
+
self.expected = expected
|
12
|
+
self.received = received
|
13
|
+
|
14
|
+
@property
|
15
|
+
def message(self):
|
16
|
+
msg = self.template.format(e=self.expected, r=self.received)
|
17
|
+
return msg
|
18
|
+
|
19
|
+
def explain(self):
|
20
|
+
return {
|
21
|
+
'_message': self.message,
|
22
|
+
'_expected': self.expected,
|
23
|
+
'_received': self.received,
|
24
|
+
}
|
25
|
+
|
26
|
+
|
27
|
+
class TypesNotEqual(Error):
|
28
|
+
template = 'Types not equal. Expected: <{e}>, received: <{r}>'
|
29
|
+
|
30
|
+
def __init__(self, e, a):
|
31
|
+
e = type(e).__name__
|
32
|
+
a = type(a).__name__
|
33
|
+
super().__init__(e, a)
|
34
|
+
|
35
|
+
|
36
|
+
class ValuesNotEqual(Error):
|
37
|
+
template = 'Values not equal. Expected: <{e}>, received: <{r}>'
|
38
|
+
|
39
|
+
|
40
|
+
class KeyNotExist(Error):
|
41
|
+
template = 'Key does not exist. Expected: <{e}>'
|
42
|
+
|
43
|
+
|
44
|
+
class LengthsNotEqual(Error):
|
45
|
+
template = 'Lengths not equal. Expected <{e}>, received: <{r}>'
|
46
|
+
|
47
|
+
|
48
|
+
class ValueNotFound(Error):
|
49
|
+
template = 'Value not found. Expected <{e}>'
|
50
|
+
|
51
|
+
|
52
|
+
class UnexpectedKey(Error):
|
53
|
+
template = 'Unexpected key. Received: <{r}>'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import re
|
2
|
+
from abc import ABC
|
3
|
+
|
4
|
+
|
5
|
+
class Ignore(ABC):
|
6
|
+
|
7
|
+
@classmethod
|
8
|
+
def transform(cls, obj, rules):
|
9
|
+
t = type(rules)
|
10
|
+
if obj:
|
11
|
+
if t is dict:
|
12
|
+
return cls._apply_dictable_rule(obj, rules)
|
13
|
+
if t is list:
|
14
|
+
return cls._apply_listable_rule(obj, rules)
|
15
|
+
return obj
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def _apply_dictable_rule(cls, obj, rules):
|
19
|
+
for key in rules:
|
20
|
+
rule = rules[key]
|
21
|
+
if cls._is_special_key(key):
|
22
|
+
obj = cls._apply_special_rule(key, obj, rule)
|
23
|
+
elif cls._is_regex_rule(rule):
|
24
|
+
obj = cls._apply_regex_rule(key, obj, rule)
|
25
|
+
elif type(rule) is str:
|
26
|
+
obj = cls._apply_stringable_rule(key, obj, rule)
|
27
|
+
elif key in obj:
|
28
|
+
obj[key] = cls.transform(obj[key], rule)
|
29
|
+
return obj
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def _apply_listable_rule(cls, obj, rules):
|
33
|
+
for key in rules:
|
34
|
+
if type(key) is dict:
|
35
|
+
for index, y in enumerate(obj):
|
36
|
+
obj[index] = cls.transform(obj[index], key)
|
37
|
+
elif key in obj:
|
38
|
+
del obj[key]
|
39
|
+
return obj
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def _apply_stringable_rule(cls, key, obj, rule):
|
43
|
+
if rule == '*':
|
44
|
+
if key in obj:
|
45
|
+
del obj[key]
|
46
|
+
return obj
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def _is_regex_rule(cls, rule):
|
50
|
+
return type(rule) is dict and '_re' in rule
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def _apply_regex_rule(cls, key, obj, rule):
|
54
|
+
regex = rule['_re']
|
55
|
+
if key in obj and re.match(regex, obj[key]):
|
56
|
+
del obj[key]
|
57
|
+
return obj
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def _is_special_key(cls, key):
|
61
|
+
return key.startswith('_')
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def _apply_special_rule(cls, key, obj, rule):
|
65
|
+
if key == '_values':
|
66
|
+
return cls._ignore_values(obj, rule)
|
67
|
+
if key == '_list':
|
68
|
+
return cls._ignore_list_items(obj, rule)
|
69
|
+
if key == '_range':
|
70
|
+
return cls._ignore_range(obj, rule)
|
71
|
+
return obj
|
72
|
+
|
73
|
+
@classmethod
|
74
|
+
def _ignore_list_items(cls, obj, rule):
|
75
|
+
return [cls.transform(x, rule) for x in obj]
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def _ignore_values(cls, obj, black_list):
|
79
|
+
t = type(obj)
|
80
|
+
if t is list:
|
81
|
+
return [x for x in obj if x not in black_list]
|
82
|
+
if t is dict:
|
83
|
+
return {k: obj[k] for k in obj if k not in black_list}
|
84
|
+
return obj
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def _ignore_range(cls, obj, rule):
|
88
|
+
t = type(obj)
|
89
|
+
if t is int or t is float:
|
90
|
+
return rule[0] <= obj and obj <= rule[1]
|
91
|
+
return obj
|
unisi/proxy.py
CHANGED
@@ -40,7 +40,7 @@ class Proxy:
|
|
40
40
|
|
41
41
|
def close(self):
|
42
42
|
self.conn.close()
|
43
|
-
|
43
|
+
|
44
44
|
@property
|
45
45
|
def screen_menu(self):
|
46
46
|
return [name_icon[0] for name_icon in self.screen['menu']] if self.screen else []
|
@@ -66,7 +66,7 @@ class Proxy:
|
|
66
66
|
if el == element:
|
67
67
|
return block
|
68
68
|
|
69
|
-
def message(self, element, value, event = 'changed'):
|
69
|
+
def message(self, element, value = None, event = 'changed'):
|
70
70
|
if event != 'changed' and event not in element:
|
71
71
|
return None
|
72
72
|
return ArgObject(block = self.block_of(element), element = element['name'],
|
unisi/reloader.py
CHANGED
@@ -17,7 +17,7 @@ if config.hot_reload:
|
|
17
17
|
from watchdog.events import PatternMatchingEventHandler
|
18
18
|
from .users import User
|
19
19
|
from .utils import divpath, Redesign, app_dir
|
20
|
-
from .autotest import
|
20
|
+
from .autotest import check_module
|
21
21
|
import re, collections
|
22
22
|
|
23
23
|
#for removing message duplicates
|
@@ -48,7 +48,7 @@ if config.hot_reload:
|
|
48
48
|
|
49
49
|
try:
|
50
50
|
module = user.load_screen(sname)
|
51
|
-
errors =
|
51
|
+
errors = check_module(module)
|
52
52
|
if errors:
|
53
53
|
print('\n'.join(errors))
|
54
54
|
busy = False
|
unisi/users.py
CHANGED
@@ -175,7 +175,7 @@ class User:
|
|
175
175
|
|
176
176
|
def process(self, message):
|
177
177
|
self.last_message = message
|
178
|
-
screen_change_message = message
|
178
|
+
screen_change_message = getattr(message, 'screen',None) and self.screen.name != message.screen
|
179
179
|
if is_screen_switch(message) or screen_change_message:
|
180
180
|
for s in self.screens:
|
181
181
|
if s.name == message.value:
|
@@ -1,13 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: unisi
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: UNified System Interface, GUI and proxy
|
5
5
|
Author-Email: UNISI Tech <g.dernovoy@gmail.com>
|
6
6
|
License: Apache-2.0
|
7
7
|
Project-URL: Homepage, https://github.com/unisi-tech/unisi
|
8
8
|
Requires-Python: >=3.8
|
9
9
|
Requires-Dist: aiohttp
|
10
|
-
Requires-Dist: jsoncomparison
|
11
10
|
Requires-Dist: jsonpickle
|
12
11
|
Requires-Dist: pandas
|
13
12
|
Requires-Dist: requests
|
@@ -29,8 +28,9 @@ UNISI technology provides a unified system interface and advanced program functi
|
|
29
28
|
- Hot reloading and updating
|
30
29
|
- Integral autotesting
|
31
30
|
- Protocol schema auto validation
|
31
|
+
- Shared sessions
|
32
32
|
- Voice interaction (not released yet)
|
33
|
-
-
|
33
|
+
- Remote GUI interaction and pipelining (not released yet)
|
34
34
|
|
35
35
|
### Installing ###
|
36
36
|
```
|
@@ -87,7 +87,7 @@ unisi.start('Test app')
|
|
87
87
|
Unisi builds the interactive app for the code above.
|
88
88
|
Connect a browser to localhast:8000 which are by default and will see:
|
89
89
|
|
90
|
-

|
91
91
|
|
92
92
|
### Handling events ###
|
93
93
|
All handlers are functions which have a signature
|
@@ -112,7 +112,7 @@ clean_button = Button('Clean the table’, clean_table)
|
|
112
112
|
| Gui object | Object to update |
|
113
113
|
| Gui object array or tuple | Objects to update |
|
114
114
|
| None | Nothing to update, Ok |
|
115
|
-
| Error(...), Warning(...), Info(...) | Show to user info about a
|
115
|
+
| Error(...), Warning(...), Info(...) | Show to user info about a state. |
|
116
116
|
| UpdateScreen, True | Redraw whole screen |
|
117
117
|
| Dialog(..) | Open a dialog with parameters |
|
118
118
|
| user.set_screen(screen_name) | switch to another screen |
|
@@ -125,12 +125,11 @@ If a Gui object doesn't have 'changed' handler the object accepts incoming value
|
|
125
125
|
If 'value' is not acceptable instead of returning an object possible to return Error or Warning or Info. That functions can update a object list passed after the message argument.
|
126
126
|
|
127
127
|
```
|
128
|
-
def changed_range(
|
129
|
-
if value < 0.5 and value > 1.0:
|
130
|
-
|
131
|
-
return Error(f‘The value of {_.name} has to be > 0.5 and < 1.0!', _)
|
128
|
+
def changed_range(guirange, value):
|
129
|
+
if value < 0.5 and value > 1.0:
|
130
|
+
return Error(f‘The value of {guirange.name} has to be > 0.5 and < 1.0!', guirange)
|
132
131
|
#accept value othewise
|
133
|
-
|
132
|
+
guirange.value = value
|
134
133
|
|
135
134
|
edit = Edit('Range of involving', 0.6, changed_range, type = 'number')
|
136
135
|
```
|
@@ -1,16 +1,21 @@
|
|
1
|
-
unisi-0.1.
|
2
|
-
unisi-0.1.
|
3
|
-
unisi-0.1.
|
1
|
+
unisi-0.1.3.dist-info/METADATA,sha256=pYm__IDa4WqrhosAznrglBGaXw1Ryd-zIF821ajKGrc,18539
|
2
|
+
unisi-0.1.3.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
|
3
|
+
unisi-0.1.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
4
|
unisi/__init__.py,sha256=bQ7yTDcgybkbIGTjxkajTbBJXtgyryrCvt3nXg8QxlQ,175
|
5
|
-
unisi/autotest.py,sha256=
|
5
|
+
unisi/autotest.py,sha256=K_ev2UcUdMwt6YwK-ueOPavX6ikHylldIF81v05e95o,9397
|
6
6
|
unisi/common.py,sha256=oQJdUHVIf3QEMlnRSY_FW-ce4oDb7xcmzOhHByKGC7w,753
|
7
7
|
unisi/containers.py,sha256=_oB1KknoHAslpmVHY_dpX4uOcKtP5Pelqdv9fMjotHo,3403
|
8
8
|
unisi/guielements.py,sha256=SVfAU9zrGMdzuU3Ip0dAnaPyJSmwoOaozn3LLNW0tkQ,5384
|
9
|
-
unisi/
|
10
|
-
unisi/
|
9
|
+
unisi/jsoncomparison/__init__.py,sha256=lsWkYEuL6v3Qol-lwSUvB6y63tm6AKcCMUd4DZDx3Cg,350
|
10
|
+
unisi/jsoncomparison/compare.py,sha256=XlJqeNaj_zmiD-x_Rz_9R2RYkDd7JCOzojoFtIsrBVU,6158
|
11
|
+
unisi/jsoncomparison/config.py,sha256=LbdLJE1KIebFq_tX7zcERhPvopKhnzcTqMCnS3jN124,381
|
12
|
+
unisi/jsoncomparison/errors.py,sha256=wqphE1Xn7K6n16uvUhDC45m2BxbsMUhIF2olPbhqf4o,1192
|
13
|
+
unisi/jsoncomparison/ignore.py,sha256=xfF0a_BBEyGdZBoq-ovpCpawgcX8SRwwp7IrGnu1c2w,2634
|
14
|
+
unisi/proxy.py,sha256=bbuuOitToR-hmy5wQ6sqnJChze0ZuqKr1eHWyhGWXIY,5739
|
15
|
+
unisi/reloader.py,sha256=MkmN6mDclrdzuO5I4Hu_GGjOyl-icnJ-IdwyNW8xEYs,6424
|
11
16
|
unisi/server.py,sha256=SKN38DjuxZ0U_FjBJDuwnpDP2YfZorpIJrjrADhqf5w,4521
|
12
17
|
unisi/tables.py,sha256=v7Fio5iIN7u1t7cE4Yvl4ewn7jTmmNPyWigoKW1Mj8U,4239
|
13
|
-
unisi/users.py,sha256=
|
18
|
+
unisi/users.py,sha256=B3Ouj-Ygx9BYU64JtE_HM4yApIZ7ikVeH2v86Lr1rSA,9819
|
14
19
|
unisi/utils.py,sha256=KnyjouXGnN8ubuwogMa1tu2HJSkmDqhuFf7RpU3n9bY,2971
|
15
20
|
unisi/web/css/815.30ec41a2.css,sha256=b8La7chOFF3VPd-kqAbwP-Cx8jMzkgZCbYsMXR2mhKI,2723
|
16
21
|
unisi/web/css/app.31d6cfe0.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -34,4 +39,4 @@ unisi/web/js/430.591e9a73.js,sha256=7S1CJTwGdE2EKXzeFRcaYuTrn0QteoVI03Hykz8qh3s,
|
|
34
39
|
unisi/web/js/815.e46226ff.js,sha256=xpfhA0GvPjvracUep66rqrKLCho-Muiw1N8nr9QzDNc,55923
|
35
40
|
unisi/web/js/app.29d640d6.js,sha256=v_u_Q9vvmrgTj7-4CdpB8yQOl19a-FC6YVCXnSyTfec,5923
|
36
41
|
unisi/web/js/vendor.d6797c01.js,sha256=2aKM3Lfxc0pHSirrcUReL1LcvDYWnfeBj2c67XsSELk,1279477
|
37
|
-
unisi-0.1.
|
42
|
+
unisi-0.1.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|