pychemstation 0.10.8.dev1__py3-none-any.whl → 0.10.8.dev3__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.
@@ -3,17 +3,18 @@ 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, Set, Union, Tuple
7
7
 
8
8
  from result import Err, Ok, Result
9
9
 
10
- from ....analysis.process_report import AgilentReport, ReportType
11
- from ....control.controllers import CommunicationController
12
10
  from pychemstation.analysis.chromatogram import (
13
11
  TIME_FORMAT,
14
12
  AgilentChannelChromatogramData,
15
13
  AgilentHPLCChromatogram,
16
14
  )
15
+
16
+ from ....analysis.process_report import AgilentReport, ReportType
17
+ from ....control.controllers import CommunicationController
17
18
  from ....utils.abc_tables.run import RunController
18
19
  from ....utils.macro import Command
19
20
  from ....utils.method_types import (
@@ -23,7 +24,7 @@ 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
 
@@ -77,26 +78,22 @@ class MethodController(RunController):
77
78
  raise ValueError("Communication controller is offline!")
78
79
 
79
80
  def get_row(self, row: int) -> TimeTableEntry:
80
- flow = None
81
- om = None
82
-
83
- try:
84
- flow = self.get_num(row, RegisterFlag.TIMETABLE_FLOW)
85
- except RuntimeError:
86
- pass
87
- try:
88
- om = self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION)
89
- except RuntimeError:
90
- pass
91
-
92
- if om is None and flow is None:
93
- raise ValueError("Both flow and organic modifier is None")
94
-
95
- return TimeTableEntry(
96
- start_time=self.get_num(row, RegisterFlag.TIME),
97
- organic_modifer=om,
98
- flow=flow,
99
- )
81
+ function = self.get_text(row, RegisterFlag.FUNCTION)
82
+ if function == RegisterFlag.FLOW.value:
83
+ return TimeTableEntry(
84
+ start_time=self.get_num(row, RegisterFlag.TIME),
85
+ organic_modifer=None,
86
+ flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW),
87
+ )
88
+ if function == RegisterFlag.SOLVENT_COMPOSITION.value:
89
+ return TimeTableEntry(
90
+ start_time=self.get_num(row, RegisterFlag.TIME),
91
+ organic_modifer=self.get_num(
92
+ row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION
93
+ ),
94
+ flow=None,
95
+ )
96
+ raise ValueError("Both flow and organic modifier are empty")
100
97
 
101
98
  def get_timetable(self, rows: int):
102
99
  uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
@@ -219,6 +216,7 @@ class MethodController(RunController):
219
216
  new_stop_time=updated_method.stop_time,
220
217
  new_post_time=updated_method.post_time,
221
218
  )
219
+ self.validate_timetable(updated_method.timetable)
222
220
  self.edit_method_timetable(updated_method.timetable)
223
221
 
224
222
  if save:
@@ -229,6 +227,7 @@ class MethodController(RunController):
229
227
  )
230
228
 
231
229
  def edit_initial_om(self, new_om: Union[int, float]):
230
+ self._validate_organic_modifier(new_om)
232
231
  initial_organic_modifier: Param = Param(
233
232
  val=new_om,
234
233
  chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
@@ -236,13 +235,31 @@ class MethodController(RunController):
236
235
  )
237
236
  self._update_param(initial_organic_modifier)
238
237
 
238
+ def _validate_organic_modifier(self, new_om):
239
+ if not (isinstance(new_om, int) or isinstance(new_om, float)):
240
+ raise ValueError("Organic modifier must be int or float")
241
+ if new_om < 0:
242
+ raise ValueError("Organic modifier must be positive")
243
+ if new_om > 100:
244
+ raise ValueError("Organic modifer must be less than 100.")
245
+
239
246
  def edit_flow(self, new_flow: Union[int, float]):
247
+ self._validate_flow(new_flow)
240
248
  flow: Param = Param(
241
249
  val=new_flow, chemstation_key=RegisterFlag.FLOW, ptype=PType.NUM
242
250
  )
243
251
  self._update_param(flow)
244
252
 
253
+ def _validate_flow(self, new_flow):
254
+ if not (isinstance(new_flow, int) or isinstance(new_flow, float)):
255
+ raise ValueError("Flow must be int or float")
256
+ if new_flow < 0:
257
+ raise ValueError("Flow must be positive")
258
+ if new_flow >= 5.0:
259
+ raise ValueError("Flow must be less than 5.0")
260
+
245
261
  def edit_stop_time(self, new_stop_time: Union[int, float]):
262
+ self.validate_stop_time(new_stop_time)
246
263
  stop_time: Param = Param(
247
264
  val=new_stop_time,
248
265
  chemstation_key=RegisterFlag.MAX_TIME,
@@ -255,7 +272,14 @@ class MethodController(RunController):
255
272
  )
256
273
  self._update_param(stop_time)
257
274
 
275
+ def validate_stop_time(self, new_stop_time):
276
+ if not (isinstance(new_stop_time, int) or isinstance(new_stop_time, float)):
277
+ raise ValueError("Stop time must be int or float")
278
+ if new_stop_time < 0:
279
+ raise ValueError("Stop time must be positive")
280
+
258
281
  def edit_post_time(self, new_post_time: Union[int, float]):
282
+ self.validate_post_time(new_post_time)
259
283
  post_time: Param = Param(
260
284
  val=new_post_time,
261
285
  chemstation_key=RegisterFlag.POST_TIME,
@@ -266,6 +290,12 @@ class MethodController(RunController):
266
290
  )
