pychemstation 0.10.7__py3-none-any.whl → 0.10.8__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.
Files changed (31) hide show
  1. pychemstation/analysis/base_spectrum.py +14 -15
  2. pychemstation/analysis/chromatogram.py +7 -8
  3. pychemstation/analysis/process_report.py +10 -16
  4. pychemstation/control/README.md +1 -1
  5. pychemstation/control/controllers/__init__.py +2 -1
  6. pychemstation/control/controllers/comm.py +33 -27
  7. pychemstation/control/controllers/data_aq/method.py +233 -79
  8. pychemstation/control/controllers/data_aq/sequence.py +104 -84
  9. pychemstation/control/controllers/devices/injector.py +3 -3
  10. pychemstation/control/hplc.py +53 -41
  11. pychemstation/utils/__init__.py +23 -0
  12. pychemstation/{control/controllers → utils}/abc_tables/abc_comm.py +15 -13
  13. pychemstation/{control/controllers → utils}/abc_tables/device.py +9 -2
  14. pychemstation/{control/controllers → utils}/abc_tables/run.py +45 -37
  15. pychemstation/{control/controllers → utils}/abc_tables/table.py +32 -29
  16. pychemstation/utils/macro.py +7 -2
  17. pychemstation/utils/method_types.py +13 -14
  18. pychemstation/utils/mocking/mock_comm.py +25 -2
  19. pychemstation/utils/mocking/mock_hplc.py +29 -1
  20. pychemstation/utils/num_utils.py +3 -3
  21. pychemstation/utils/sequence_types.py +30 -14
  22. pychemstation/utils/spec_utils.py +42 -66
  23. pychemstation/utils/table_types.py +15 -2
  24. pychemstation/utils/tray_types.py +28 -16
  25. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/METADATA +1 -7
  26. pychemstation-0.10.8.dist-info/RECORD +41 -0
  27. pychemstation/utils/pump_types.py +0 -7
  28. pychemstation-0.10.7.dist-info/RECORD +0 -42
  29. /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
  30. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/licenses/LICENSE +0 -0
@@ -3,18 +3,19 @@ from __future__ import annotations
3
3
  import os
4
4
  import time
5
5
  import warnings
6
- from typing import List, Optional, Union, Dict, Set
6
+ from typing import Dict, List, Optional, Union, Tuple
7
7
 
8
8
  from result import Err, Ok, Result
9
9
 
10
- from ..abc_tables.run import RunController
11
- from ....analysis.process_report import AgilentReport, ReportType
12
- from ....control.controllers import CommunicationController
13
10
  from pychemstation.analysis.chromatogram import (
14
11
  TIME_FORMAT,
15
12
  AgilentChannelChromatogramData,
16
13
  AgilentHPLCChromatogram,
17
14
  )
15
+
16
+ from ....analysis.process_report import AgilentReport, ReportType
17
+ from ....control.controllers import CommunicationController
18
+ from ....utils.abc_tables.run import RunController
18
19
  from ....utils.macro import Command
