QuLab 2.1.1__tar.gz → 2.1.3__tar.gz

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.
Files changed (97) hide show
  1. {qulab-2.1.1 → qulab-2.1.3}/PKG-INFO +1 -1
  2. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/PKG-INFO +1 -1
  3. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/SOURCES.txt +0 -1
  4. qulab-2.1.3/qulab/__init__.py +3 -0
  5. {qulab-2.1.1 → qulab-2.1.3}/qulab/__main__.py +0 -2
  6. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/__init__.py +1 -1
  7. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/curd.py +78 -1
  8. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/models.py +21 -6
  9. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/query.py +8 -8
  10. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/record.py +94 -3
  11. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/scan.py +159 -23
  12. qulab-2.1.1/qulab/scan/recorder.py → qulab-2.1.3/qulab/scan/server.py +89 -13
  13. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/zmq_socket.py +7 -1
  14. qulab-2.1.3/qulab/version.py +1 -0
  15. qulab-2.1.1/qulab/__init__.py +0 -1
  16. qulab-2.1.1/qulab/scan/server.py +0 -104
  17. qulab-2.1.1/qulab/version.py +0 -1
  18. {qulab-2.1.1 → qulab-2.1.3}/LICENSE +0 -0
  19. {qulab-2.1.1 → qulab-2.1.3}/MANIFEST.in +0 -0
  20. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/dependency_links.txt +0 -0
  21. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/entry_points.txt +0 -0
  22. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/requires.txt +0 -0
  23. {qulab-2.1.1 → qulab-2.1.3}/QuLab.egg-info/top_level.txt +0 -0
  24. {qulab-2.1.1 → qulab-2.1.3}/README.md +0 -0
  25. {qulab-2.1.1 → qulab-2.1.3}/pyproject.toml +0 -0
  26. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/__init__.py +0 -0
  27. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/__main__.py +0 -0
  28. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/config.py +0 -0
  29. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/dataset.py +0 -0
  30. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/event_queue.py +0 -0
  31. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/mainwindow.py +0 -0
  32. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/monitor.py +0 -0
  33. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/ploter.py +0 -0
  34. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/qt_compat.py +0 -0
  35. {qulab-2.1.1 → qulab-2.1.3}/qulab/monitor/toolbar.py +0 -0
  36. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/expression.py +0 -0
  37. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/optimize.py +0 -0
  38. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/space.py +0 -0
  39. {qulab-2.1.1 → qulab-2.1.3}/qulab/scan/utils.py +0 -0
  40. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/__init__.py +0 -0
  41. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/__main__.py +0 -0
  42. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/backend/__init__.py +0 -0
  43. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/backend/redis.py +0 -0
  44. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/base_dataset.py +0 -0
  45. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/chunk.py +0 -0
  46. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/dataset.py +0 -0
  47. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/file.py +0 -0
  48. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/__init__.py +0 -0
  49. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/base.py +0 -0
  50. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/config.py +0 -0
  51. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/file.py +0 -0
  52. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/ipy.py +0 -0
  53. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/models.py +0 -0
  54. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/record.py +0 -0
  55. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/report.py +0 -0
  56. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/models/tag.py +0 -0
  57. {qulab-2.1.1 → qulab-2.1.3}/qulab/storage/storage.py +0 -0
  58. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/__init__.py +0 -0
  59. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/chat.py +0 -0
  60. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/device/__init__.py +0 -0
  61. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/device/basedevice.py +0 -0
  62. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/device/loader.py +0 -0
  63. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/device/utils.py +0 -0
  64. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/drivers/FakeInstrument.py +0 -0
  65. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/drivers/__init__.py +0 -0
  66. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/ipy_events.py +0 -0
  67. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/__init__.py +0 -0
  68. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/bencoder.py +0 -0
  69. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/cli.py +0 -0
  70. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/dhcp.py +0 -0
  71. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/dhcpd.py +0 -0
  72. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/kad.py +0 -0
  73. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/kcp.py +0 -0
  74. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/net/nginx.py +0 -0
  75. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/progress.py +0 -0
  76. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/__init__.py +0 -0
  77. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/client.py +0 -0
  78. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/exceptions.py +0 -0
  79. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/msgpack.py +0 -0
  80. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/msgpack.pyi +0 -0
  81. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/rpc.py +0 -0
  82. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/serialize.py +0 -0
  83. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/server.py +0 -0
  84. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/socket.py +0 -0
  85. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/utils.py +0 -0
  86. {qulab-2.1.1 → qulab-2.1.3}/qulab/sys/rpc/worker.py +0 -0
  87. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/__init__.py +0 -0
  88. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/__main__.py +0 -0
  89. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/_autoplot.py +0 -0
  90. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/plot_layout.py +0 -0
  91. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/plot_seq.py +0 -0
  92. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/qdat.py +0 -0
  93. {qulab-2.1.1 → qulab-2.1.3}/qulab/visualization/widgets.py +0 -0
  94. {qulab-2.1.1 → qulab-2.1.3}/setup.cfg +0 -0
  95. {qulab-2.1.1 → qulab-2.1.3}/setup.py +0 -0
  96. {qulab-2.1.1 → qulab-2.1.3}/src/qulab.h +0 -0
  97. {qulab-2.1.1 → qulab-2.1.3}/tests/test_scan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.1.1
