QuLab 2.3.2__cp311-cp311-macosx_10_9_universal2.whl → 2.3.3__cp311-cp311-macosx_10_9_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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.3.2
3
+ Version: 2.3.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,7 +1,7 @@
1
1
  qulab/__init__.py,sha256=P-Mx2p4TVmL91SoxoeXcj8Qm0x4xUf5Q_FLk0Yc_gIQ,138
2
2
  qulab/__main__.py,sha256=ZC1NKaoxKyy60DaCfB8vYnB1z3RXQ2j8E1sRZ4A8sXE,428
3
- qulab/fun.cpython-311-darwin.so,sha256=aFDKwsfj4iIn_qPA_HgPOb23YQ_AwnE5G_fPkQEbQIs,159616
4
- qulab/version.py,sha256=wmsQzkE8l2rmrt5-AjUYaTVQ_X-dbo-FDvqay4JDxVk,21
3
+ qulab/fun.cpython-311-darwin.so,sha256=rjrdj1j69--qg_Yc4J2F9YJ94bVxv9Jy3Eix-vaQ8AM,159616
4
+ qulab/version.py,sha256=_TojZ2sN-5aiITSzim-_br5UuW9pwpo2Tx8vnDWpsqE,21
5
5
  qulab/monitor/__init__.py,sha256=nTHelnDpxRS_fl_B38TsN0njgq8eVTEz9IAnN3NbDlM,42
6
6
  qulab/monitor/__main__.py,sha256=w3yUcqq195LzSnXTkQcuC1RSFRhy4oQ_PEBmucXguME,97
7
7
  qulab/monitor/config.py,sha256=fQ5JcsMApKc1UwANEnIvbDQZl8uYW0tle92SaYtX9lI,744
@@ -14,13 +14,13 @@ qulab/monitor/qt_compat.py,sha256=OK71_JSO_iyXjRIKHANmaK4Lx4bILUzmXI-mwKg3QeI,78
14
14
  qulab/monitor/toolbar.py,sha256=WEag6cxAtEsOLL14XvM7pSE56EA3MO188_JuprNjdBs,7948
15
15
  qulab/scan/__init__.py,sha256=ZX4WsvqYxvJeHLgGSrtJoAnVU94gxY7EHKMxYooMERg,130
16
16
  qulab/scan/curd.py,sha256=thq_qfi3qng3Zx-1uhNG64IQhGCuum_LR4MOKnS8cDI,6896
17
- qulab/scan/expression.py,sha256=s2_sWFm4gKgJ7pGLmuugCU8XifAm9Mc3AT3bdoyFUtM,19459
17
+ qulab/scan/expression.py,sha256=ngWrP1o9CuYJ1gq5YHaV7EfxKIKUX7Gz6KG80E6XThY,20070
18
18
  qulab/scan/models.py,sha256=5Jpo25WGMWs0GtLzYLsWO61G3-FFYx5BHhBr2b6rOTE,17681
19
19
  qulab/scan/optimize.py,sha256=jYceGazEabuwYqmZE8P6Nnq_KsLyUP0p-p88VbbJLhM,2559
20
20
  qulab/scan/query.py,sha256=-5uHMhXSyGovK1oy_uUbGVEbRFzaMBkP78ZMNfI3jD0,11809
21
- qulab/scan/record.py,sha256=sICMLAWf3IgvJ3JlS0cKQA4mSpLZins7K5qFoAnCSYI,21194
22
- qulab/scan/scan.py,sha256=KjQyq42xFi9wCjFTu26cXzV5_iii2X0dceqzL1s5w3Q,37834
23
- qulab/scan/server.py,sha256=ZT-J447xuLSpmbTNm5dNIz8_CXKOow66_bjkFNGh_vI,14274
21
+ qulab/scan/record.py,sha256=yIHPANf6nuBXy8Igf-dMtGJ7wuFTLYlBaaAUc0AzwyU,21280
22
+ qulab/scan/scan.py,sha256=GhRhBLWb5PVFi9qkyXJl0p1XXbpjdnjoSz_wPegf9Vs,38783
23
+ qulab/scan/server.py,sha256=tGQ4bVePqvFACb0L26viA57UBD7LkkZI9q5kQGffapA,19112
24
24
  qulab/scan/space.py,sha256=X5anTvw7X0XGN83y0o0C-zhvh6bDEU-kshGgo004pNE,6076
