QuLab 2.10.10__cp313-cp313-macosx_10_13_universal2.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.
- qulab/__init__.py +33 -0
- qulab/__main__.py +4 -0
- qulab/cli/__init__.py +0 -0
- qulab/cli/commands.py +30 -0
- qulab/cli/config.py +170 -0
- qulab/cli/decorators.py +28 -0
- qulab/dicttree.py +523 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/analyze.py +188 -0
- qulab/executor/cli.py +434 -0
- qulab/executor/load.py +563 -0
- qulab/executor/registry.py +185 -0
- qulab/executor/schedule.py +543 -0
- qulab/executor/storage.py +615 -0
- qulab/executor/template.py +259 -0
- qulab/executor/utils.py +194 -0
- qulab/expression.py +827 -0
- qulab/fun.cpython-313-darwin.so +0 -0
- qulab/monitor/__init__.py +1 -0
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/config.py +41 -0
- qulab/monitor/dataset.py +77 -0
- qulab/monitor/event_queue.py +54 -0
- qulab/monitor/mainwindow.py +234 -0
- qulab/monitor/monitor.py +115 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +2 -0
- qulab/scan/curd.py +221 -0
- qulab/scan/models.py +554 -0
- qulab/scan/optimize.py +76 -0
- qulab/scan/query.py +387 -0
- qulab/scan/record.py +603 -0
- qulab/scan/scan.py +1166 -0
- qulab/scan/server.py +450 -0
- qulab/scan/space.py +213 -0
- qulab/scan/utils.py +234 -0
- qulab/storage/__init__.py +0 -0
- qulab/storage/__main__.py +51 -0
- qulab/storage/backend/__init__.py +0 -0
- qulab/storage/backend/redis.py +204 -0
- qulab/storage/base_dataset.py +352 -0
- qulab/storage/chunk.py +60 -0
- qulab/storage/dataset.py +127 -0
- qulab/storage/file.py +273 -0
- qulab/storage/models/__init__.py +22 -0
- qulab/storage/models/base.py +4 -0
- qulab/storage/models/config.py +28 -0
- qulab/storage/models/file.py +89 -0
- qulab/storage/models/ipy.py +58 -0
- qulab/storage/models/models.py +88 -0
- qulab/storage/models/record.py +161 -0
- qulab/storage/models/report.py +22 -0
- qulab/storage/models/tag.py +93 -0
- qulab/storage/storage.py +95 -0
- qulab/sys/__init__.py +2 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +255 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +79 -0
- qulab/sys/drivers/FakeInstrument.py +68 -0
- qulab/sys/drivers/__init__.py +0 -0
- qulab/sys/ipy_events.py +125 -0
- qulab/sys/net/__init__.py +0 -0
- qulab/sys/net/bencoder.py +205 -0
- qulab/sys/net/cli.py +169 -0
- qulab/sys/net/dhcp.py +543 -0
- qulab/sys/net/dhcpd.py +176 -0
- qulab/sys/net/kad.py +1142 -0
- qulab/sys/net/kcp.py +192 -0
- qulab/sys/net/nginx.py +194 -0
- qulab/sys/progress.py +190 -0
- qulab/sys/rpc/__init__.py +0 -0
- qulab/sys/rpc/client.py +0 -0
- qulab/sys/rpc/exceptions.py +96 -0
- qulab/sys/rpc/msgpack.py +1052 -0
- qulab/sys/rpc/msgpack.pyi +41 -0
- qulab/sys/rpc/router.py +35 -0
- qulab/sys/rpc/rpc.py +412 -0
- qulab/sys/rpc/serialize.py +139 -0
- qulab/sys/rpc/server.py +29 -0
- qulab/sys/rpc/socket.py +29 -0
- qulab/sys/rpc/utils.py +25 -0
- qulab/sys/rpc/worker.py +0 -0
- qulab/sys/rpc/zmq_socket.py +227 -0
- qulab/tools/__init__.py +0 -0
- qulab/tools/connection_helper.py +39 -0
- qulab/typing.py +2 -0
- qulab/utils.py +95 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +464 -0
- qulab/visualization/plot_circ.py +319 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +242 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/rot3d.py +23 -0
- qulab/visualization/widgets.py +86 -0
- qulab-2.10.10.dist-info/METADATA +110 -0
- qulab-2.10.10.dist-info/RECORD +107 -0
- qulab-2.10.10.dist-info/WHEEL +5 -0
- qulab-2.10.10.dist-info/entry_points.txt +2 -0
- qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
- qulab-2.10.10.dist-info/top_level.txt +1 -0
qulab/monitor/toolbar.py
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
import itertools
|
2
|
+
import re
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
from .config import style
|
6
|
+
from .qt_compat import AlignRight, QtWidgets
|
7
|
+
|
8
|
+
|
9
|
+
def matched_xy_pairs(patterns: str, lst: list[str]) -> list[tuple[str, str]]:
|
10
|
+
patterns = patterns.replace(" ", "").split(";")
|
11
|
+
pairs = []
|
12
|
+
for x, y in itertools.product(lst, repeat=2):
|
13
|
+
test = f"{x},{y}"
|
14
|
+
for pattern in patterns:
|
15
|
+
r = re.match(pattern, test)
|
16
|
+
if r and r.group(0) == test:
|
17
|
+
pairs.append((x, y))
|
18
|
+
break
|
19
|
+
return pairs
|
20
|
+
|
21
|
+
|
22
|
+
class FormatCombo(QtWidgets.QComboBox):
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
super().__init__()
|
26
|
+
self.on_change_callable = None
|
27
|
+
|
28
|
+
def set_on_change_event_action(self, callback: Callable[[], None]):
|
29
|
+
self.on_change_callable = callback
|
30
|
+
self.activated.connect(callback)
|
31
|
+
|
32
|
+
def set_idx(self, idx: int):
|
33
|
+
self.setCurrentIndex(idx)
|
34
|
+
if (callable(self.on_change_callable)):
|
35
|
+
self.on_change_callable()
|
36
|
+
|
37
|
+
|
38
|
+
class XFormatCombo(FormatCombo):
|
39
|
+
|
40
|
+
def __init__(self):
|
41
|
+
super().__init__()
|
42
|
+
self.addItem("real")
|
43
|
+
self.addItem("imag")
|
44
|
+
self.addItem("mag")
|
45
|
+
self.addItem("phase")
|
46
|
+
|
47
|
+
|
48
|
+
class YFormatCombo(FormatCombo):
|
49
|
+
|
50
|
+
def __init__(self):
|
51
|
+
super().__init__()
|
52
|
+
self.addItem("mag")
|
53
|
+
self.addItem("phase")
|
54
|
+
self.addItem("real")
|
55
|
+
self.addItem("imag")
|
56
|
+
|
57
|
+
|
58
|
+
class LineEdit(QtWidgets.QLineEdit):
|
59
|
+
|
60
|
+
def set_on_change_event_action(self, callback: Callable[[], None]):
|
61
|
+
self.on_change_callable = callback
|
62
|
+
self.editingFinished.connect(callback)
|
63
|
+
|
64
|
+
def set_text(self, w):
|
65
|
+
self.setText(w)
|
66
|
+
if (callable(self.on_change_callable)):
|
67
|
+
self.on_change_callable()
|
68
|
+
|
69
|
+
|
70
|
+
class SelectionBundle():
|
71
|
+
|
72
|
+
def __init__(self):
|
73
|
+
self.stx = LineEdit()
|
74
|
+
# select text
|
75
|
+
self.fx = XFormatCombo()
|
76
|
+
self.fy = YFormatCombo()
|
77
|
+
self.lx = QtWidgets.QCheckBox("logX")
|
78
|
+
self.ly = QtWidgets.QCheckBox("logY")
|
79
|
+
self.linkx = QtWidgets.QCheckBox("ShareX")
|
80
|
+
self.linky = QtWidgets.QCheckBox("ShareY")
|
81
|
+
self.sels = []
|
82
|
+
# tuple enumeration
|
83
|
+
|
84
|
+
def set_on_change_event_actions(self, on_text_edited, on_format_changed,
|
85
|
+
on_log_scale_marker_changed):
|
86
|
+
self.stx.set_on_change_event_action(on_text_edited)
|
87
|
+
self.fx.set_on_change_event_action(on_format_changed)
|
88
|
+
self.fy.set_on_change_event_action(on_format_changed)
|
89
|
+
self.lx.toggled.connect(on_format_changed)
|
90
|
+
self.ly.toggled.connect(on_format_changed)
|
91
|
+
self.linkx.toggled.connect(on_log_scale_marker_changed)
|
92
|
+
self.linky.toggled.connect(on_log_scale_marker_changed)
|
93
|
+
|
94
|
+
def rm4l(self): # remove from layout
|
95
|
+
self.stx.setParent(None)
|
96
|
+
self.fx.setParent(None)
|
97
|
+
self.fy.setParent(None)
|
98
|
+
self.lx.setParent(None)
|
99
|
+
self.ly.setParent(None)
|
100
|
+
self.linkx.setParent(None)
|
101
|
+
self.linky.setParent(None)
|
102
|
+
|
103
|
+
def a2l(self, layout): # add to layout
|
104
|
+
i = 3
|
105
|
+
layout.addWidget(self.stx, 0, i)
|
106
|
+
i += 2
|
107
|
+
layout.addWidget(self.fx, 0, i)
|
108
|
+
i += 2
|
109
|
+
layout.addWidget(self.fy, 0, i)
|
110
|
+
i += 2
|
111
|
+
layout.addWidget(self.lx, 0, 10)
|
112
|
+
layout.addWidget(self.ly, 0, 11)
|
113
|
+
layout.addWidget(self.linkx, 0, 12)
|
114
|
+
layout.addWidget(self.linky, 0, 13)
|
115
|
+
|
116
|
+
|
117
|
+
class ToolBar(QtWidgets.QWidget):
|
118
|
+
|
119
|
+
def __init__(self):
|
120
|
+
super().__init__()
|
121
|
+
|
122
|
+
# the buttons
|
123
|
+
# button for points
|
124
|
+
self.mode = 'P'
|
125
|
+
self.setStyleSheet(style)
|
126
|
+
|
127
|
+
self.pb = QtWidgets.QRadioButton('Points')
|
128
|
+
self.pb.setChecked(True)
|
129
|
+
self.pb.toggled.connect(self.toggle_mode)
|
130
|
+
|
131
|
+
# button for Traces
|
132
|
+
self.tb = QtWidgets.QRadioButton('Traces')
|
133
|
+
self.tb.toggled.connect(self.toggle_mode)
|
134
|
+
|
135
|
+
# text labels
|
136
|
+
self.ytxt_lb = QtWidgets.QLabel("(X,Y)")
|
137
|
+
self.ytxt_lb.setAlignment(AlignRight)
|
138
|
+
self.fx_lb = QtWidgets.QLabel("fx")
|
139
|
+
self.fx_lb.setAlignment(AlignRight)
|
140
|
+
self.fy_lb = QtWidgets.QLabel("fy")
|
141
|
+
self.fy_lb.setAlignment(AlignRight)
|
142
|
+
|
143
|
+
# enumeration
|
144
|
+
ps = SelectionBundle()
|
145
|
+
ps.set_on_change_event_actions(self.textEdited, self.generateXYFM,
|
146
|
+
self.link_edited)
|
147
|
+
|
148
|
+
ts = SelectionBundle()
|
149
|
+
ts.set_on_change_event_actions(self.textEdited, self.generateXYFM,
|
150
|
+
self.link_edited)
|
151
|
+
|
152
|
+
# connections :
|
153
|
+
self.ps = ps
|
154
|
+
self.ts = ts
|
155
|
+
|
156
|
+
# plot format configures
|
157
|
+
self.xypairs = []
|
158
|
+
self.xypairs_dirty = True
|
159
|
+
self.fx = None
|
160
|
+
self.fy = None
|
161
|
+
self.xyfm_dirty = True
|
162
|
+
self.link_dirty = True
|
163
|
+
self.lx = False
|
164
|
+
self.ly = False
|
165
|
+
# setting layout
|
166
|
+
self.layout = QtWidgets.QGridLayout()
|
167
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
168
|
+
self.setLayout(self.layout)
|
169
|
+
|
170
|
+
self.layout.addWidget(self.pb, 0, 0)
|
171
|
+
self.layout.addWidget(self.tb, 0, 1)
|
172
|
+
|
173
|
+
self.layout.addWidget(self.ytxt_lb, 0, 2)
|
174
|
+
self.layout.addWidget(self.fx_lb, 0, 4)
|
175
|
+
self.layout.addWidget(self.fy_lb, 0, 6)
|
176
|
+
|
177
|
+
self.AR = QtWidgets.QPushButton("AR")
|
178
|
+
self.AR.setMaximumWidth(30)
|
179
|
+
self.AR.setToolTip("Auto Range")
|
180
|
+
self.CR = QtWidgets.QPushButton("CLR")
|
181
|
+
self.CR.setMaximumWidth(30)
|
182
|
+
self.CR.setToolTip("Clearing History Plots")
|
183
|
+
self.CR_flag = False
|
184
|
+
self.layout.addWidget(self.AR, 0, 8)
|
185
|
+
self.layout.addWidget(self.CR, 0, 9)
|
186
|
+
|
187
|
+
self.refresh_layout()
|
188
|
+
|
189
|
+
@property
|
190
|
+
def column_names(self) -> list[str]:
|
191
|
+
return {
|
192
|
+
"P": self.mainwindow.point_data_box.column_names,
|
193
|
+
"T": self.mainwindow.trace_data_box.column_names
|
194
|
+
}[self.mode]
|
195
|
+
|
196
|
+
@property
|
197
|
+
def selections(self) -> SelectionBundle:
|
198
|
+
return {"P": self.ps, "T": self.ts}[self.mode]
|
199
|
+
|
200
|
+
def set_trace_text(self, text: str):
|
201
|
+
self.ts.stx.set_text(text)
|
202
|
+
|
203
|
+
def set_point_text(self, text: str):
|
204
|
+
self.ps.stx.set_text(text)
|
205
|
+
|
206
|
+
def sharexy(self):
|
207
|
+
return self.selections.linkx.isChecked(
|
208
|
+
), self.selections.linky.isChecked()
|
209
|
+
|
210
|
+
def set_mainwindow(self, mainwindow):
|
211
|
+
self.mainwindow = mainwindow
|
212
|
+
self.AR.clicked.connect(self.mainwindow.all_enable_auto_range)
|
213
|
+
self.CR.clicked.connect(self.CR_action)
|
214
|
+
|
215
|
+
def AR_action(self):
|
216
|
+
self.mainwindow.enable_all_auto_range()
|
217
|
+
|
218
|
+
def CR_action(self):
|
219
|
+
self.CR_flag = True
|
220
|
+
|
221
|
+
def refresh_layout(self):
|
222
|
+
if self.mode == 'P':
|
223
|
+
self.ts.rm4l()
|
224
|
+
self.ps.a2l(self.layout)
|
225
|
+
elif self.mode == 'T':
|
226
|
+
self.ps.rm4l()
|
227
|
+
self.ts.a2l(self.layout)
|
228
|
+
|
229
|
+
def toggle_mode(self):
|
230
|
+
if (self.pb.isChecked()):
|
231
|
+
self.mode = 'P'
|
232
|
+
elif (self.tb.isChecked()):
|
233
|
+
self.mode = 'T'
|
234
|
+
self.refresh_layout()
|
235
|
+
self.refresh_comb()
|
236
|
+
|
237
|
+
def refresh_comb(self, ):
|
238
|
+
self.generateXYFM()
|
239
|
+
self.textEdited()
|
240
|
+
#set tooltips
|
241
|
+
self.ytxt_lb.setToolTip(str(self.column_names))
|
242
|
+
|
243
|
+
def link_edited(self):
|
244
|
+
# print("LinkEdited")
|
245
|
+
self.link_dirty = True
|
246
|
+
|
247
|
+
def generateXYFM(self):
|
248
|
+
self.fx = self.selections.fx.currentText()
|
249
|
+
self.fy = self.selections.fy.currentText()
|
250
|
+
self.lx = self.selections.lx.isChecked()
|
251
|
+
self.ly = self.selections.ly.isChecked()
|
252
|
+
self.xyfm_dirty = True
|
253
|
+
#self.show_info() ;
|
254
|
+
|
255
|
+
def textEdited(self):
|
256
|
+
new_pairs = matched_xy_pairs(self.selections.stx.text(),
|
257
|
+
self.column_names)
|
258
|
+
if (len(self.xypairs) != len(new_pairs)):
|
259
|
+
self.xypairs_dirty = True
|
260
|
+
else:
|
261
|
+
for i, xy in enumerate(new_pairs):
|
262
|
+
if (xy != self.xypairs[i]):
|
263
|
+
self.xypairs_dirty = True
|
264
|
+
break
|
265
|
+
self.xypairs = new_pairs
|
qulab/scan/__init__.py
ADDED
qulab/scan/curd.py
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
import lzma
|
2
|
+
import pickle
|
3
|
+
from datetime import date, datetime, timezone
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Sequence, Type, Union
|
6
|
+
|
7
|
+
from sqlalchemy.orm import Query, Session, aliased
|
8
|
+
from sqlalchemy.orm.exc import NoResultFound
|
9
|
+
from sqlalchemy.orm.session import Session
|
10
|
+
from ..dicttree import foldDict
|
11
|
+
|
12
|
+
from .models import (Cell, Comment, Config, InputText, Notebook, Record,
|
13
|
+
Report, Sample, Tag, utcnow)
|
14
|
+
|
15
|
+
|
16
|
+
def tag(session: Session, tag_text: str) -> Tag:
|
17
|
+
"""Get a tag from the database or create a new if not exists."""
|
18
|
+
try:
|
19
|
+
return session.query(Tag).filter(Tag.text == tag_text).one()
|
20
|
+
except NoResultFound:
|
21
|
+
tag = Tag(text=tag_text)
|
22
|
+
return tag
|
23
|
+
|
24
|
+
|
25
|
+
def tag_it(session: Session, tag_text: str, obj: Union[Sample, Record,
|
26
|
+
Report]) -> Tag:
|
27
|
+
"""Tag an object."""
|
28
|
+
if obj.id is None:
|
29
|
+
session.add(obj)
|
30
|
+
obj.tags.append(tag(session, tag_text))
|
31
|
+
else:
|
32
|
+
session.query(type(obj)).filter(
|
33
|
+
type(obj).id == obj.id).one().tags.append(tag(session, tag_text))
|
34
|
+
session.commit()
|
35
|
+
|
36
|
+
|
37
|
+
def get_object_with_tags(session: Session,
|
38
|
+
cls: Union[Type[Comment], Type[Sample], Type[Record],
|
39
|
+
Type[Report]], *tags: str) -> Query:
|
40
|
+
"""
|
41
|
+
Query objects with the given tags.
|
42
|
+
|
43
|
+
Parameters
|
44
|
+
----------
|
45
|
+
session : :class:`sqlalchemy.orm.Session`
|
46
|
+
The database session.
|
47
|
+
cls : :class:`sqlalchemy.orm.Mapper`
|
48
|
+
The object class.
|
49
|
+
tags : str
|
50
|
+
The tags.
|
51
|
+
|
52
|
+
Returns
|
53
|
+
-------
|
54
|
+
:class:`sqlalchemy.orm.Query`
|
55
|
+
The query.
|
56
|
+
"""
|
57
|
+
if isinstance(session, Query):
|
58
|
+
q = session
|
59
|
+
else:
|
60
|
+
q = session.query(cls)
|
61
|
+
if not hasattr(cls, 'tags'):
|
62
|
+
return []
|
63
|
+
|
64
|
+
aliase = {tag: aliased(Tag) for tag in tags}
|
65
|
+
|
66
|
+
for tag, a in aliase.items():
|
67
|
+
q = q.join(a, cls.tags)
|
68
|
+
if '*' in tag:
|
69
|
+
q = q.filter(a.text.like(tag.replace('*', '%')))
|
70
|
+
else:
|
71
|
+
q = q.filter(a.text == tag)
|
72
|
+
return q
|
73
|
+
|
74
|
+
|
75
|
+
def query_record(session: Session,
|
76
|
+
offset: int = 0,
|
77
|
+
limit: int = 10,
|
78
|
+
app: str | None = None,
|
79
|
+
tags: Sequence[str] = (),
|
80
|
+
before: datetime | date | None = None,
|
81
|
+
after: datetime | date | None = None):
|
82
|
+
tz_offset = datetime.now(timezone.utc).astimezone().utcoffset()
|
83
|
+
table = {'header': ['ID', 'App', 'tags', 'created time'], 'body': []}
|
84
|
+
apps = sorted(
|
85
|
+
set([
|
86
|
+
n for n, *_ in get_object_with_tags(session.query(Record.app),
|
87
|
+
Record, *tags).all()
|
88
|
+
]))
|
89
|
+
apps = foldDict(dict([(app, None) for app in apps]))
|
90
|
+
|
91
|
+
query = get_object_with_tags(session, Record, *tags)
|
92
|
+
|
93
|
+
if app is not None:
|
94
|
+
if app.endswith('*'):
|
95
|
+
query = query.filter(Record.app.like(app[:-1] + '%'))
|
96
|
+
else:
|
97
|
+
query = query.filter(Record.app == app)
|
98
|
+
if before is not None:
|
99
|
+
if isinstance(before, date):
|
100
|
+
before = datetime(before.year, before.month, before.day)
|
101
|
+
query = query.filter(Record.ctime <= before - tz_offset)
|
102
|
+
if after is not None:
|
103
|
+
if isinstance(after, date):
|
104
|
+
after = datetime(after.year, after.month, after.day)
|
105
|
+
query = query.filter(Record.ctime >= after - tz_offset)
|
106
|
+
total = query.count()
|
107
|
+
for r in query.order_by(Record.ctime.desc()).limit(limit).offset(offset):
|
108
|
+
tags = sorted([t.text for t in r.tags])
|
109
|
+
ctime = r.ctime + tz_offset
|
110
|
+
row = [r.id, r.app, tags, ctime]
|
111
|
+
table['body'].append(row)
|
112
|
+
|
113
|
+
return total, apps, table
|
114
|
+
|
115
|
+
|
116
|
+
def update_tags(session: Session,
|
117
|
+
record_id: int,
|
118
|
+
tags: Sequence[str],
|
119
|
+
append: bool = False):
|
120
|
+
record = session.get(Record, record_id)
|
121
|
+
if record is None:
|
122
|
+
return False
|
123
|
+
if append:
|
124
|
+
old = [t.text for t in record.tags]
|
125
|
+
for t in old:
|
126
|
+
if t not in tags:
|
127
|
+
tags.append(t)
|
128
|
+
record.tags = [tag(session, t) for t in tags]
|
129
|
+
try:
|
130
|
+
session.commit()
|
131
|
+
except Exception:
|
132
|
+
session.rollback()
|
133
|
+
return False
|
134
|
+
return True
|
135
|
+
|
136
|
+
|
137
|
+
def remove_tags(session: Session, record_id: int, tags: Sequence[str]):
|
138
|
+
record = session.get(Record, record_id)
|
139
|
+
if record is None:
|
140
|
+
return False
|
141
|
+
old = [t.text for t in record.tags]
|
142
|
+
record.tags = [tag(session, t) for t in old if t not in tags]
|
143
|
+
try:
|
144
|
+
session.commit()
|
145
|
+
except Exception:
|
146
|
+
session.rollback()
|
147
|
+
return False
|
148
|
+
return True
|
149
|
+
|
150
|
+
|
151
|
+
def create_notebook(session: Session, notebook_name: str) -> Notebook:
|
152
|
+
"""Create a notebook in the database."""
|
153
|
+
notebook = Notebook(name=notebook_name)
|
154
|
+
session.add(notebook)
|
155
|
+
return notebook
|
156
|
+
|
157
|
+
|
158
|
+
def create_input_text(session: Session, input_text: str) -> InputText:
|
159
|
+
"""Create an input text in the database."""
|
160
|
+
input = InputText()
|
161
|
+
input.text = input_text
|
162
|
+
try:
|
163
|
+
input = session.query(InputText).filter(
|
164
|
+
InputText.hash == input.hash,
|
165
|
+
InputText.text_field == input_text).one()
|
166
|
+
except NoResultFound:
|
167
|
+
session.add(input)
|
168
|
+
return input
|
169
|
+
|
170
|
+
|
171
|
+
def create_cell(session: Session, notebook: Notebook, input_text: str) -> Cell:
|
172
|
+
"""Create a cell in the database."""
|
173
|
+
cell = Cell()
|
174
|
+
cell.notebook = notebook
|
175
|
+
cell.input = create_input_text(session, input_text)
|
176
|
+
cell.index = len(notebook.cells) - 1
|
177
|
+
session.add(cell)
|
178
|
+
notebook.atime = cell.ctime
|
179
|
+
return cell
|
180
|
+
|
181
|
+
|
182
|
+
def create_config(session: Session, config: dict | bytes, base: Path,
|
183
|
+
filename: str) -> Config:
|
184
|
+
"""Create a config in the database."""
|
185
|
+
|
186
|
+
if not isinstance(config, bytes):
|
187
|
+
buf = pickle.dumps(config)
|
188
|
+
buf = lzma.compress(buf)
|
189
|
+
content_type = 'application/pickle+lzma'
|
190
|
+
else:
|
191
|
+
buf = config
|
192
|
+
content_type = 'application/octet-stream'
|
193
|
+
config = Config(buf)
|
194
|
+
config.content_type = content_type
|
195
|
+
for cfg in session.query(Config).filter(Config.hash == config.hash).all():
|
196
|
+
with open(base / cfg.file, 'rb') as f:
|
197
|
+
if f.read() == buf:
|
198
|
+
cfg.atime = utcnow()
|
199
|
+
return cfg
|
200
|
+
else:
|
201
|
+
path = base / filename
|
202
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
203
|
+
with open(path, 'wb') as f:
|
204
|
+
f.write(buf)
|
205
|
+
config.file = filename
|
206
|
+
session.add(config)
|
207
|
+
return config
|
208
|
+
|
209
|
+
|
210
|
+
def get_config(session: Session, config_id: int, base: Path):
|
211
|
+
config = session.get(Config, config_id)
|
212
|
+
if config is None:
|
213
|
+
return None
|
214
|
+
config.atime = utcnow()
|
215
|
+
path = base / config.file
|
216
|
+
with open(path, 'rb') as f:
|
217
|
+
buf = f.read()
|
218
|
+
if config.content_type == 'application/pickle+lzma':
|
219
|
+
buf = lzma.decompress(buf)
|
220
|
+
buf = pickle.loads(buf)
|
221
|
+
return buf
|