267
291
  self._update_param(post_time)
268
292
 
293
+ def validate_post_time(self, new_post_time):
294
+ if not (isinstance(new_post_time, int) or isinstance(new_post_time, float)):
295
+ raise ValueError("Post time must be int or float")
296
+ if new_post_time < 0:
297
+ raise ValueError("Post time must be positive")
298
+
269
299
  def update_method_params(
270
300
  self,
271
301
  new_flow: Union[int, float],
@@ -334,51 +364,146 @@ class MethodController(RunController):
334
364
  self.sleepy_send("DownloadRCMethod PMP1")
335
365
  self.send("Sleep 1")
336
366
 
337
- def _edit_row(self, row: TimeTableEntry, first_row: bool = False):
338
- if first_row:
339
- if row.organic_modifer:
340
- self.add_row()
341
- self.add_new_col_text(
342
- col_name=RegisterFlag.FUNCTION,
343
- val=RegisterFlag.SOLVENT_COMPOSITION.value,
344
- )
367
+ def _edit_row(
368
+ self,
369
+ row: TimeTableEntry,
370
+ first_row: bool,
371
+ time_added: bool,
372
+ flow_added: bool,
373
+ om_added: bool,
374
+ function_added: bool,
375
+ ) -> Tuple[bool, bool, bool, bool]:
376
+ def add_time():
377
+ nonlocal time_added
378
+ nonlocal first_row
379
+ if not time_added:
345
380
  self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
381
+ time_added = True
382
+ elif not first_row:
383
+ self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
384
+
385
+ def add_flow():
386
+ nonlocal flow_added
387
+ nonlocal function_added
388
+ if not flow_added:
389
+ if not function_added:
390
+ self.add_new_col_text(
391
+ col_name=RegisterFlag.FUNCTION,
392
+ val=RegisterFlag.FLOW.value,
393
+ )
394
+ function_added = True
395
+ else:
396
+ self._edit_row_text(
397
+ col_name=RegisterFlag.FUNCTION,
398
+ val=RegisterFlag.FLOW.value,
399
+ )
346
400
  self.add_new_col_num(
347
- col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
348
- val=row.organic_modifer,
401
+ col_name=RegisterFlag.TIMETABLE_FLOW,
402
+ val=row.flow,
349
403
  )
350
- if row.flow:
351
- self.add_row()
352
- self.get_num_rows()
404
+ flow_added = True
405
+ else:
353
406
  self._edit_row_text(
354
407
  col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
355
408
  )
356
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
357
409
  self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
358
- self.download()
359
- else:
360
- if row.organic_modifer:
361
- self.add_row()
362
- self.get_num_rows()
410
+
411
+ def add_om():
412
+ nonlocal om_added
413
+ nonlocal function_added
414
+ if not om_added:
415
+ if not function_added:
416
+ self.add_new_col_text(
417
+ col_name=RegisterFlag.FUNCTION,
418
+ val=RegisterFlag.SOLVENT_COMPOSITION.value,
419
+ )
420
+ function_added = True
421
+ else:
422
+ self._edit_row_text(
423
+ col_name=RegisterFlag.FUNCTION,
424
+ val=RegisterFlag.SOLVENT_COMPOSITION.value,
425
+ )
426
+ self.add_new_col_num(
427
+ col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
428
+ val=row.organic_modifer,
429
+ )
430
+ om_added = True
431
+ else:
363
432
  self._edit_row_text(
364
433
  col_name=RegisterFlag.FUNCTION,
365
434
  val=RegisterFlag.SOLVENT_COMPOSITION.value,
366
435
  )
367
- self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
368
436
  self._edit_row_num(
369
437
  col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
370
438
  val=row.organic_modifer,
371
439
  )
372
- self.download()
373
- if row.flow:
374
- self.add_row()
375
- self.get_num_rows()
376
- self._edit_row_text(
377
- col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
378
- )
379
- self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
380
- self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
381
- self.download()
440
+
441
+ if row.organic_modifer:
442
+ self.add_row()
443
+ add_om()
444
+ add_time()
445
+ if row.flow:
446
+ self.add_row()
447
+ add_flow()
448
+ add_time()
449
+ self.download()
450
+ return time_added, flow_added, om_added, function_added
451
+
452
+ # if first_row:
453
+ # time_added = False
454
+ # flow_row_method: Callable = (
455
+ # self.add_new_col_text
456
+ # if row.flow and not row.organic_modifer
457
+ # else self._edit_row_text
458
+ # )
459
+ # if row.organic_modifer:
460
+ # self.add_row()
461
+ # self.add_new_col_text(
462
+ # col_name=RegisterFlag.FUNCTION,
463
+ # val=RegisterFlag.SOLVENT_COMPOSITION.value,
464
+ # )
465
+ # if not time_added:
466
+ # time_added = True
467
+ # self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
468
+ # self.add_new_col_num(
469
+ # col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
470
+ # val=row.organic_modifer,
471
+ # )
472
+ # if row.flow:
473
+ # self.add_row()
474
+ # self.get_num_rows()
475
+ # flow_row_method(
476
+ # col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
477
+ # )
478
+ # if not time_added:
479
+ # time_added = True
480
+ # self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
481
+ # self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
482
+ # self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
483
+ # self.download()
484
+ # else:
485
+ # if row.organic_modifer:
486
+ # self.add_row()
487
+ # self.get_num_rows()
488
+ # self._edit_row_text(
489
+ # col_name=RegisterFlag.FUNCTION,
490
+ # val=RegisterFlag.SOLVENT_COMPOSITION.value,
491
+ # )
492
+ # self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
493
+ # self._edit_row_num(
494
+ # col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
495
+ # val=row.organic_modifer,
496
+ # )
497
+ # self.download()
498
+ # if row.flow:
499
+ # self.add_row()
500
+ # self.get_num_rows()
501
+ # self._edit_row_text(
502
+ # col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
503
+ # )
504
+ # self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
505
+ # self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
506
+ # self.download()
382
507
 
383
508
  def edit_method_timetable(self, timetable_rows: List[TimeTableEntry]):
384
509
  self.get_num_rows()
@@ -389,10 +514,23 @@ class MethodController(RunController):
389
514
  res = self.get_num_rows()
390
515
 
391
516
  self.new_table()
392
- self.get_num_rows()
393
-
517
+ num_rows = self.get_num_rows()
518
+ if num_rows.ok_value.num_response != 0:
519
+ raise ValueError("Should be zero rows!")
520
+
521
+ time_added = False
522
+ flow_added = False
523
+ om_added = False
524
+ function_added = False
394
525
  for i, row in enumerate(timetable_rows):
395
- self._edit_row(row=row, first_row=i == 0)
526
+ time_added, flow_added, om_added, function_added = self._edit_row(
527
+ row=row,
528
+ first_row=i == 0,
529
+ time_added=time_added,
530
+ flow_added=flow_added,
531
+ om_added=om_added,
532
+ function_added=function_added,
533
+ )
396
534
 
397
535
  def stop(self):
398
536
  """
@@ -490,3 +628,25 @@ class MethodController(RunController):
490
628
  if len(possible_data.x) > 0:
491
629
  signal.data = possible_data
492
630
  return [metd_report]
631
+
632
+ def _validate_row(self, row: TimeTableEntry):
633
+ if not (row.flow or row.organic_modifer):
634
+ raise ValueError(
635
+ "Require one of flow or organic modifier for the method timetable entry!"
636
+ )
637
+ if row.flow:
638
+ self._validate_flow(row.flow)
639
+ if row.organic_modifer:
640
+ self._validate_organic_modifier(row.organic_modifer)
641
+
642
+ def validate_timetable(self, timetable: List[TimeTableEntry]):
643
+ start_time = 0.0
644
+ for i, row in enumerate(timetable):
645
+ if row.start_time > start_time:
646
+ start_time = row.start_time
647
+ elif row.start_time <= start_time:
648
+ raise ValueError(
649
+ f"""Every row's start time must be larger than the previous start time.
650
+ Row {i + 1} ({timetable[i].start_time}) has a smaller or equal starttime than row {i} ({start_time})"""
651
+ )
652
+ self._validate_row(row)
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import time
3
5
  import warnings
4
- from typing import Any, Dict, List, Optional, Union, Set
6
+ from typing import Any, Dict, List, Optional, Union, Set, Tuple
5
7
 
6
8
  from result import Err, Ok, Result
7
9
  from typing_extensions import override
@@ -9,6 +11,7 @@ from typing_extensions import override
9
11
  from pychemstation.analysis.chromatogram import (
10
12
  AgilentChannelChromatogramData,
11
13
  AgilentHPLCChromatogram,
14
+ SEQUENCE_TIME_FORMAT,
12
15
  )
13
16
 
14
17
  from ....analysis.process_report import AgilentReport, ReportType
@@ -118,7 +121,6 @@ class SequenceController(RunController):
118
121
  parsed_response = self.get_current_sequence_name()
119
122
 
120
123
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
121
- self.table_state = self.load()
122
124
 
123
125
  def get_current_sequence_name(self):
124
126
  self.send(Command.GET_SEQUENCE_CMD)
@@ -186,6 +188,8 @@ class SequenceController(RunController):
186
188
  def edit_sample_type(
187
189
  self, sample_type: SampleType, row_num: int, save: bool = True
188
190
  ):
191
+ if not isinstance(sample_type, SampleType):
192
+ raise ValueError("`sample_type` should be of type `SampleType`")
189
193
  self._edit_row_num(
190
194
  row=row_num,
191
195
  col_name=RegisterFlag.SAMPLE_TYPE,
@@ -207,6 +211,8 @@ class SequenceController(RunController):
207
211
  def edit_injection_source(
208
212
  self, inj_source: InjectionSource, row_num: int, save: bool = True
209
213
  ):
214
+ if not isinstance(inj_source, InjectionSource):
215
+ raise ValueError("`inj_source` should be of type `InjectionSource`")
210
216
  self._edit_row_text(
211
217
  row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
212
218
  )
@@ -224,25 +230,65 @@ class SequenceController(RunController):
224
230
 
225
231
  def edit_num_injections(self, num_inj: int, row_num: int, save: bool = True):
226
232
  self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
233
+ if save:
234
+ self.save()
227
235
 
228
- def edit_method_name(self, method: str, row_num: int, save: bool = True):
236
+ def edit_method_name(
237
+ self, method: str, row_num: int, save: bool = True, override_check: bool = False
238
+ ):
229
239
  method_dir = self.method_controller.src
230
240
  possible_path = os.path.join(method_dir, method) + ".M\\"
231
241
  if os.path.exists(possible_path):
232
242
  method = os.path.join(method_dir, method)
243
+ elif not override_check:
244
+ raise ValueError(
245
+ "Method may not exist. If you would still like to use this method, set the `override_check` flag to `True`"
246
+ )
233
247
  self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
234
248
  if save:
235
249
  self.save()
236
250
 
237
251
  def edit_vial_location(self, loc: Tray, row_num: int, save: bool = True):
238
252
  loc_num = -1
239
- if isinstance(loc, VialBar):
240
- loc_num = loc.value
241
- elif isinstance(loc, FiftyFourVialPlate):
242
- loc_num = loc.value()
243
- self._edit_row_num(
244
- row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
245
- )
253
+ try:
254
+ previous_contents = self.get_row(row_num)
255
+ if (
256
+ isinstance(loc, VialBar)
257
+ and isinstance(previous_contents.vial_location, VialBar)
258
+ or isinstance(loc, FiftyFourVialPlate)
259
+ and isinstance(previous_contents.vial_location, FiftyFourVialPlate)
260
+ ):
261
+ if isinstance(loc, VialBar):
262
+ loc_num = loc.value
263
+ elif isinstance(loc, FiftyFourVialPlate):
264
+ loc_num = loc.value()
265
+ self._edit_row_num(
266
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
267
+ )
268
+ elif isinstance(loc, VialBar) or isinstance(loc, FiftyFourVialPlate):
269
+ self.add_row()
270
+ previous_contents.vial_location = loc
271
+ num_rows = self.get_num_rows().ok_value.num_response
272
+ self._edit_row(previous_contents, num_rows)
273
+ self.move_row(int(num_rows), row_num)
274
+ self.delete_row(row_num + 1)
275
+ self.save()
276
+ else:
277
+ raise ValueError(
278
+ "`loc` should be of type `VialBar`, `FiftyFourVialPlate`"
279
+ )
280
+ except Exception:
281
+ if not (isinstance(loc, VialBar) or isinstance(loc, FiftyFourVialPlate)):
282
+ raise ValueError(
283
+ "`loc` should be of type `VialBar`, `FiftyFourVialPlate`"
284
+ )
285
+ if isinstance(loc, VialBar):
286
+ loc_num = loc.value
287
+ elif isinstance(loc, FiftyFourVialPlate):
288
+ loc_num = loc.value()
289
+ self._edit_row_num(
290
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
291
+ )
246
292
  if save:
247
293
  self.save()
248
294
 
@@ -284,6 +330,9 @@ class SequenceController(RunController):
284
330
  curr_method_runtime = self.method_controller.get_total_runtime()
285
331
  total_runtime += curr_method_runtime
286
332
 
333
+ timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
334
+ folder_name = f"{self.table_state.name} {timestamp}"
335
+
287
336
  self.send(Command.RUN_SEQUENCE_CMD.value)
288
337
  self.timeout = total_runtime * 60
289
338
 
@@ -295,15 +344,7 @@ class SequenceController(RunController):
295
344
  break
296
345
 
297
346
  if hplc_running:
298
- full_path_name, current_sample_file = None, None
299
- for _ in range(5):
300
- try:
301
- full_path_name, current_sample_file = (
302
- self.get_current_run_data_dir_file()
303
- )
304
- break
305
- except ValueError:
306
- pass
347
+ current_sample_file, full_path_name = self.try_getting_run_info(folder_name)
307
348
  if full_path_name and current_sample_file:
308
349
  data_file = SequenceDataFiles(
309
350
  sequence_name=self.table_state.name,
@@ -324,6 +365,36 @@ class SequenceController(RunController):
324
365
  else:
325
366
  raise RuntimeError("Sequence run may not have started.")
326
367
 
368
+ def try_getting_run_info(self, folder_name: str) -> Tuple[str, str | None]:
369
+ full_path_name, current_sample_file = None, None
370
+ for _ in range(5):
371
+ try:
372
+ full_path_name, current_sample_file = (
373
+ self.get_current_run_data_dir_file()
374
+ )
375
+ except ValueError:
376
+ pass
377
+ if full_path_name and folder_name in full_path_name:
378
+ pass
379
+ else:
380
+ if self.controller:
381
+ self.controller.send(Command.GET_RUNNING_SEQUENCE_DIR)
382
+ res = self.controller.receive()
383
+ if res.is_ok():
384
+ if os.path.isdir(res.ok_value.string_response):
385
+ if folder_name in res.ok_value.string_response:
386
+ full_path_name = res.ok_value.string_response
387
+ else:
388
+ raise ValueError("Could not get sequence data folder.")
389
+ else:
390
+ raise ValueError("Controller is offline.")
391
+ if current_sample_file and full_path_name:
392
+ return full_path_name, current_sample_file
393
+ elif full_path_name:
394
+ return full_path_name, None
395
+ else:
396
+ raise ValueError("Could not get sequence data folder")
397
+
327
398
  @override
328
399
  def _fuzzy_match_most_recent_folder(
329
400
  self, most_recent_folder: T, child_dirs: Optional[Set[str]]
@@ -338,8 +409,13 @@ class SequenceController(RunController):
338
409
  and set(most_recent_folder._data_files) == child_dirs
339
410
  ):
340
411
  most_recent_folder.child_dirs = [
341
- os.path.join(most_recent_folder.dir, f) for f in child_dirs
412
+ os.path.join(most_recent_folder.dir, f)
413
+ if not os.path.isdir(f)
414
+ else f
415
+ for f in child_dirs
342
416
  ]
417
+ for d in most_recent_folder.child_dirs:
418
+ assert os.path.isdir(d)
343
419
  else:
344
420
  potential_folders: List[str] = sorted(
345
421
  list(
@@ -427,6 +503,13 @@ class SequenceController(RunController):
427
503
  ).ok_value
428
504
  )
429
505
  parent_dir = self.data_files[-1]
506
+ if (
507
+ len(parent_dir._data_files) != len(parent_dir.child_dirs)
508
+ or len(parent_dir.child_dirs) == 0
509
+ ):
510
+ parent_dir = self._fuzzy_match_most_recent_folder(
511
+ most_recent_folder=parent_dir, child_dirs=None
512
+ )
430
513
  spectra = self.get_data()
431
514
  reports = []
432
515
  for i, child_dir in enumerate(parent_dir.child_dirs):
@@ -20,7 +20,7 @@ from ..control.controllers import CommunicationController
20
20
  from ..utils.injector_types import InjectorTable
21
21
  from ..utils.macro import Command, Response, Status
22
22
  from ..utils.method_types import MethodDetails
23
- from ..utils.sequence_types import SequenceTable
23
+ from ..utils.sequence_types import SequenceTable, SequenceDataFiles
24
24
  from ..utils.table_types import Table
25
25
 
26
26
 
@@ -107,7 +107,11 @@ class HPLCController:
107
107
  raise RuntimeError(
108
108
  "Communication controller must be initialized before sending command. It is currently in offline mode."
109
109
  )
110
- return self.comm.receive().value
110
+ res = self.comm.receive()
111
+ if res.is_ok():
112
+ return res.ok_value
113
+ else:
114
+ return res.err_value
111
115
 
112
116
  def status(self) -> Status:
113
117
  """Get the current status of the HPLC machine.
@@ -202,6 +206,16 @@ class HPLCController:
202
206
  """
203
207
  self.sequence_controller.edit(updated_sequence)
204
208
 
209
+ def get_last_run_method_file_path(self) -> str:
210
+ """Get the folder (ending with .D) for last run method.
211
+
212
+ :return: Complete path for method run.
213
+ """
214
+ if len(self.method_controller.data_files) > 0:
215
+ return self.method_controller.data_files[-1]
216
+ else:
217
+ raise UserWarning("No data yet!")
218
+
205
219
  def get_last_run_method_report(
206
220
  self,
207
221
  custom_path: Optional[str] = None,
@@ -230,6 +244,20 @@ class HPLCController:
230
244
  else:
231
245
  return self.method_controller.get_data(custom_path=custom_path)
232
246
 
247
+ def get_last_run_sequence_file_paths(self) -> SequenceDataFiles:
248
+ """Get the sequence folder and all run folders (ending with .D).
249
+
250
+ :return: `SequenceDataFiles` containing complete path locations for sequence folder and all runs.
251
+ """
252
+ if len(self.sequence_controller.data_files):
253
+ self.sequence_controller._fuzzy_match_most_recent_folder(
254
+ most_recent_folder=self.sequence_controller.data_files[-1],
255
+ child_dirs=set(),
256
+ )
257
+ return self.sequence_controller.data_files[-1]
258
+ else:
259
+ raise UserWarning("No data files yet!")
260
+
233
261
  def get_last_run_sequence_reports(
234
262
  self,
235
263
  custom_path: Optional[str] = None,
@@ -57,7 +57,6 @@ class ABCCommunicationController(abc.ABC):
57
57
 
58
58
  self.reset_cmd_counter()
59
59
  self._most_recent_hplc_status: Status = self.get_status()
60
- self.send("Local Rows")
61
60
 
62
61
  @abstractmethod
63
62
  def get_num_val(self, cmd: str) -> Union[int, float]:
@@ -255,9 +255,14 @@ class RunController(ABCTableController, abc.ABC):
255
255
  self.send(Command.GET_CURRENT_RUN_DATA_FILE)
256
256
  current_sample_file = self.receive()
257
257
  if full_path_name.is_ok() and current_sample_file.is_ok():
258
- return (
259
- full_path_name.ok_value.string_response,
260
- current_sample_file.ok_value.string_response,
261
- )
262
- else:
263
- raise ValueError("Couldn't read data dir and file.")
258
+ if os.path.isdir(full_path_name.ok_value.string_response) and os.path.isdir(
259
+ os.path.join(
260
+ full_path_name.ok_value.string_response,
261
+ current_sample_file.ok_value.string_response,
262
+ )
263
+ ):
264
+ return (
265
+ full_path_name.ok_value.string_response,
266
+ current_sample_file.ok_value.string_response,
267
+ )
268
+ raise ValueError("Couldn't read data dir and file or doesn't exist yet.")
@@ -126,8 +126,8 @@ class ABCTableController(abc.ABC):
126
126
  ):
127
127
  if not (isinstance(val, int) or isinstance(val, float)):
128
128
  raise ValueError(f"{val} must be an int or float.")
129
+ num_rows = self.get_num_rows()
129
130
  if row:
130
- num_rows = self.get_num_rows()
131
131
  if num_rows.is_ok():
132
132
  if num_rows.ok_value.num_response < row:
133
133
  raise ValueError("Not enough rows to edit!")
@@ -136,7 +136,7 @@ class ABCTableController(abc.ABC):
136
136
  TableOperation.EDIT_ROW_VAL.value.format(
137
137
  register=self.table_locator.register,
138
138
  table_name=self.table_locator.name,
139
- row=row if row is not None else "Rows",
139
+ row=row if row is not None else "response_num",
140
140
  col_name=col_name,
141
141
  val=val,
142
142
  )
@@ -147,8 +147,8 @@ class ABCTableController(abc.ABC):
147
147
  ):
148
148
  if not isinstance(val, str):
149
149
  raise ValueError(f"{val} must be a str.")
150
+ num_rows = self.get_num_rows()
150
151
  if row:
151
- num_rows = self.get_num_rows()
152
152
  if num_rows.is_ok():
153
153
  if num_rows.ok_value.num_response < row:
154
154
  raise ValueError("Not enough rows to edit!")
@@ -157,7 +157,7 @@ class ABCTableController(abc.ABC):
157
157
  TableOperation.EDIT_ROW_TEXT.value.format(
158
158
  register=self.table_locator.register,
159
159
  table_name=self.table_locator.name,
160
- row=row if row is not None else "Rows",
160
+ row=row if row is not None else "response_num",
161
161
  col_name=col_name,
162
162
  val=val,
163
163
  )
@@ -178,11 +178,15 @@ class ABCTableController(abc.ABC):
178
178
 
179
179
  def add_row(self):
180
180
  """Adds a row to the provided table for currently loaded method or sequence."""
181
+ previous_row_count = self.get_num_rows().ok_value.num_response
181
182
  self.sleepy_send(
182
183
  TableOperation.NEW_ROW.value.format(
183
184
  register=self.table_locator.register, table_name=self.table_locator.name
184
185
  )
185
186
  )
187
+ new_row_count = self.get_num_rows().ok_value.num_response
188
+ if previous_row_count + 1 != new_row_count:
189
+ raise ValueError("Row could not be added.")
186
190
 
187
191
  def delete_table(self):
188
192
  """Deletes the table."""
@@ -201,13 +205,6 @@ class ABCTableController(abc.ABC):
201
205
  )
202
206
 
203
207
  def get_num_rows(self) -> Result[Response, str]:
204
- self.send(
205
- TableOperation.GET_NUM_ROWS.value.format(
206
- register=self.table_locator.register,
207
- table_name=self.table_locator.name,
208
- col_name=RegisterFlag.NUM_ROWS,
209
- )
210
- )
211
208
  self.send(
212
209
  Command.GET_ROWS_CMD.value.format(
213
210
  register=self.table_locator.register,
@@ -221,8 +218,16 @@ class ABCTableController(abc.ABC):
221
218
  raise ValueError("Controller is offline")
222
219
 
223
220
  if res.is_ok():
224
- self.send("Sleep 0.1")
225
- self.send("Print Rows")
226
221
  return res
227
222
  else:
228
223
  return Err("No rows could be read.")
224
+
225
+ def move_row(self, from_row: int, to_row: int):
226
+ self.send(
227
+ TableOperation.MOVE_ROW.value.format(
228
+ register=self.table_locator.register,
229
+ table_name=self.table_locator.name,
230
+ from_row=from_row,
231
+ to_row=to_row,
232
+ )
233
+ )
@@ -55,12 +55,15 @@ class Command(Enum):
55
55
  # Get directories
56
56
  GET_METHOD_DIR = "response$ = _METHPATH$"
57
57
  GET_SEQUENCE_DIR = "response$ = _SEQUENCEPATHS$"
58
+ CONFIG_MET_PATH = "_CONFIGMETPATH"
59
+ CONFIG_SEQ_PATH = "_CONFIGSEQPATH"
60
+ GET_RUNNING_SEQUENCE_DIR = "response$ = _SEQPATHS$"
58
61
  GET_DATA_DIRS = "response$ = _DATAPATHS$"
59
62
  GET_CURRENT_RUN_DATA_DIR = "response$ = _DATAPath$"
60
63
  GET_CURRENT_RUN_DATA_FILE = "response$ = _DATAFILE1$"
61
64
 
62
- # Debuggng
63
- ERROR = "response$ = _ERROR$"
65
+ # Errors
66
+ ERROR = "response$ = _ERRMSG$"
64
67
 
65
68
 
66
69
  class HPLCRunningStatus(Enum):
@@ -33,7 +33,7 @@ class TimeTableEntry:
33
33
  """Row in a method timetable."""
34
34
 
35
35
  start_time: float
36
- organic_modifer: Optional[float]
36
+ organic_modifer: Optional[float] = None
37
37
  flow: Optional[float] = None
38
38
 
39
39
 
@@ -1,5 +1,28 @@
1
- from ...control.controllers.abc_tables.abc_comm import ABCCommunicationController
1
+ from typing import Union
2
+
3
+ from result import Result
4
+
5
+ from .mock_hplc import MockHPLC
6
+ from ..abc_tables.abc_comm import ABCCommunicationController
7
+ from ..macro import Status
2
8
 
3
9
 
4
10
  class MockCommunicationController(ABCCommunicationController):
5
- pass
11
+ def __init__(self, comm_dir: str):
12
+ super().__init__(comm_dir)
13
+ self.hplc = MockHPLC()
14
+
15
+ def get_num_val(self, cmd: str) -> Union[int, float]:
16
+ raise NotImplementedError
17
+
18
+ def get_text_val(self, cmd: str) -> str:
19
+ raise NotImplementedError
20
+
21
+ def get_status(self) -> Status:
22
+ raise NotImplementedError
23
+
24
+ def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
25
+ raise NotImplementedError
26
+
27
+ def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
28
+ raise NotImplementedError
@@ -1,2 +1,30 @@
1
+ from ..macro import HPLCAvailStatus, Status
2
+ from ..method_types import MethodDetails, HPLCMethodParams, TimeTableEntry
3
+ from ..sequence_types import SequenceTable, SequenceEntry, InjectionSource
4
+ from ..tray_types import FiftyFourVialPlate
5
+
6
+
1
7
  class MockHPLC:
2
- pass
8
+ def __init__(self):
9
+ self.current_method: MethodDetails = MethodDetails(
10
+ name="General-Poroshell",
11
+ params=HPLCMethodParams(organic_modifier=5, flow=0.65),
12
+ timetable=[TimeTableEntry(start_time=3, organic_modifer=99, flow=0.65)],
13
+ stop_time=5,
14
+ post_time=2,
15
+ )
16
+ self.current_sequence: SequenceTable = SequenceTable(
17
+ name="hplc_testing",
18
+ rows=[
19
+ SequenceEntry(
20
+ vial_location=FiftyFourVialPlate.from_str("P1-A2"),
21
+ sample_name="sample1",
22
+ data_file="sample1",
23
+ method="General-Poroshell",
24
+ num_inj=1,
25
+ inj_vol=1,
26
+ inj_source=InjectionSource.HIP_ALS,
27
+ )
28
+ ],
29
+ )
30
+ self.current_status: Status = HPLCAvailStatus.STANDBY
@@ -16,6 +16,8 @@ class TableOperation(Enum):
16
16
  DELETE_TABLE = 'DelTab {register}, "{table_name}"'
17
17
  CREATE_TABLE = 'NewTab {register}, "{table_name}"'
18
18
  NEW_ROW = 'InsTabRow {register}, "{table_name}"'
19
+ NEW_ROW_SPECIFIC = 'InsTabRow {register}, "{table_name}"'
20
+ MOVE_ROW = 'MoveTabRow {register}, "{table_name}", {from_row}, {to_row}'
19
21
  DELETE_ROW = 'DelTabRow {register}, "{table_name}", {row}'
20
22
  EDIT_ROW_VAL = 'SetTabVal "{register}", "{table_name}", {row}, "{col_name}", {val}'
21
23
  EDIT_ROW_TEXT = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pychemstation
3
- Version: 0.10.8.dev1
3
+ Version: 0.10.8.dev3
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Project-URL: Documentation, https://pychemstation-e5a086.gitlab.io/pychemstation.html
6
6
  Project-URL: Repository, https://gitlab.com/heingroup/device-api/pychemstation
@@ -5,13 +5,13 @@ pychemstation/analysis/chromatogram.py,sha256=Jk6xOMHA6kSV597fJDZgrFRlvVorldvK4z
5
5
  pychemstation/analysis/process_report.py,sha256=aTsW6u5-0iwsH3jQtkoKE9jbsy5NAUG6l7O-I49B8kY,14650
6
6
  pychemstation/control/README.md,sha256=ohPn3xhgjFyPraQqR4x9aZhurQVAOYuYHv-E8sj0eK4,3124
7
7
  pychemstation/control/__init__.py,sha256=7lSkY7Qa7Ikdz82-2FESc_oqktv7JndsjltCkiMqnMI,147
8
- pychemstation/control/hplc.py,sha256=Evmo_D6X2RDmPBFmo3vsKE9kxJzy36Ptz4MyF2IKNzA,13146
8
+ pychemstation/control/hplc.py,sha256=JrfkQ5Jt9K9kf5l2X5wQzuYaQvPEnDa_aAkCVeMBk0o,14244
9
9
  pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
10
10
  pychemstation/control/controllers/__init__.py,sha256=q2TUEie3J-OLlxcGLkG7vIy8fazCEhHm61OGzJPbhD0,179
11
11
  pychemstation/control/controllers/comm.py,sha256=ySjgMBIfJ11sygXiZSPp9Rf6ABM4t6JhZRONRj1u2Cc,6652
12
12
  pychemstation/control/controllers/data_aq/__init__.py,sha256=w-Zgbit10niOQfz780ZmRHjUFxV1hMkdui7fOMPqeLA,132
13
- pychemstation/control/controllers/data_aq/method.py,sha256=hfLU5bnlmqKc1DPnK-HNpe-S2DollXyNx89hiLwhKWI,18251
14
- pychemstation/control/controllers/data_aq/sequence.py,sha256=zhilGZXUEttqrux8OI2ieAWktINsrD98fm_b6guZKlo,17209
13
+ pychemstation/control/controllers/data_aq/method.py,sha256=KFKL8yt3lHxpgND52z4gN6TeMvnNaAOVetpO2su1GlU,24935
14
+ pychemstation/control/controllers/data_aq/sequence.py,sha256=CgNPc-IaPXCHkkWjhb2UrxhDmAltUz7rPCiZa4O6ATw,20967
15
15
  pychemstation/control/controllers/devices/__init__.py,sha256=QpgGnLXyWiB96KIB98wMccEi8oOUUaLxvBCyevJzcOg,75
16
16
  pychemstation/control/controllers/devices/injector.py,sha256=LyubM-fqf5ruseGx32deTDK-yevmaTOvdo6YKg2PF7I,4029
17
17
  pychemstation/generated/__init__.py,sha256=xnEs0QTjeuGYO3tVUIy8GDo95GqTV1peEjosGckpOu0,977
@@ -19,23 +19,23 @@ pychemstation/generated/dad_method.py,sha256=xTUiSCvkXcxBUhjVm1YZKu-tHs16k23pF-0
19
19
  pychemstation/generated/pump_method.py,sha256=s3MckKDw2-nZUC5lHrJVvXYdneWP8-9UvblNuGryPHY,12092
20
20
  pychemstation/utils/__init__.py,sha256=GZJyDzkhzrlMguxZTUpgthq72pA3YV23DJIR2Q63PCk,449
21
21
  pychemstation/utils/injector_types.py,sha256=z2iWwTklGm0GRDCL9pnPCovQrwyRwxv8w5w5Xh7Pj3U,1152
22
- pychemstation/utils/macro.py,sha256=n2CcvB7MY4fl8Ds909K41aqqH7_6DlKK5R4aemLhOdM,3300
23
- pychemstation/utils/method_types.py,sha256=pXfZWmIPnZu3kIdGPxSPJMhL4WSt-u00If1JDUaLaMQ,1555
22
+ pychemstation/utils/macro.py,sha256=VGOU380ruJSKQhXJFI1g--qg4Xgx-e0D8z5FDPGe0cA,3433
23
+ pychemstation/utils/method_types.py,sha256=ck8I4dRGhHXCUfBf3AT1OU1eCcSSZPgnlhvlLTfezEM,1562
24
24
  pychemstation/utils/num_utils.py,sha256=OpqZwMPoxTYkpjpinA1CcoQAXDY_0sie6d-hTU547uo,2087
25
25
  pychemstation/utils/parsing.py,sha256=mzdpxrH5ux4-_i4CwZvnIYnIwAnRnOptKb3fZyYJcx0,9307
26
26
  pychemstation/utils/sequence_types.py,sha256=JLGL7kCkPouHN7iBsnZfWe-nSjzF8XqFKzBPyF4SulE,2724
27
27
  pychemstation/utils/spec_utils.py,sha256=BtXgQndZy4kVKsrgEDxwNd0HctypnAt5upB9SOk1D9w,9700
28
- pychemstation/utils/table_types.py,sha256=obQrnwDNmy-7r1IDD3N6wY7gUPKfpcM6D9Ow0IN-T48,3609
28
+ pychemstation/utils/table_types.py,sha256=ZmV52Vl1cYG_C1PEmVGB02mC3Dhfi7QMkUZwe0ujzAg,3748
29
29
  pychemstation/utils/tray_types.py,sha256=UeHM0hUYaNc9giT96ZiGpyWBPQwG-SyLA0rVGzDDAJk,6618
30
30
  pychemstation/utils/abc_tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- pychemstation/utils/abc_tables/abc_comm.py,sha256=rTrnfyTdqRSiBfBW1_460KsHER5vpfhv7JYwObBgBZc,5501
31
+ pychemstation/utils/abc_tables/abc_comm.py,sha256=gLvCiD8hDHXlMsiUzZX__g2kgFCeNnUIK7_nsLwI7Hc,5465
32
32
  pychemstation/utils/abc_tables/device.py,sha256=v8MNFvymbKe4hWsqzO6Z_qsVvju_rISYtnPgaftecyE,919
33
- pychemstation/utils/abc_tables/run.py,sha256=FPwGuWIS4BLTS-aZqBLWoHvgev4qsAnUdPVjVPnFWVU,9842
34
- pychemstation/utils/abc_tables/table.py,sha256=4mfmegCdMppxgpxVDt7oULoxTBVia8_JTxYPjlWq8VE,7743
33
+ pychemstation/utils/abc_tables/run.py,sha256=Sr6Iz_YrwJ78HTNHFv6fTA-Kca8gP3Dic_-7-AriyOw,10140
34
+ pychemstation/utils/abc_tables/table.py,sha256=AbuitDcBHODV-KpxjL7ZPoE-TSa6gyG2uqXXY90Mv2o,7992
35
35
  pychemstation/utils/mocking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- pychemstation/utils/mocking/mock_comm.py,sha256=4DcKmUxp-LYNXjywT_za1_GpqKa4sFTj7F2V3r_qsA0,156
37
- pychemstation/utils/mocking/mock_hplc.py,sha256=Hx6127C7d3miYGCZYxbN-Q3PU8kpMgXYX2n6we2Twgw,25
38
- pychemstation-0.10.8.dev1.dist-info/METADATA,sha256=kACFfqsEpvT70MVDKx6KYzmgSjmPXe6pDcwhoj82ZIQ,5757
39
- pychemstation-0.10.8.dev1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
- pychemstation-0.10.8.dev1.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
41
- pychemstation-0.10.8.dev1.dist-info/RECORD,,
36
+ pychemstation/utils/mocking/mock_comm.py,sha256=vZeYBXaKBZOlJmhn4TSbkov62gqlkfztqf3MSFU9kLE,800
37
+ pychemstation/utils/mocking/mock_hplc.py,sha256=esVIlU4oqEsYLPOQs0AeVnKv9l52xBGT6UY862l9RQE,1163
38
+ pychemstation-0.10.8.dev3.dist-info/METADATA,sha256=v0QV6RM1Zumdp8W0qD8rNuy-O-b_Uov8uFcDBsA1FK4,5757
39
+ pychemstation-0.10.8.dev3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
+ pychemstation-0.10.8.dev3.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
41
+ pychemstation-0.10.8.dev3.dist-info/RECORD,,