25
25
  qulab/scan/utils.py,sha256=Pg_tCf3SUKTiPSBqb6Enkgx4bAyQJAkDGe9uYys1xVU,3613
26
26
  qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -78,9 +78,9 @@ qulab/visualization/plot_layout.py,sha256=clNw9QjE_kVNpIIx2Ob4YhAz2fucPGMuzkoIrO
78
78
  qulab/visualization/plot_seq.py,sha256=lphYF4VhkEdc_wWr1kFBwrx2yujkyFPFaJ3pjr61awI,2693
79
79
  qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
80
80
  qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
81
- QuLab-2.3.2.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
82
- QuLab-2.3.2.dist-info/METADATA,sha256=mLdXTnca-RyYTycpdpxKFyrFe9UPysvw5sBDoGhqcAg,3510
83
- QuLab-2.3.2.dist-info/WHEEL,sha256=UDBB_KFYXAT_a6Q3uGzMOBYEG2sfuzNdKs9Nu_rq9v4,114
84
- QuLab-2.3.2.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
85
- QuLab-2.3.2.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
86
- QuLab-2.3.2.dist-info/RECORD,,
81
+ QuLab-2.3.3.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
82
+ QuLab-2.3.3.dist-info/METADATA,sha256=DcGCVF8ABDW4sJ8mRhZ2N8jWlYXRA3yvHV6sLt_T24o,3510
83
+ QuLab-2.3.3.dist-info/WHEEL,sha256=UDBB_KFYXAT_a6Q3uGzMOBYEG2sfuzNdKs9Nu_rq9v4,114
84
+ QuLab-2.3.3.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
85
+ QuLab-2.3.3.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
86
+ QuLab-2.3.3.dist-info/RECORD,,
Binary file
qulab/scan/expression.py CHANGED
@@ -419,7 +419,7 @@ class Expression():
419
419
  def is_const(self, env) -> bool:
420
420
  return False
421
421
 
422
- def value(self, env):
422
+ def value(self, env=_default_env):
423
423
  if self.changed(env):
424
424
  self.cache = self.eval(env)
425
425
  return self.cache
@@ -612,3 +612,31 @@ class Symbol(Expression):
612
612
 
613
613
  def __repr__(self) -> str:
614
614
  return self.name
615
+
616
+
617
+ sin = Symbol('sin')
618
+ cos = Symbol('cos')
619
+ tan = Symbol('tan')
620
+ pi = Symbol('pi')
621
+ e = Symbol('e')
622
+ log = Symbol('log')
623
+ log2 = Symbol('log2')
624
+ log10 = Symbol('log10')
625
+ exp = Symbol('exp')
626
+ sqrt = Symbol('sqrt')
627
+ abs = Symbol('abs')
628
+ sinh = Symbol('sinh')
629
+ cosh = Symbol('cosh')
630
+ tanh = Symbol('tanh')
631
+ arcsin = Symbol('arcsin')
632
+ arccos = Symbol('arccos')
633
+ arctan = Symbol('arctan')
634
+ arctan2 = Symbol('arctan2')
635
+ arcsinh = Symbol('arcsinh')
636
+ arccosh = Symbol('arccosh')
637
+ arctanh = Symbol('arctanh')
638
+ sinc = Symbol('sinc')
639
+ sign = Symbol('sign')
640
+ heaviside = Symbol('heaviside')
641
+ erf = Symbol('erf')
642
+ erfc = Symbol('erfc')
qulab/scan/record.py CHANGED
@@ -474,7 +474,9 @@ class Record():
474
474
  'method': 'record_keys',
475
475
  'record_id': self.id
476
476
  })
477
- return socket.recv_pyobj()
477
+ keys = socket.recv_pyobj()
478
+ self._items = {key: None for key in keys}
479
+ return keys
478
480
  else:
479
481
  return list(self._items.keys())
480
482
 
qulab/scan/scan.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import copy
3
4
  import inspect
4
5
  import itertools
@@ -204,6 +205,19 @@ def _run_function_in_process(buf):
204
205
  return func(*args, **kwds)
205
206
 
206
207
 