3
+ Version: 2.1.3
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.1.1
3
+ Version: 2.1.3
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -29,7 +29,6 @@ qulab/scan/models.py
29
29
  qulab/scan/optimize.py
30
30
  qulab/scan/query.py
31
31
  qulab/scan/record.py
32
- qulab/scan/recorder.py
33
32
  qulab/scan/scan.py
34
33
  qulab/scan/server.py
35
34
  qulab/scan/space.py
@@ -0,0 +1,3 @@
1
+ from .scan import Scan, get_record, load_record, lookup, lookup_list
2
+ from .version import __version__
3
+ from .visualization import autoplot
@@ -1,7 +1,6 @@
1
1
  import click
2
2
 
3
3
  from .monitor.__main__ import main as monitor
4
- from .scan.recorder import record
5
4
  from .scan.server import server
6
5
  from .sys.net.cli import dht
7
6
  from .visualization.__main__ import plot
@@ -21,7 +20,6 @@ def hello():
21
20
  main.add_command(monitor)
22
21
  main.add_command(plot)
23
22
  main.add_command(dht)
24
- main.add_command(record)
25
23
  main.add_command(server)
26
24
 
27
25
  if __name__ == '__main__':
@@ -1,3 +1,3 @@
1
1
  from .expression import Expression, Symbol
2
- from .query import get_record, lookup, lookup_list
2
+ from .query import get_record, load_record, lookup, lookup_list
3
3
  from .scan import Scan
@@ -1,4 +1,7 @@
1
+ import lzma
2
+ import pickle
1
3
  from datetime import date, datetime, timezone
4
+ from pathlib import Path
2
5
  from typing import Sequence, Type, Union
3
6
 
4
7
  from sqlalchemy.orm import Query, Session, aliased
@@ -6,7 +9,8 @@ from sqlalchemy.orm.exc import NoResultFound
6
9
  from sqlalchemy.orm.session import Session
7
10
  from waveforms.dicttree import foldDict
8
11
 
9
- from .models import Comment, Record, Report, Sample, Tag
12
+ from .models import (Cell, Comment, Config, InputText, Notebook, Record,
13
+ Report, Sample, Tag, utcnow)
10
14
 
11
15
 
12
16
  def tag(session: Session, tag_text: str) -> Tag:
@@ -142,3 +146,76 @@ def remove_tags(session: Session, record_id: int, tags: Sequence[str]):
142
146
  session.rollback()
143
147
  return False
144
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
@@ -1,11 +1,9 @@
1
1
  import hashlib
2
2
  import pickle
3
- import time
4
3
  from datetime import datetime, timezone
5
4
  from functools import singledispatchmethod
6
- from typing import Optional
7
5
 