19
20
  from ....utils.method_types import (
20
21
  HPLCMethodParams,
@@ -23,20 +24,18 @@ from ....utils.method_types import (
23
24
  PType,
24
25
  TimeTableEntry,
25
26
  )
26
- from ....utils.table_types import RegisterFlag, Table, TableOperation, T
27
+ from ....utils.table_types import RegisterFlag, T, Table, TableOperation
27
28
  from ..devices.injector import InjectorController
28
29
 
29
30
 
30
31
  class MethodController(RunController):
31
- """
32
- Class containing method related logic
33
- """
32
+ """Class containing method related logic."""
34
33
 
35
34
  def __init__(
36
35
  self,
37
- controller: CommunicationController,
38
- src: str,
39
- data_dirs: List[str],
36
+ controller: Optional[CommunicationController],
37
+ src: Optional[str],
38
+ data_dirs: Optional[List[str]],
40
39
  table: Table,
41
40
  offline: bool,
42
41
  injector_controller: InjectorController,
@@ -51,10 +50,8 @@ class MethodController(RunController):
51
50
  offline=offline,
52
51
  )
53
52
 
54
- def check(self) -> str:
55
- time.sleep(2)
56
- self.send(Command.GET_METHOD_CMD)
57
- time.sleep(2)
53
+ def get_current_method_name(self) -> str:
54
+ self.sleepy_send(Command.GET_METHOD_CMD)
58
55
  res = self.receive()
59
56
  if res.is_ok():
60
57
  return res.ok_value.string_response
@@ -79,26 +76,22 @@ class MethodController(RunController):
79
76
  raise ValueError("Communication controller is offline!")
80
77
 
81
78
  def get_row(self, row: int) -> TimeTableEntry:
82
- flow = None
83
- om = None
84
-
85
- try:
86
- flow = self.get_num(row, RegisterFlag.TIMETABLE_FLOW)
87
- except RuntimeError:
88
- pass
89
- try:
90
- om = self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION)
91
- except RuntimeError:
92
- pass
93
-
94
- if om is None and flow is None:
95
- raise ValueError("Both flow and organic modifier is None")
96
-
97
- return TimeTableEntry(
98
- start_time=self.get_num(row, RegisterFlag.TIME),
99
- organic_modifer=om,
100
- flow=flow,
101
- )
79
+ function = self.get_text(row, RegisterFlag.FUNCTION)
80
+ if function == RegisterFlag.FLOW.value:
81
+ return TimeTableEntry(
82
+ start_time=self.get_num(row, RegisterFlag.TIME),
83
+ organic_modifer=None,
84
+ flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW),
85
+ )
86
+ if function == RegisterFlag.SOLVENT_COMPOSITION.value:
87
+ return TimeTableEntry(
88
+ start_time=self.get_num(row, RegisterFlag.TIME),
89
+ organic_modifer=self.get_num(
90
+ row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION
91
+ ),
92
+ flow=None,
93
+ )
94
+ raise ValueError("Both flow and organic modifier are empty")
102
95
 
103
96
  def get_timetable(self, rows: int):
104
97
  uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
@@ -197,7 +190,6 @@ class MethodController(RunController):
197
190
  method_dir=method_dir, method_name=method_name
198
191
  )
199
192
  )
200
-
201
193
  time.sleep(2)
202
194
  self.send(Command.GET_METHOD_CMD)
203
195
  time.sleep(2)
@@ -221,6 +213,7 @@ class MethodController(RunController):
221
213
  new_stop_time=updated_method.stop_time,
222
214
  new_post_time=updated_method.post_time,
223
215
  )
216
+ self.validate_timetable(updated_method.timetable)
224
217
  self.edit_method_timetable(updated_method.timetable)
225
218
 
226
219
  if save:
@@ -231,6 +224,7 @@ class MethodController(RunController):
231
224
  )
232
225
 
233
226
  def edit_initial_om(self, new_om: Union[int, float]):
227
+ self._validate_organic_modifier(new_om)
234
228
  initial_organic_modifier: Param = Param(
235
229
  val=new_om,
236
230
  chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
@@ -238,13 +232,31 @@ class MethodController(RunController):
238
232
  )
239
233
  self._update_param(initial_organic_modifier)
240
234
 
235
+ def _validate_organic_modifier(self, new_om):
236
+ if not (isinstance(new_om, int) or isinstance(new_om, float)):
237
+ raise ValueError("Organic modifier must be int or float")
238
+ if new_om < 0:
239
+ raise ValueError("Organic modifier must be positive")
240
+ if new_om > 100:
241
+ raise ValueError("Organic modifer must be less than 100.")
242
+
241
243
  def edit_flow(self, new_flow: Union[int, float]):
