QuLab 2.0.1__cp310-cp310-win_amd64.whl → 2.0.2__cp310-cp310-win_amd64.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.
@@ -0,0 +1,361 @@
1
+ import functools
2
+ import math
3
+ from pathlib import Path
4
+
5
+ import dill
6
+ import ipywidgets as widgets
7
+ import zmq
8
+ from IPython.display import display
9
+
10
+ from qulab.sys.rpc.zmq_socket import ZMQContextManager
11
+
12
+ from .recorder import Record
13
+
14
+
15
+ def get_record(id, database='tcp://127.0.0.1:6789'):
16
+ if isinstance(database, str) and database.startswith('tcp://'):
17
+ with ZMQContextManager(zmq.DEALER, connect=database) as socket:
18
+ socket.send_pyobj({
19
+ 'method': 'record_description',
20
+ 'record_id': id
21
+ })
22
+ d = dill.loads(socket.recv_pyobj())
23
+ print(d.keys())
24
+ return Record(id, database, d)
25
+ else:
26
+ from .models import Record as RecordInDB
27
+ from .models import create_engine, sessionmaker
28
+
29
+ db_file = Path(database) / 'data.db'
30
+ engine = create_engine(f'sqlite:///{db_file}')
31
+ Session = sessionmaker(bind=engine)
32
+ with Session() as session:
33
+ path = Path(database) / 'objects' / session.get(RecordInDB,
34
+ id).file
35
+ with open(path, 'rb') as f:
36
+ record = dill.load(f)
37
+ return record
38
+
39
+
40
+ def _format_tag(tag):
41
+ if tag.startswith('!'):
42
+ return f'<code style="color: white; background: red">{tag}</code>'
43
+ elif tag.startswith('?'):
44
+ return f'<code style="background: orange">{tag}</code>'
45
+ elif tag.startswith('@'):
46
+ return f'<code style="color: white; background: green">{tag}</code>'
47
+ elif tag.startswith('#'):
48
+ return f'<code style="color: white; background: blue">{tag}</code>'
49
+ elif tag.startswith('$'):
50
+ return f'<code style="color: white; background: purple">{tag}</code>'
51
+ elif tag.startswith('%'):
52
+ return f'<code style="color: white; background: brown">{tag}</code>'
53
+ else:
54
+ return f'<code>{tag}</code>'
55
+
56
+
57
+ def _format_value(title, value):
58
+ if title in ['ID', 'App']:
59
+ return str(value)
60
+ elif title in ['created time']:
61
+ return str(value).split('.')[0]
62
+ elif title in ['tags']:
63
+ tags = sorted(value)
64
+ if len(tags) <= 6:
65
+ tags = [_format_tag(t) for t in tags]
66
+ elif len(tags) <= 12:
67
+ tags = [_format_tag(t) for t in tags[:6]
68
+ ] + ['<br />'] + [_format_tag(t) for t in tags[6:]]
69
+ elif len(tags) <= 18:
70
+ tags = [_format_tag(t) for t in tags[:6]] + ['<br />'] + [
71
+ _format_tag(t) for t in tags[6:12]
72
+ ] + ['<br />'] + [_format_tag(t) for t in tags[12:]]
73
+ else:
74
+ tags = [_format_tag(t) for t in tags[:6]] + ['<br />'] + [
75
+ _format_tag(t) for t in tags[6:12]
76
+ ] + ['<br />'] + [_format_tag(t) for t in tags[12:17]] + ['...']
77
+ return ' '.join(tags)
78
+ else:
79
+ return repr(value)
80
+
81
+
82
+ def _format_table(table):
83
+ tb = [
84
+ """<div class="output_subarea output_markdown rendered_html" dir="auto">"""
85
+ """<table>"""
86
+ """<thead>"""
87
+ """<tr>"""
88
+ ]
89
+ for s in table['header']:
90
+ tb.append(f'<th style="text-align: center">{s}</th>')
91
+ tb.append("""</tr></thead><tbody>""")
92
+
93
+ for row in table["body"]:
94
+ r = ["<tr>"]
95
+ for k, v in zip(table['header'], row):
96
+ s = _format_value(k, v)
97
+ r.append(f'<td style="text-align: center">{s}</td>')
98
+ r.append("</tr>")
99
+ tb.append(''.join(r))
100
+ tb.append("""</tbody></table>"""
101
+ """</div>""")
102
+ return ''.join(tb)
103
+
104
+
105
+ class _QueryState():
106
+
107
+ def __init__(self):
108
+ self.table = None
109
+ self.apps = {}
110
+ self.page = 1
111
+ self.total = 0
112
+ self.app_prefix = []
113
+ self.app_name = '*'
114
+ self.records_per_page = 10
115
+
116
+ def list_apps(self, prefix=None):
117
+ common = ('..', '*') if prefix else ('*', )
118
+
119
+ if prefix is None:
120
+ prefix = self.app_prefix
121
+ d = self.apps
122
+ for p in prefix:
123
+ try:
124
+ d = d[p]
125
+ except:
126
+ return common
127
+ if isinstance(d, dict):
128
+ return common + tuple(sorted(d.keys()))
129
+ elif prefix:
130
+ return prefix[-1]
131
+ else:
132
+ return common + tuple(sorted(self.apps.keys()))
133
+
134
+
135
+ __query_state = _QueryState()
136
+
137
+
138
+ def _update_state(app_prefix,
139
+ app_name,
140
+ tags,
141
+ offset,
142
+ after,
143
+ before,
144
+ records_per_page,
145
+ database='tcp://127.0.0.1:6789'):
146
+
147
+ state = __query_state
148
+
149
+ app_pattern = '.'.join(app_prefix + [app_name])
150
+ if app_pattern == '*':
151
+ app_pattern = None
152
+
153
+ if app_name == '..':
154
+ app_prefix = app_prefix[:-1]
155
+ app_name = '*'
156
+ elif app_name == '*':
157
+ pass
158
+ else:
159
+ app_prefix = app_prefix + [app_name]
160
+ app_name = '*'
161
+
162
+ if isinstance(database, str) and database.startswith('tcp://'):
163
+ with ZMQContextManager(zmq.DEALER, connect=database) as socket:
164
+ socket.send_pyobj({
165
+ 'method': 'record_query',
166
+ 'app': app_pattern,
167
+ 'tags': tags,
168
+ 'offset': offset,
169
+ 'limit': records_per_page,
170
+ 'before': before,
171
+ 'after': after
172
+ })
173
+ total, apps, table = socket.recv_pyobj()
174
+ else:
175
+ from .curd import query_record
176
+ from .models import create_engine, sessionmaker
177
+
178
+ url = f'sqlite:///{database}/data.db'
179
+
180
+ engine = create_engine(url)
181
+ Session = sessionmaker(bind=engine)
182
+ with Session() as db:
183
+ total, apps, table = query_record(db,
184
+ offset=offset,
185
+ limit=records_per_page,
186
+ app=app_pattern,
187
+ tags=tags,
188
+ before=before,
189
+ after=after)
190
+ state.table = table
191
+ state.apps = apps
192
+ state.total = total
193
+ state.records_per_page = records_per_page
194
+ app_list = state.list_apps(app_prefix)
195
+ if isinstance(app_list, str):
196
+ state.app_name = app_prefix[-1]
197
+ else:
198
+ state.app_prefix = app_prefix
199
+ state.app_name = '*'
200
+ return total
201
+
202
+
203
+ def _update_view(ui_widgets):
204
+ state = __query_state
205
+
206
+ page = state.page
207
+ total = state.total
208
+ records_per_page = state.records_per_page
209
+
210
+ app_options = state.list_apps(state.app_prefix)
211
+ ui_widgets['app_prefix'].value = 'App:' + '.'.join(state.app_prefix)
212
+ handlers = ui_widgets['app']._trait_notifiers['value']['change']
213
+ ui_widgets['app']._trait_notifiers['value']['change'] = handlers[:1]
214
+ ui_widgets['app'].options = app_options
215
+ ui_widgets['app']._trait_notifiers['value']['change'] = handlers
216
+ ui_widgets['app'].value = state.app_name
217
+
218
+ offset = (page - 1) * records_per_page
219
+ if offset < records_per_page:
220
+ ui_widgets['bt_pre'].disabled = True
221
+ else:
222
+ ui_widgets['bt_pre'].disabled = False
223
+
224
+ ui_widgets['table'].value = _format_table(state.table)
225
+ if total - offset <= records_per_page:
226
+ ui_widgets['bt_next'].disabled = True
227
+ else:
228
+ ui_widgets['bt_next'].disabled = False
229
+ ui_widgets[
230
+ 'page_num_label'].value = f"{page} | {math.ceil(total/10)} pages"
231
+
232
+
233
+ def _get_query_params(state: _QueryState, ui_widgets: dict):
234
+ page = state.page
235
+ records_per_page = state.records_per_page
236
+ tags = [t.strip() for t in ui_widgets['tags'].value.split(',') if t]
237
+ offset = (page - 1) * records_per_page
238
+ after = ui_widgets['after'].value
239
+ before = ui_widgets['before'].value
240
+
241
+ if state.app_name == '..' and len(state.app_prefix) == 1:
242
+ state.app_prefix = []
243
+ state.app_name = '*'
244
+
245
+ return state.app_prefix, state.app_name, tags, offset, after, before, records_per_page
246
+
247
+
248
+ def _on_pre_bt_clicked(bt, ui_widgets):
249
+ __query_state.page -= 1
250
+ _update_state(*_get_query_params(__query_state, ui_widgets))
251
+ _update_view(ui_widgets)
252
+
253
+
254
+ def _on_next_bt_clicked(bt, ui_widgets):
255
+ __query_state.page += 1
256
+ _update_state(*_get_query_params(__query_state, ui_widgets))
257
+ _update_view(ui_widgets)
258
+
259
+
260
+ def _on_app_changed(changes, ui_widgets, stack=[]):
261
+ if changes['name'] not in ['value', 'index']:
262
+ stack.append(changes['name'])
263
+ return
264
+ if stack and changes['name'] == 'index':
265
+ stack.clear()
266
+ return
267
+
268
+ __query_state.app_name = ui_widgets['app'].value
269
+ __query_state.page = 1
270
+ _update_state(*_get_query_params(__query_state, ui_widgets))
271
+ _update_view(ui_widgets)
272
+
273
+
274
+ def _on_after_changed(changes, ui_widgets):
275
+ if changes['name'] != 'value':
276
+ return
277
+ __query_state.page = 1
278
+ _update_state(*_get_query_params(__query_state, ui_widgets))
279
+ _update_view(ui_widgets)
280
+
281
+
282
+ def _on_before_changed(changes, ui_widgets):
283
+ if changes['name'] != 'value':
284
+ return
285
+ __query_state.page = 1
286
+ _update_state(*_get_query_params(__query_state, ui_widgets))
287
+ _update_view(ui_widgets)
288
+
289
+
290
+ def _on_tags_submit(tags, ui_widgets):
291
+ __query_state.page = 1
292
+ _update_state(*_get_query_params(__query_state, ui_widgets))
293
+ _update_view(ui_widgets)
294
+
295
+
296
+ def lookup(app=None, limit=10, database='tcp://127.0.0.1:6789'):
297
+ after = widgets.DatePicker()
298
+ before = widgets.DatePicker()
299
+ app_prefix = widgets.Label('App:')
300
+ app_name = widgets.Dropdown(
301
+ options=[
302
+ '*',
303
+ ],
304
+ value='*',
305
+ disabled=False,
306
+ )
307
+ bt_pre = widgets.Button(description='<<')
308
+ bt_next = widgets.Button(description='>>')
309
+ tags = widgets.Text()
310
+ page_num_label = widgets.Label('1 | * pages')
311
+ row1 = widgets.HBox(
312
+ [bt_pre, page_num_label, bt_next, app_prefix, app_name])
313
+ row2 = widgets.HBox([widgets.Label('Between:'), after, before])
314
+ row3 = widgets.HBox([widgets.Label(' Tags :'), tags])
315
+ table = widgets.HTML()
316
+ box = widgets.VBox([row1, row2, row3, table])
317
+
318
+ ui_widgets = {
319
+ 'app_prefix': app_prefix,
320
+ 'app': app_name,
321
+ 'before': before,
322
+ 'after': after,
323
+ 'tags': tags,
324
+ 'table': table,
325
+ 'page_num_label': page_num_label,
326
+ 'bt_pre': bt_pre,
327
+ 'bt_next': bt_next,
328
+ }
329
+
330
+ __query_state.records_per_page = limit
331
+ if app is not None:
332
+ __query_state.app_prefix = app.split('.')
333
+ __query_state.app_name = '*'
334
+
335
+ _update_state(*_get_query_params(__query_state, ui_widgets),
336
+ database=database)
337
+ _update_view(ui_widgets)
338
+
339
+ bt_pre.on_click(
340
+ functools.partial(_on_pre_bt_clicked, ui_widgets=ui_widgets))
341
+ bt_next.on_click(
342
+ functools.partial(_on_next_bt_clicked, ui_widgets=ui_widgets))
343
+ after.observe(functools.partial(_on_after_changed, ui_widgets=ui_widgets),
344
+ names='value')
345
+ before.observe(functools.partial(_on_before_changed,
346
+ ui_widgets=ui_widgets),
347
+ names='value')
348
+ app_name.observe(functools.partial(_on_app_changed, ui_widgets=ui_widgets),
349
+ names='value')
350
+ tags.on_submit(functools.partial(_on_tags_submit, ui_widgets=ui_widgets))
351
+
352
+ display(box)
353
+
354
+
355
+ def lookup_list(*, full=False):
356
+ if __query_state.table is None:
357
+ return []
358
+ if full:
359
+ return __query_state.table['body']
360
+ else:
361
+ return [r[0] for r in __query_state.table['body']]