QuLab 2.0.0__py3-none-any.whl → 2.10.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.
- qulab/__init__.py +33 -1
- qulab/__main__.py +4 -0
- qulab/cli/commands.py +31 -0
- qulab/cli/config.py +170 -0
- qulab/cli/decorators.py +28 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/analyze.py +188 -0
- qulab/executor/cli.py +447 -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/monitor/__init__.py +1 -93
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/monitor.py +115 -0
- qulab/scan/__init__.py +2 -4
- 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 +230 -34
- qulab/sys/__init__.py +2 -0
- qulab/sys/device/basedevice.py +50 -16
- qulab/sys/device/loader.py +1 -1
- qulab/sys/device/utils.py +46 -13
- qulab/sys/drivers/FakeInstrument.py +36 -20
- qulab/sys/net/nginx.py +16 -14
- qulab/sys/rpc/router.py +35 -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 -1
- 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.0.0.dist-info → qulab-2.10.12.dist-info}/METADATA +28 -12
- qulab-2.10.12.dist-info/RECORD +104 -0
- {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info}/WHEEL +1 -1
- qulab-2.10.12.dist-info/entry_points.txt +2 -0
- QuLab-2.0.0.dist-info/RECORD +0 -71
- qulab/monitor/multiploter/__init__.py +0 -1
- qulab/scan/base.py +0 -544
- qulab/scan/expression.py +0 -360
- qulab/scan/scanner.py +0 -244
- qulab/scan/transforms.py +0 -16
- /qulab/{scan/dataset.py → cli/__init__.py} +0 -0
- /qulab/monitor/{multiploter/config.py → config.py} +0 -0
- /qulab/monitor/{multiploter/dataset.py → dataset.py} +0 -0
- /qulab/monitor/{multiploter/event_queue.py → event_queue.py} +0 -0
- /qulab/monitor/{multiploter/main.py → mainwindow.py} +0 -0
- /qulab/monitor/{multiploter/ploter.py → ploter.py} +0 -0
- /qulab/monitor/{multiploter/qt_compat.py → qt_compat.py} +0 -0
- /qulab/monitor/{multiploter/toolbar.py → toolbar.py} +0 -0
- {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info/licenses}/LICENSE +0 -0
- {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info}/top_level.txt +0 -0
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 qlispreg.dicttree import foldDict
|
8
|
+
from sqlalchemy.orm import Query, Session, aliased
|
9
|
+
from sqlalchemy.orm.exc import NoResultFound
|
10
|
+
from sqlalchemy.orm.session import Session
|
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
|