244
+ self._validate_flow(new_flow)
242
245
  flow: Param = Param(
243
246
  val=new_flow, chemstation_key=RegisterFlag.FLOW, ptype=PType.NUM
244
247
  )
245
248
  self._update_param(flow)
246
249
 
250
+ def _validate_flow(self, new_flow):
251
+ if not (isinstance(new_flow, int) or isinstance(new_flow, float)):
252
+ raise ValueError("Flow must be int or float")
253
+ if new_flow < 0:
254
+ raise ValueError("Flow must be positive")
255
+ if new_flow >= 5.0:
256
+ raise ValueError("Flow must be less than 5.0")
257
+
247
258
  def edit_stop_time(self, new_stop_time: Union[int, float]):
259
+ self.validate_stop_time(new_stop_time)
248
260
  stop_time: Param = Param(
249
261
  val=new_stop_time,
250
262
  chemstation_key=RegisterFlag.MAX_TIME,
@@ -257,7 +269,14 @@ class MethodController(RunController):
257
269
  )
258
270
  self._update_param(stop_time)
259
271
 
272
+ def validate_stop_time(self, new_stop_time):
273
+ if not (isinstance(new_stop_time, int) or isinstance(new_stop_time, float)):
274
+ raise ValueError("Stop time must be int or float")
275
+ if new_stop_time < 0:
276
+ raise ValueError("Stop time must be positive")
277
+
260
278
  def edit_post_time(self, new_post_time: Union[int, float]):
279
+ self.validate_post_time(new_post_time)
261
280
  post_time: Param = Param(
262
281
  val=new_post_time,
263
282
  chemstation_key=RegisterFlag.POST_TIME,
@@ -268,6 +287,12 @@ class MethodController(RunController):
268
287
  )
269
288
  self._update_param(post_time)
270
289
 
290
+ def validate_post_time(self, new_post_time):
291
+ if not (isinstance(new_post_time, int) or isinstance(new_post_time, float)):
292
+ raise ValueError("Post time must be int or float")
293
+ if new_post_time < 0:
294
+ raise ValueError("Post time must be positive")
295
+
271
296
  def update_method_params(
272
297
  self,
273
298
  new_flow: Union[int, float],
@@ -328,59 +353,151 @@ class MethodController(RunController):
328
353
  val=method_param.val,
329
354
  )
330
355
  )
331
- time.sleep(2)
332
356
  self.download()
333
357
 
334
358
  def download(self):
335
- self.send("Sleep 1")
336
359
  self.sleepy_send("DownloadRCMethod PMP1")
337
- self.send("Sleep 1")
338
360
 
339
- def _edit_row(self, row: TimeTableEntry, first_row: bool = False):
340
- if first_row:
341
- if row.organic_modifer:
342
- self.add_row()
343
- self.add_new_col_text(
344
- col_name=RegisterFlag.FUNCTION,
345
- val=RegisterFlag.SOLVENT_COMPOSITION.value,
346
- )
361
+ def _edit_row(
362
+ self,
363
+ row: TimeTableEntry,
364
+ first_row: bool,
365
+ time_added: bool,
366
+ flow_added: bool,
367
+ om_added: bool,
368
+ function_added: bool,
369
+ ) -> Tuple[bool, bool, bool, bool]:
370
+ def add_time():
371
+ nonlocal time_added
372
+ nonlocal first_row
373
+ if not time_added:
347
374
  self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
375
+ time_added = True
376
+ elif not first_row:
377
+ self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
378
+
379
+ def add_flow():
380
+ nonlocal flow_added
381
+ nonlocal function_added
382
+ if not flow_added:
383
+ if not function_added:
384
+ self.add_new_col_text(
385
+ col_name=RegisterFlag.FUNCTION,
386
+ val=RegisterFlag.FLOW.value,
387
+ )
388
+ function_added = True
389
+ else:
390
+ self._edit_row_text(
391
+ col_name=RegisterFlag.FUNCTION,
392
+ val=RegisterFlag.FLOW.value,
393
+ )
348
394
  self.add_new_col_num(
349
- col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
350
- val=row.organic_modifer,
395
+ col_name=RegisterFlag.TIMETABLE_FLOW,
396
+ val=row.flow,
351
397
  )