208
+ def _dump_description(description):
209
+ d = {}
210
+ for key, value in description.items():
211
+ if key in [
212
+ 'intrinsic_loops', 'app', 'tags', 'loops',
213
+ 'independent_variables', 'axis', 'config', 'entry'
214
+ ]:
215
+ d[key] = value
216
+ else:
217
+ d[key] = dill.dumps(value)
218
+ return dill.dumps(description)
219
+
220
+
207
221
  class Scan():
208
222
 
209
223
  def __new__(cls, *args, mixin=None, **kwds):
@@ -331,6 +345,9 @@ class Scan():
331
345
  await _unpack(key, variables)
332
346
  elif inspect.isawaitable(value) and not self.hiden(key):
333
347
  variables[key] = await value
348
+
349
+ if self.record is None:
350
+ self.record = await self.create_record()
334
351
  if self._sock is not None:
335
352
  await self._sock.send_pyobj({
336
353
  'task': self.id,
@@ -374,20 +391,33 @@ class Scan():
374
391
  return True
375
392
 
376
393
  async def create_record(self):
377
- if self._sock is not None:
378
- await self._sock.send_pyobj({
379
- 'task':
380
- self.id,
381
- 'method':
382
- 'record_create',
383
- 'description':
384
- dill.dumps(self.description)
385
- })
394
+ if self._sock is None:
395
+ return Record(None, self.description['database'], self.description)
396
+
397
+ if self.config:
398
+ self.description['config'] = await create_config(
399
+ self.config, self.description['database'], self._sock)
400
+ if current_notebook() is None:
401
+ await create_notebook('untitle', self.description['database'],
402
+ self._sock)
403
+ cell_id = await save_input_cells(current_notebook(),
404
+ self.description['entry']['scripts'],
405
+ self.description['database'],
406
+ self._sock)
407
+ self.description['entry']['scripts'] = cell_id
408
+
409
+ await self._sock.send_pyobj({
410
+ 'task':
411
+ self.id,
412
+ 'method':
413
+ 'record_create',
414
+ 'description':
415
+ _dump_description(self.description)
416
+ })
386
417
 
387
- record_id = await self._sock.recv_pyobj()
388
- return Record(record_id, self.description['database'],
389
- self.description)
390
- return Record(None, self.description['database'], self.description)
418
+ record_id = await self._sock.recv_pyobj()
419
+ return Record(record_id, self.description['database'],
420
+ self.description)
391
421
 
392
422
  def get(self, name: str):
393
423
  if name in self.description['consts']:
@@ -481,7 +511,7 @@ class Scan():
481
511
  elif isinstance(space, OptimizeSpace):
482
512
  space.name = name
483
513
  space.optimizer.dimensions[name] = space.space
484
- if space.suggestion:
514
+ if space.suggestion is not None:
485
515
  space.optimizer.suggestion[name] = space.suggestion
486
516
  self._add_search_space(name, space.optimizer.level, space)
487
517
  self.add_depends(space.optimizer.name, [name])
@@ -584,6 +614,26 @@ class Scan():
584
614
 
585
615
  async def run(self):
586
616
  assymbly(self.description)
617
+
618
+ @contextlib.asynccontextmanager
619
+ async def send_msg_and_update_bar(self):
620
+ send_msg_task = asyncio.create_task(self._send_msg())
621
+ update_progress_task = asyncio.create_task(self._update_progress())
622
+ try:
623
+ yield
624
+ finally:
625
+ update_progress_task.cancel()
626
+ send_msg_task.cancel()
627
+ while True:
628
+ try:
629
+ task = self._prm_queue.get_nowait()
630
+ except:
631
+ break
632
+ try:
633
+ task.cancel()
634
+ except:
635
+ pass
636
+
587
637
  if isinstance(
588
638
  self.description['database'],
589
639
  str) and self.description['database'].startswith("tcp://"):
@@ -591,27 +641,15 @@ class Scan():
591
641
  connect=self.description['database'],
592
642
  socket=self._sock) as socket:
593
643
  self._sock = socket
594
- if self.config:
595
- self.description['config'] = await create_config(
596
- self.config, self.description['database'], self._sock)
597
- if current_notebook() is None:
598
- await create_notebook('untitle',
599
- self.description['database'],
600
- self._sock)
601
- cell_id = await save_input_cells(
602
- current_notebook(), self.description['entry']['scripts'],
603
- self.description['database'], self._sock)
604
- self.description['entry']['scripts'] = cell_id
605
- await self._run()
644
+ async with send_msg_and_update_bar(self):
645
+ await self._run()
606
646
  else:
607
647
  if self.config:
608
648
  self.description['config'] = copy.deepcopy(self.config)
609
- await self._run()
649
+ async with send_msg_and_update_bar(self):
650
+ await self._run()
610
651
 
611
652
  async def _run(self):
612
- send_msg_task = asyncio.create_task(self._send_msg())
613
- update_progress_task = asyncio.create_task(self._update_progress())
614
-
615
653
  self._variables = {'self': self, 'config': self.config}
616
654
 
617
655
  consts = {}
@@ -634,7 +672,6 @@ class Scan():
634
672
  await update_variables(self.variables, updates,
635
673
  self.description['setters'])
636
674
 
637
- self.record = await self.create_record()
638
675
  await self.work()
639
676
  for level, bar in self._bar.items():
640
677
  bar.close()
@@ -648,9 +685,7 @@ class Scan():
648
685
  await self.emit(-1, 0, 0, {})
649
686
 
650
687
  await self._prm_queue.join()
651
- update_progress_task.cancel()
652
688
  await self._msg_queue.join()
653
- send_msg_task.cancel()
654
689
  return self.variables
655
690
 
656
691
  async def done(self):
@@ -776,10 +811,10 @@ class Scan():
776
811
  buf = dill.dumps((awaitable, args, kwds))
777
812
  task = asyncio.get_running_loop().run_in_executor(
778
813
  self._executors, _run_function_in_process, buf)
779
- self._prm_queue.put_nowait(task)
780
- return Promise(task)
781
814
  except:
782
815
  return awaitable(*args, **kwds)
816
+ self._prm_queue.put_nowait(task)
817
+ return Promise(task)
783
818
  else:
784
819
  return awaitable
785
820
 
qulab/scan/server.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import asyncio
2
2
  import os
3
3
  import pickle
4
+ import subprocess
5
+ import sys
4
6
  import time
5
7
  import uuid
6
8
  from pathlib import Path
@@ -59,33 +61,45 @@ def clear_cache():
59
61
  if len(record_cache) < CACHE_SIZE:
60
62
  return
61
63
 
64
+ logger.debug(f"clear_cache record_cache: {len(record_cache)}")
62
65
  for k, (t, _) in zip(sorted(record_cache.items(), key=lambda x: x[1][0]),
63
66
  range(len(record_cache) - CACHE_SIZE)):
64
67
  del record_cache[k]
65
68
 
69
+ logger.debug(f"clear_cache buffer_list_cache: {len(buffer_list_cache)}")
66
70
  for k, (t,
67
71
  _) in zip(sorted(buffer_list_cache.items(), key=lambda x: x[1][0]),
68
72
  range(len(buffer_list_cache) - CACHE_SIZE)):
69
73
  del buffer_list_cache[k]
74
+ logger.debug(f"clear_cache done.")
70
75
 
71
76
 
72
77
  def flush_cache():
78
+ logger.debug(f"flush_cache: {len(record_cache)}")
73
79
  for k, (t, r) in record_cache.items():
74
80
  r.flush()
81
+ logger.debug(f"flush_cache done.")
75
82
 
76
83
 
77
84
  def get_local_record(session: Session, id: int, datapath: Path) -> Record:
85
+ logger.debug(f"get_local_record: {id}")
78
86
  record_in_db = session.get(RecordInDB, id)
79
87
  if record_in_db is None:
88
+ logger.debug(f"record not found: {id=}")
80
89
  return None
81
90
  record_in_db.atime = utcnow()
82
91
 
83
92
  if record_in_db.file.endswith('.zip'):
84
- return Record.load(datapath / 'objects' / record_in_db.file)
93
+ logger.debug(f"load record from zip: {record_in_db.file}")
94
+ record = Record.load(datapath / 'objects' / record_in_db.file)
95
+ logger.debug(f"load record from zip done.")
96
+ return record
85
97
 
86
98
  path = datapath / 'objects' / record_in_db.file
87
99
  with open(path, 'rb') as f:
100
+ logger.debug(f"load record from file: {path}")
88
101
  record = dill.load(f)
102
+ logger.debug(f"load record from file done.")
89
103
  record.database = datapath
90
104
  record._file = path
91
105
  return record
@@ -95,13 +109,16 @@ def get_record(session: Session, id: int, datapath: Path) -> Record:
95
109
  if id not in record_cache:
96
110
  record = get_local_record(session, id, datapath)
97
111
  else:
112
+ logger.debug(f"get_record from cache: {id=}")
98
113
  record = record_cache[id][1]
99
114
  clear_cache()
115
+ logger.debug(f"update lru time for record cache: {id=}")
100
116
  record_cache[id] = time.time(), record
101
117
  return record
102
118
 
103
119
 
104
120
  def record_create(session: Session, description: dict, datapath: Path) -> int:
121
+ logger.debug(f"record_create: {description['app']}")
105
122
  record = Record(None, datapath, description)
106
123
  record_in_db = RecordInDB()
107
124
  if 'app' in description:
@@ -111,28 +128,39 @@ def record_create(session: Session, description: dict, datapath: Path) -> int:
111
128
  record_in_db.file = '/'.join(record._file.parts[-4:])
112
129
  record_in_db.config_id = description['config']
113
130
  record._file = datapath / 'objects' / record_in_db.file
131
+ logger.debug(f"record_create generate random file: {record_in_db.file}")
114
132
  session.add(record_in_db)
115
133
  try:
116
134
  session.commit()
135
+ logger.debug(f"record_create commited: record.id={record_in_db.id}")
117
136
  record.id = record_in_db.id
118
137
  clear_cache()
119
138
  record_cache[record.id] = time.time(), record
120
139
  return record.id
121
140
  except:
141
+ logger.debug(f"record_create rollback")
122
142
  session.rollback()
123
143
  raise
124
144
 
125
145
 
126
146
  def record_append(session: Session, record_id: int, level: int, step: int,
127
147
  position: int, variables: dict, datapath: Path):
148
+ logger.debug(f"record_append: {record_id}")
128
149
  record = get_record(session, record_id, datapath)
150
+ logger.debug(f"record_append: {record_id}, {level}, {step}, {position}")
129
151
  record.append(level, step, position, variables)
152
+ logger.debug(f"record_append done.")
130
153
  try:
154
+ logger.debug(f"record_append update SQL database.")
131
155
  record_in_db = session.get(RecordInDB, record_id)
156
+ logger.debug(f"record_append get RecordInDB: {record_in_db}")
132
157
  record_in_db.mtime = utcnow()
133
158
  record_in_db.atime = utcnow()
159
+ logger.debug(f"record_append update RecordInDB: {record_in_db}")
134
160
  session.commit()
161
+ logger.debug(f"record_append commited.")
135
162
  except:
163
+ logger.debug(f"record_append rollback.")
136
164
  session.rollback()
137
165
  raise
138
166
 
@@ -150,11 +178,15 @@ async def handle(session: Session, request: Request, datapath: Path):
150
178
 
151
179
  msg = request.msg
152
180
 
181
+ if request.method not in ['ping']:
182
+ logger.debug(f"handle: {request.method}")
183
+
153
184
  match request.method:
154
185
  case 'ping':
155
186
  await reply(request, 'pong')
156
187
  case 'bufferlist_iter':
157
- if msg['iter_id'] in buffer_list_cache:
188
+ logger.debug(f"bufferlist_iter: {msg}")
189
+ if msg['iter_id'] and msg['iter_id'] in buffer_list_cache:
158
190
  it = buffer_list_cache[msg['iter_id']][1]
159
191
  iter_id = msg['iter_id']
160
192
  else:
@@ -174,22 +206,30 @@ async def handle(session: Session, request: Request, datapath: Path):
174
206
  except StopIteration:
175
207
  end = True
176
208
  break
209
+ logger.debug(f"bufferlist_iter: {iter_id}, {end}")
177
210
  await reply(request, (iter_id, ret, end))
211
+ logger.debug(f"reply bufferlist_iter: {iter_id}, {end}")
178
212
  buffer_list_cache[iter_id] = time.time(), it
179
213
  clear_cache()
180
214
  case 'bufferlist_iter_exit':
215
+ logger.debug(f"bufferlist_iter_exit: {msg}")
181
216
  try:
182
217
  it = buffer_list_cache.pop(msg['iter_id'])[1]
183
218
  it.throw(Exception)
184
219
  except:
185
220
  pass
186
221
  clear_cache()
222
+ logger.debug(f"end bufferlist_iter_exit: {msg}")
187
223
  case 'record_create':
224
+ logger.debug(f"record_create")
188
225
  description = dill.loads(msg['description'])
189
226
  await reply(request, record_create(session, description, datapath))
227
+ logger.debug(f"reply record_create")
190
228
  case 'record_append':
229
+ logger.debug(f"record_append")
191
230
  record_append(session, msg['record_id'], msg['level'], msg['step'],
192
231
  msg['position'], msg['variables'], datapath)
232
+ logger.debug(f"reply record_append")
193
233
  case 'record_description':
194
234
  record = get_record(session, msg['record_id'], datapath)
195
235
  await reply(request, dill.dumps(record))
@@ -295,6 +335,9 @@ async def handle(session: Session, request: Request, datapath: Path):
295
335
  case _:
296
336
  logger.error(f"Unknown method: {msg['method']}")
297
337
 
338
+ if request.method not in ['ping']:
339
+ logger.debug(f"finished handle: {request.method}")
340
+
298
341
 
299
342
  async def handle_with_timeout(session: Session, request: Request,
300
343
  datapath: Path, timeout: float):
@@ -311,22 +354,41 @@ async def handle_with_timeout(session: Session, request: Request,
311
354
 
312
355
  async def serv(port,
313
356
  datapath,
314
- url=None,
357
+ url='',
315
358
  buffer_size=1024 * 1024 * 1024,
316
- interval=60):
317
- logger.info('Server starting.')
359
+ interval=60,
360
+ log='stderr',
361
+ debug=False):
362
+ if debug:
363
+ level = 'DEBUG'
364
+ else:
365
+ level = 'INFO'
366
+
367
+ if log == 'stderr':
368
+ pass
369
+ #logger.add(sys.stderr, level=level)
370
+ elif log == 'stdout':
371
+ logger.add(sys.stdout, level=level)
372
+ else:
373
+ logger.add(log, level=level)
374
+
375
+ logger.debug('Creating socket...')
318
376
  async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
319
- if url is None:
377
+ logger.info(f'Server started at port {port}.')
378
+ logger.info(f'Data path: {datapath}.')
379
+ if not url or url == 'sqlite':
320
380
  url = 'sqlite:///' + str(datapath / 'data.db')
321
381
  engine = create_engine(url)
322
382
  create_tables(engine)
323
383
  Session = sessionmaker(engine)
324
384
  with Session() as session:
325
- logger.info('Server started.')
385
+ logger.info(f'Database connected: {url}.')
326
386
  received = 0
327
387
  last_flush_time = time.time()
328
388
  while True:
389
+ logger.debug('Waiting for request...')
329
390
  identity, msg = await sock.recv_multipart()
391
+ logger.debug('Received request.')
330
392
  received += len(msg)
331
393
  try:
332
394
  req = Request(sock, identity, msg)
@@ -334,7 +396,8 @@ async def serv(port,
334
396
  logger.exception('bad request')
335
397
  continue
336
398
  asyncio.create_task(
337
- handle_with_timeout(session, req, datapath, timeout=60.0))
399
+ handle_with_timeout(session, req, datapath,
400
+ timeout=3600.0))
338
401
  if received > buffer_size or time.time(
339
402
  ) - last_flush_time > interval:
340
403
  flush_cache()
@@ -342,46 +405,94 @@ async def serv(port,
342
405
  last_flush_time = time.time()
343
406
 
344
407
 
345
- async def watch(port, datapath, url=None, timeout=1, buffer=1024, interval=60):
346
- with ZMQContextManager(zmq.DEALER,
347
- connect=f"tcp://127.0.0.1:{port}") as sock:
348
- sock.setsockopt(zmq.LINGER, 0)
408
+ async def main(port,
409
+ datapath,
410
+ url,
411
+ timeout=1,
412
+ buffer=1024,
413
+ interval=60,
414
+ log='stderr',
415
+ no_watch=True,
416
+ debug=False):
417
+ if no_watch:
418
+ logger.info('Server starting...')
419
+ await serv(port, datapath, url, buffer * 1024 * 1024, interval, log,
420
+ debug)
421
+ else:
422
+ process = None
423
+
349
424
  while True:
350
425
  try:
351
- sock.send_pyobj({"method": "ping"})
352
- if sock.poll(int(1000 * timeout)):
353
- sock.recv()
354
- else:
355
- raise asyncio.TimeoutError()
426
+ with ZMQContextManager(
427
+ zmq.DEALER, connect=f"tcp://127.0.0.1:{port}") as sock:
428
+ sock.setsockopt(zmq.LINGER, 0)
429
+ sock.send_pyobj({"method": "ping"})
430
+ logger.debug('ping.')
431
+ if sock.poll(int(1000 * timeout)):
432
+ sock.recv()
433
+ logger.debug('recv pong.')
434
+ else:
435
+ logger.debug('timeout.')
436
+ raise asyncio.TimeoutError()
356
437
  except (zmq.error.ZMQError, asyncio.TimeoutError):
357
- return asyncio.create_task(
358
- serv(port, datapath, url, buffer * 1024 * 1024, interval))
438
+ if process is not None:
439
+ logger.debug(
440
+ f'killing process... PID={process.pid}, returncode={process.returncode}'
441
+ )
442
+ process.kill()
443
+ logger.debug(
444
+ f'killed process. PID={process.pid}, returncode={process.returncode}'
445
+ )
446
+ cmd = [
447
+ sys.executable, "-m", "qulab", "server", "--port",
448
+ f"{port}", "--datapath", f"{datapath}", "--url", f"{url}",
449
+ "--timeout", f"{timeout}", "--buffer", f"{buffer}",
450
+ "--interval", f"{interval}", "--log", f"{log}",
451
+ ]
452
+ if url:
453
+ cmd.extend(['--url', url])
454
+ if debug:
455
+ cmd.append('--debug')
456
+ cmd.append("--no-watch")
457
+ logger.debug(f"starting process: {' '.join(cmd)}")
458
+ process = subprocess.Popen(cmd,
459
+ stdout=subprocess.PIPE,
460
+ stderr=subprocess.PIPE,
461
+ cwd=os.getcwd())
462
+ logger.debug(
463
+ f'process started. PID={process.pid}, returncode={process.returncode}'
464
+ )
465
+
466
+ # Capture and log the output
467
+ # stdout, stderr = process.communicate(timeout=5)
468
+ # if stdout:
469
+ # logger.info(f'Server stdout: {stdout.decode()}')
470
+ # if stderr:
471
+ # logger.error(f'Server stderr: {stderr.decode()}')
472
+
473
+ await asyncio.sleep(5)
359
474
  await asyncio.sleep(timeout)
360
475
 
361
476
 
362
- async def main(port, datapath, url, timeout=1, buffer=1024, interval=60):
363
- task = await watch(port=port,
364
- datapath=datapath,
365
- url=url,
366
- timeout=timeout,
367
- buffer=buffer,
368
- interval=interval)
369
- await task
370
-
371
-
372
477
  @click.command()
373
478
  @click.option('--port',
374
479
  default=os.getenv('QULAB_RECORD_PORT', 6789),
375
480
  help='Port of the server.')
376
481
  @click.option('--datapath', default=datapath, help='Path of the data.')
377
- @click.option('--url', default=None, help='URL of the database.')
482
+ @click.option('--url', default='sqlite', help='URL of the database.')
378
483
  @click.option('--timeout', default=1, help='Timeout of ping.')
379
484
  @click.option('--buffer', default=1024, help='Buffer size (MB).')
380
485
  @click.option('--interval',
381
486
  default=60,
382
487
  help='Interval of flush cache, in unit of second.')
383
- def server(port, datapath, url, timeout, buffer, interval):
384
- asyncio.run(main(port, Path(datapath), url, timeout, buffer, interval))
488
+ @click.option('--log', default='stderr', help='Log file.')
489
+ @click.option('--no-watch', is_flag=True, help='Watch the server.')
490
+ @click.option('--debug', is_flag=True, help='Debug mode.')
491
+ def server(port, datapath, url, timeout, buffer, interval, log, no_watch,
492
+ debug):
493
+ asyncio.run(
494
+ main(port, Path(datapath), url, timeout, buffer, interval, log,
495
+ True, debug))
385
496
 
386
497
 
387
498
  if __name__ == "__main__":
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.3.2"
1
+ __version__ = "2.3.3"
File without changes
File without changes