8
- from sqlalchemy import (JSON, Column, DateTime, Float, ForeignKey, Integer,
6
+ from sqlalchemy import (Column, DateTime, Float, ForeignKey, Integer,
9
7
  LargeBinary, String, Table, Text, create_engine)
10
8
  from sqlalchemy.orm import (backref, declarative_base, relationship,
11
9
  sessionmaker)
@@ -325,7 +323,7 @@ class InputText(Base):
325
323
  __tablename__ = 'inputs'
326
324
 
327
325
  id = Column(Integer, primary_key=True)
328
- hash = Column(LargeBinary(20))
326
+ hash = Column(LargeBinary(20), index=True)
329
327
  text_field = Column(Text, unique=True)
330
328
 
331
329
  @property
@@ -432,6 +430,22 @@ class SampleTransfer(Base):
432
430
  comments = relationship("Comment", secondary=sample_transfer_comments)
433
431
 
434
432
 
433
+ class Config(Base):
434
+ __tablename__ = 'configs'
435
+
436
+ id = Column(Integer, primary_key=True)
437
+ hash = Column(LargeBinary(20), index=True)
438
+ file = Column(String)
439
+ content_type = Column(String, default='application/pickle')
440
+ ctime = Column(DateTime, default=utcnow)
441
+ atime = Column(DateTime, default=utcnow)
442
+
443
+ records = relationship("Record", back_populates="config")
444
+
445
+ def __init__(self, data: bytes) -> None:
446
+ self.hash = hashlib.sha1(data).digest()
447
+
448
+
435
449
  class Record(Base):
436
450
  __tablename__ = 'records'
437
451
 
@@ -440,14 +454,14 @@ class Record(Base):
440
454
  mtime = Column(DateTime, default=utcnow)
441
455
  atime = Column(DateTime, default=utcnow)
442
456
  user_id = Column(Integer, ForeignKey('users.id'))
457
+ config_id = Column(Integer, ForeignKey('configs.id'))
443
458
  parent_id = Column(Integer, ForeignKey('records.id'))
444
459
  cell_id = Column(Integer, ForeignKey('cells.id'))
445
460
 
446
461
  app = Column(String)
447
462
  file = Column(String)
463
+ content_type = Column(String, default='application/pickle')
448
464
  key = Column(String)
449
- config = Column(JSON)
450
- task_hash = Column(LargeBinary(32))
451
465
 
452
466
  parent = relationship("Record",
453
467
  remote_side=[id],
@@ -456,6 +470,7 @@ class Record(Base):
456
470
  remote_side=[parent_id],
457
471
  back_populates="parent")
458
472
 
473
+ config = relationship("Config", back_populates="records")
459
474
  user = relationship("User")
460
475
  samples = relationship("Sample",
461
476
  secondary=sample_records,
@@ -6,10 +6,13 @@ import dill
6
6
  import ipywidgets as widgets
7
7
  import zmq
8
8
  from IPython.display import display
9
+ from sqlalchemy import create_engine
10
+ from sqlalchemy.orm import sessionmaker
9
11
 
10
12
  from qulab.sys.rpc.zmq_socket import ZMQContextManager
11
13
 
12
14
  from .record import Record
15
+ from .server import get_local_record
13
16
  from .scan import default_server
14
17
 
15
18
 
@@ -26,18 +29,15 @@ def get_record(id, database=default_server) -> Record:
26
29
  d._file = None
27
30
  return d
28
31
  else:
29
- from .models import Record as RecordInDB
30
- from .models import create_engine, sessionmaker
31
-
32
32
  db_file = Path(database) / 'data.db'
33
33
  engine = create_engine(f'sqlite:///{db_file}')
34
34
  Session = sessionmaker(bind=engine)
35
35
  with Session() as session:
36
- path = Path(database) / 'objects' / session.get(RecordInDB,
37
- id).file
38
- with open(path, 'rb') as f:
39
- record = dill.load(f)
40
- return record
36
+ return get_local_record(session, id, database)
37
+
38
+
39
+ def load_record(file):
40
+ return Record.load(file)
41
41
 
42
42
 
43
43
  def _format_tag(tag):
@@ -1,6 +1,7 @@
1
1
  import itertools
2
2
  import sys
3
3
  import uuid
4
+ import zipfile
4
5
  from pathlib import Path
5
6
  from threading import Lock
6
7
  from types import EllipsisType
@@ -13,7 +14,7 @@ from qulab.sys.rpc.zmq_socket import ZMQContextManager
13
14
 
14
15
  from .space import OptimizeSpace, Space
15
16
 
16
- _notgiven = object()
17
+ _not_given = object()
17
18
 
18
19
 
19
20
  def random_path(base):
@@ -94,6 +95,11 @@ class BufferList():
94
95
  dill.dump(item, f)
95
96
  self._list.clear()
96
97
 
98
+ def delete(self):
99
+ if isinstance(self.file, Path):
100
+ self.file.unlink()
101
+ self.file = None
102
+
97
103
  def append(self, pos, value, dims=None):
98
104
  if dims is not None:
99
105
  if any([p != 0 for i, p in enumerate(pos) if i not in dims]):
@@ -120,6 +126,18 @@ class BufferList():
120
126
  yield pos, value
121
127
  except EOFError:
122
128
  break
129
+ elif isinstance(
130
+ self.file, tuple) and len(self.file) == 2 and isinstance(
131
+ self.file[0], str) and self.file[0].endswith('.zip'):
132
+ f, name = self.file
133
+ with zipfile.ZipFile(f, 'r') as z:
134
+ with z.open(name, 'r') as f:
135
+ while True:
136
+ try:
137
+ pos, value = dill.load(f)
138
+ yield pos, value
139
+ except EOFError:
140
+ break
123
141
 
124
142
  def iter(self):
125
143
  if self._data_id is None:
@@ -327,7 +345,7 @@ class Record():
327
345
  ret = ret.toarray()
328
346
  return ret
329
347
 
330
- def get(self, key, default=_notgiven, buffer_to_array=False, slice=None):
348
+ def get(self, key, default=_not_given, buffer_to_array=False, slice=None):
331
349
  if self.is_remote_record():
332
350
  with ZMQContextManager(zmq.DEALER,
333
351
  connect=self.database) as socket:
@@ -355,7 +373,7 @@ class Record():
355
373
  else:
356
374
  return ret
357
375
  else:
358
- if default is _notgiven:
376
+ if default is _not_given:
359
377
  d = self._items.get(key)
360
378
  else:
361
379
  d = self._items.get(key, default)
@@ -437,6 +455,79 @@ class Record():
437
455
  with open(self._file, 'wb') as f:
438
456
  dill.dump(self, f)
439
457
 
458
+ def delete(self):
459
+ if self.is_remote_record():
460
+ with ZMQContextManager(zmq.DEALER,
461
+ connect=self.database) as socket:
462
+ socket.send_pyobj({
463
+ 'method': 'record_delete',
464
+ 'record_id': self.id
465
+ })
466
+ elif self.is_local_record():
467
+ for key, value in self._items.items():
468
+ if isinstance(value, BufferList):
469
+ value.delete()
470
+ self._file.unlink()
471
+
472
+ def export(self, file):
473
+ with zipfile.ZipFile(file,
474
+ 'w',
475
+ compression=zipfile.ZIP_DEFLATED,
476
+ compresslevel=9) as z:
477
+ items = {}
478
+ for key in self.keys():
479
+ value = self.get(key)
480
+ if isinstance(value, BufferList):
481
+ v = BufferList()
482
+ v.lu = value.lu
483
+ v.rd = value.rd
484
+ v.inner_shape = value.inner_shape
485
+ items[key] = v
486
+ with z.open(f'{key}.buf', 'w') as f:
487
+ for pos, data in value.iter():
488
+ dill.dump((pos, data), f)
489
+ else:
490
+ items[key] = value
491
+ with z.open('record.pkl', 'w') as f:
492
+ self.description['entry']['scripts'] = self.scripts()
493
+ dill.dump((self.description, items), f)
494
+
495
+ def scripts(self, session=None):
496
+ scripts = self.description['entry']['scripts']
497
+ if isinstance(scripts, list):
498
+ return scripts
499
+ else:
500
+ cell_id = scripts
501
+
502
+ if self.is_remote_record():
503
+ with ZMQContextManager(zmq.DEALER,
504
+ connect=self.database) as socket:
505
+ socket.send_pyobj({
506
+ 'method': 'notebook_history',
507
+ 'cell_id': cell_id
508
+ })
509
+ return socket.recv_pyobj()
510
+ elif self.is_local_record():
511
+ from .models import Cell
512
+ assert session is not None, "session is required for local record"
513
+ cell = session.get(Cell, cell_id)
514
+ return [
515
+ cell.input.text
516
+ for cell in cell.notebook.cells[1:cell.index + 2]
517
+ ]
518
+
519
+ @classmethod
520
+ def load(cls, file: str):
521
+ with zipfile.ZipFile(file, 'r') as z:
522
+ with z.open('record.pkl', 'r') as f:
523
+ description, items = dill.load(f)
524
+ record = cls(None, None, description)
525
+ for key, value in items.items():
526
+ if isinstance(value, BufferList):
527
+ value.file = file, f'{key}.buf'
528
+ record._items[key] = value
529
+ return record
530
+
440
531
  def __repr__(self):
441
532
  return f"<Record: id={self.id} app={self.description['app']}, keys={self.keys()}>"
442
533