352
- if row.flow:
353
- self.add_row()
354
- self.get_num_rows()
398
+ flow_added = True
399
+ else:
355
400
  self._edit_row_text(
356
401
  col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
357
402
  )
358
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
359
403
  self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
360
- self.download()
361
- else:
362
- if row.organic_modifer:
363
- self.add_row()
364
- self.get_num_rows()
404
+
405
+ def add_om():
406
+ nonlocal om_added
407
+ nonlocal function_added
408
+ if not om_added:
409
+ if not function_added:
410
+ self.add_new_col_text(
411
+ col_name=RegisterFlag.FUNCTION,
412
+ val=RegisterFlag.SOLVENT_COMPOSITION.value,
413
+ )
414
+ function_added = True
415
+ else:
416
+ self._edit_row_text(
417
+ col_name=RegisterFlag.FUNCTION,
418
+ val=RegisterFlag.SOLVENT_COMPOSITION.value,
419
+ )
420
+ self.add_new_col_num(
421
+ col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
422
+ val=row.organic_modifer,
423
+ )
424
+ om_added = True
425
+ else:
365
426
  self._edit_row_text(
366
427
  col_name=RegisterFlag.FUNCTION,
367
428
  val=RegisterFlag.SOLVENT_COMPOSITION.value,
368
429
  )
369
- self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
370
430
  self._edit_row_num(
371
431
  col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
372
432
  val=row.organic_modifer,
373
433
  )
374
- self.download()
375
- if row.flow:
376
- self.add_row()
377
- self.get_num_rows()
378
- self._edit_row_text(
379
- col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
380
- )
381
- self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
382
- self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
383
- self.download()
434
+
435
+ if row.organic_modifer:
436
+ self.add_row()
437
+ add_om()
438
+ add_time()
439
+ if row.flow:
440
+ self.add_row()
441
+ add_flow()
442
+ add_time()
443
+ self.download()
444
+ return time_added, flow_added, om_added, function_added
445
+
446
+ # if first_row:
447
+ # time_added = False
448
+ # flow_row_method: Callable = (
449
+ # self.add_new_col_text
450
+ # if row.flow and not row.organic_modifer
451
+ # else self._edit_row_text
452
+ # )
453
+ # if row.organic_modifer:
454
+ # self.add_row()
455
+ # self.add_new_col_text(
456
+ # col_name=RegisterFlag.FUNCTION,
457
+ # val=RegisterFlag.SOLVENT_COMPOSITION.value,
458
+ # )
459
+ # if not time_added:
460
+ # time_added = True
461
+ # self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
462
+ # self.add_new_col_num(
463
+ # col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
464
+ # val=row.organic_modifer,
465
+ # )
466
+ # if row.flow:
467
+ # self.add_row()
468
+ # self.get_num_rows()
469
+ # flow_row_method(
470
+ # col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
471
+ # )
472
+ # if not time_added:
473
+ # time_added = True
474
+ # self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
475
+ # self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
476
+ # self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
477
+ # self.download()
478
+ # else:
479
+ # if row.organic_modifer:
480
+ # self.add_row()
481
+ # self.get_num_rows()
482
+ # self._edit_row_text(
483
+ # col_name=RegisterFlag.FUNCTION,
484
+ # val=RegisterFlag.SOLVENT_COMPOSITION.value,
485
+ # )
486
+ # self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
487
+ # self._edit_row_num(
488
+ # col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
489
+ # val=row.organic_modifer,
490
+ # )
491
+ # self.download()
492
+ # if row.flow:
493
+ # self.add_row()
494
+ # self.get_num_rows()
495
+ # self._edit_row_text(
496
+ # col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
497
+ # )
498
+ # self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
499
+ # self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
500
+ # self.download()
384
501
 
385
502
  def edit_method_timetable(self, timetable_rows: List[TimeTableEntry]):
386
503
  self.get_num_rows()
@@ -391,10 +508,23 @@ class MethodController(RunController):
391
508
  res = self.get_num_rows()
392
509
 
393
510
  self.new_table()
394
- self.get_num_rows()
395
-
511
+ num_rows = self.get_num_rows()
512
+ if num_rows.ok_value.num_response != 0:
513
+ raise ValueError("Should be zero rows!")
514
+
515
+ time_added = False
516
+ flow_added = False
517
+ om_added = False
518
+ function_added = False
396
519
  for i, row in enumerate(timetable_rows):
397
- self._edit_row(row=row, first_row=i == 0)
520
+ time_added, flow_added, om_added, function_added = self._edit_row(
521
+ row=row,
522
+ first_row=i == 0,
523
+ time_added=time_added,
524
+ flow_added=flow_added,
525
+ om_added=om_added,
526
+ function_added=function_added,
527
+ )
398
528
 
399
529
  def stop(self):
400
530
  """
@@ -441,23 +571,25 @@ class MethodController(RunController):
441
571
  if run_completed.is_ok():
442
572
  self.data_files[-1] = run_completed.ok_value
443
573
  else:
444
- raise RuntimeError(f"Run error has occurred:{run_completed.err_value}.")
574
+ warnings.warn(run_completed.err_value)
445
575
  else:
446
- folder = self.fuzzy_match_most_recent_folder(self.data_files[-1], None)
447
- while folder.is_err():
448
- folder = self.fuzzy_match_most_recent_folder(self.data_files[-1], None)
576
+ folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
577
+ i = 0
578
+ while folder.is_err() and i < 10:
579
+ folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
580
+ i += 1
449
581
  if folder.is_ok():
450
582
  self.data_files[-1] = folder.ok_value
451
583
  else:
452
584
  warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
453
585
  warnings.warn(warning)
454
586
 
455
- def fuzzy_match_most_recent_folder(
456
- self, most_recent_folder: T, child_dirs: Optional[Set[str]]
587
+ def _fuzzy_match_most_recent_folder(
588
+ self, most_recent_folder: T
457
589
  ) -> Result[str, str]:
458
590
  if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
459
591
  if os.path.exists(most_recent_folder):
460
- return Ok(most_recent_folder)
592
+ return Ok(str(most_recent_folder))
461
593
  return Err("Folder not found!")
462
594
  raise ValueError("Folder is not a str or byte type.")
463
595
 
@@ -490,3 +622,25 @@ class MethodController(RunController):
490
622
  if len(possible_data.x) > 0:
491
623
  signal.data = possible_data
492
624
  return [metd_report]
625
+
626
+ def _validate_row(self, row: TimeTableEntry):
627
+ if not (row.flow or row.organic_modifer):
628
+ raise ValueError(
629
+ "Require one of flow or organic modifier for the method timetable entry!"
630
+ )
631
+ if row.flow:
632
+ self._validate_flow(row.flow)
633
+ if row.organic_modifer:
634
+ self._validate_organic_modifier(row.organic_modifer)
635
+
636
+ def validate_timetable(self, timetable: List[TimeTableEntry]):
637
+ start_time = 0.0
638
+ for i, row in enumerate(timetable):
639
+ if row.start_time > start_time:
640
+ start_time = row.start_time
641
+ elif row.start_time <= start_time:
642
+ raise ValueError(
643
+ f"""Every row's start time must be larger than the previous start time.
644
+ Row {i + 1} ({timetable[i].start_time}) has a smaller or equal starttime than row {i} ({start_time})"""
645
+ )
646
+ self._validate_row(row)