pychemstation 0.10.6__py3-none-any.whl → 0.10.7.dev1__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 +7 -15
  4. pychemstation/control/README.md +2 -2
  5. pychemstation/control/controllers/__init__.py +2 -1
  6. pychemstation/control/controllers/comm.py +40 -13
  7. pychemstation/control/controllers/data_aq/method.py +19 -22
  8. pychemstation/control/controllers/data_aq/sequence.py +129 -111
  9. pychemstation/control/controllers/devices/injector.py +7 -7
  10. pychemstation/control/hplc.py +57 -60
  11. pychemstation/utils/__init__.py +23 -0
  12. pychemstation/utils/{mocking → abc_tables}/abc_comm.py +8 -14
  13. pychemstation/utils/abc_tables/device.py +27 -0
  14. pychemstation/{control/controllers → utils}/abc_tables/run.py +69 -34
  15. pychemstation/{control/controllers → utils}/abc_tables/table.py +29 -22
  16. pychemstation/utils/macro.py +13 -0
  17. pychemstation/utils/method_types.py +12 -13
  18. pychemstation/utils/mocking/mock_comm.py +1 -1
  19. pychemstation/utils/num_utils.py +3 -3
  20. pychemstation/utils/sequence_types.py +30 -12
  21. pychemstation/utils/spec_utils.py +42 -66
  22. pychemstation/utils/table_types.py +13 -2
  23. pychemstation/utils/tray_types.py +28 -16
  24. {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/METADATA +2 -8
  25. pychemstation-0.10.7.dev1.dist-info/RECORD +41 -0
  26. pychemstation/control/controllers/abc_tables/device.py +0 -15
  27. pychemstation/utils/pump_types.py +0 -7
  28. pychemstation-0.10.6.dist-info/RECORD +0 -42
  29. /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
  30. {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,7 @@ Authors: Lucy Hao
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import os.path
9
10
  from typing import Dict, List, Optional, Tuple, Union
10
11
 
11
12
  from pychemstation.analysis.chromatogram import AgilentChannelChromatogramData
@@ -36,9 +37,9 @@ class HPLCController:
36
37
  def __init__(
37
38
  self,
38
39
  comm_dir: str,
39
- method_dir: str,
40
- sequence_dir: str,
41
- data_dirs: List[str],
40
+ method_dir: Optional[str] = None,
41
+ sequence_dir: Optional[str] = None,
42
+ extra_data_dirs: Optional[List[str]] = None,
42
43
  offline: bool = False,
43
44
  debug: bool = False,
44
45
  ):
@@ -47,37 +48,47 @@ class HPLCController:
47
48
  double escaped: "C:\\my_folder\\"
48
49
 
49
50
  :param comm_dir: Name of directory for communication, where ChemStation will read and write from. Can be any existing directory.
50
- :param data_dirs: Name of directories for storing data after method or sequence runs. Method data dir is default
51
51
  the first one in the list. In other words, the first dir in the list is highest prio. Must be "normal" strings and not r-strings.
52
- :param method_dir: Name of directory where method files are stored.
53
- :param sequence_dir: Name of directory where sequence files are stored.
54
52
  :raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
55
53
  """
56
54
  self.comm: CommunicationController = CommunicationController(
57
- comm_dir=comm_dir, debug=debug
58
- )
59
- self.method_controller: MethodController = MethodController(
60
- controller=self.comm,
61
- src=method_dir,
62
- data_dirs=data_dirs,
63
- table=self.METHOD_TIMETABLE,
64
- offline=offline,
65
- injector_controller=InjectorController(
66
- controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
67
- ),
68
- )
69
- self.sequence_controller: SequenceController = SequenceController(
70
- controller=self.comm,
71
- src=sequence_dir,
72
- data_dirs=data_dirs,
73
- table=self.SEQUENCE_TABLE,
74
- method_controller=self.method_controller,
75
- offline=offline,
55
+ comm_dir=comm_dir, debug=debug, offline=offline
76
56
  )
57
+ data_dirs: List[str] = []
58
+ if not offline:
59
+ if not method_dir or not sequence_dir or not extra_data_dirs:
60
+ method_dir, sequence_dir, data_dirs = self.comm.get_chemstation_dirs()
61
+ if extra_data_dirs:
62
+ data_dirs.extend(extra_data_dirs)
63
+ data_dirs = list(set([os.path.normpath(p) for p in data_dirs]))
64
+ if (method_dir and sequence_dir and data_dirs and not offline) or offline:
65
+ self.method_controller: MethodController = MethodController(
66
+ controller=self.comm,
67
+ src=method_dir,
68
+ data_dirs=data_dirs,
69
+ table=self.METHOD_TIMETABLE,
70
+ offline=offline,
71
+ injector_controller=InjectorController(
72
+ controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
73
+ ),
74
+ )
75
+ self.sequence_controller: SequenceController = SequenceController(
76
+ controller=self.comm,
77
+ src=sequence_dir,
78
+ data_dirs=data_dirs,
79
+ table=self.SEQUENCE_TABLE,
80
+ method_controller=self.method_controller,
81
+ offline=offline,
82
+ )
83
+ elif not offline and (not method_dir or not sequence_dir or not data_dirs):
84
+ raise ValueError(
85
+ f"Expected a method dir: {method_dir}, sequence dir: {sequence_dir} and data dirs:{data_dirs} but one was None."
86
+ )
87
+ else:
88
+ raise ValueError("Expected error occured, please try again.")
77
89
 
78
90
  def send(self, cmd: Union[Command, str]):
79
- """
80
- Sends any Command or string to Chemstation.
91
+ """Sends any Command or string to Chemstation.
81
92
 
82
93
  :param cmd: the macro to send to Chemstation
83
94
  """
@@ -88,10 +99,9 @@ class HPLCController:
88
99
  self.comm.send(cmd)
89
100
 
90
101
  def receive(self) -> None | Response | str:
91
- """
92
- Get the most recent response from Chemstation.
102
+ """Get the most recent response from Chemstation.
93
103
 
94
- :return: most recent response from a macro that returned a response.
104
+ :return: most recent response from the most recently sent MACRO that returned a response.
95
105
  """
96
106
  if not self.comm:
97
107
  raise RuntimeError(
@@ -100,8 +110,7 @@ class HPLCController:
100
110
  return self.comm.receive().value
101
111
 
102
112
  def status(self) -> Status:
103
- """
104
- Get the current status of the HPLC machine.
113
+ """Get the current status of the HPLC machine.
105
114
 
106
115
  :return: current status of the HPLC machine; Status types can be found in `pychemstation.utils.macro`
107
116
  """
@@ -112,8 +121,7 @@ class HPLCController:
112
121
  return self.comm.get_status()
113
122
 
114
123
  def switch_method(self, method_name: str):
115
- """
116
- Allows the user to switch between pre-programmed methods. No need to append '.M'
124
+ """Allows the user to switch between pre-programmed methods. No need to append '.M'
117
125
  to the end of the method name. For example. for the method named 'General-Poroshell.M',
118
126
  only 'General-Poroshell' is needed.
119
127
 
@@ -124,8 +132,7 @@ class HPLCController:
124
132
  self.method_controller.switch(method_name)
125
133
 
126
134
  def switch_sequence(self, sequence_name: str):
127
- """
128
- Allows the user to switch between pre-programmed sequences. The sequence name does not need the '.S' extension.
135
+ """Allows the user to switch between pre-programmed sequences. The sequence name does not need the '.S' extension.
129
136
  For example: for the method named 'mySeq.S', only 'mySeq' is needed.
130
137
 
131
138
  :param sequence_name: The name of the sequence file
@@ -138,8 +145,7 @@ class HPLCController:
138
145
  add_timestamp: bool = True,
139
146
  stall_while_running: bool = True,
140
147
  ):
141
- """
142
- This is the preferred method to trigger a run.
148
+ """This is the preferred method to trigger a run.
143
149
  Starts the currently selected method, storing data
144
150
  under the <data_dir>/<experiment_name>.D folder.
145
151
  Device must be ready.
@@ -159,8 +165,7 @@ class HPLCController:
159
165
  self.method_controller.stop()
160
166
 
161
167
  def run_sequence(self, stall_while_running: bool = True):
162
- """
163
- Starts the currently loaded sequence, storing data
168
+ """Starts the currently loaded sequence, storing data
164
169
  under one of the data_dirs/<sequence table name> folder.
165
170
  Device must be ready.
166
171
 
@@ -169,24 +174,21 @@ class HPLCController:
169
174
  self.sequence_controller.run(stall_while_running=stall_while_running)
170
175
 
171
176
  def check_method_complete(self) -> Tuple[float, int]:
172
- """
173
- Check if the currently running method (if any) is done.
177
+ """Check if the currently running method (if any) is done.
174
178
 
175
- :returns the percent of the method run completed, and whether the run is complete.
179
+ :returns: the percent of the method run completed, and whether the run is complete.
176
180
  """
177
181
  return self.method_controller.check_hplc_run_finished()
178
182
 
179
183
  def check_sequence_complete(self) -> Tuple[float, int]:
180
- """
181
- Check if the currently running sequence (if any) is done.
184
+ """Check if the currently running sequence (if any) is done.
182
185
 
183
186
  :return: the percent of the sequence run completed, and whether the run is complete.
184
187
  """
185
188
  return self.sequence_controller.check_hplc_run_finished()
186
189
 
187
190
  def edit_method(self, updated_method: MethodDetails, save: bool = False):
188
- """
189
- Updated the currently loaded method in ChemStation with provided values.
191
+ """Updated the currently loaded method in ChemStation with provided values.
190
192
 
191
193
  :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
192
194
  :param save: whether this method should be saved to disk, or just modified.
@@ -194,8 +196,7 @@ class HPLCController:
194
196
  self.method_controller.edit(updated_method, save)
195
197
 
196
198
  def edit_sequence(self, updated_sequence: SequenceTable):
197
- """
198
- Updates the currently loaded sequence table with the provided table, and saves the sequence.
199
+ """Updates the currently loaded sequence table with the provided table, and saves the sequence.
199
200
 
200
201
  :param updated_sequence: The sequence table to be written to the currently loaded sequence table.
201
202
  """
@@ -206,8 +207,7 @@ class HPLCController:
206
207
  custom_path: Optional[str] = None,
207
208
  report_type: ReportType = ReportType.CSV,
208
209
  ) -> AgilentReport:
209
- """
210
- Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
210
+ """Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
211
211
 
212
212
  :param custom_path: path to sequence folder
213
213
  :param report_type: read either the TXT or CSV version
@@ -220,8 +220,7 @@ class HPLCController:
220
220
  def get_last_run_method_data(
221
221
  self, read_uv: bool = False, custom_path: Optional[str] = None
222
222
  ) -> Dict[int, AgilentHPLCChromatogram] | AgilentChannelChromatogramData:
223
- """
224
- Returns the last run method data.
223
+ """Returns the last run method data.
225
224
 
226
225
  :param custom_path: If you want to just load method data but from a file path. This file path must be the complete file path.
227
226
  :param read_uv: whether to also read the UV file
@@ -236,8 +235,7 @@ class HPLCController:
236
235
  custom_path: Optional[str] = None,
237
236
  report_type: ReportType = ReportType.CSV,
238
237
  ) -> List[AgilentReport]:
239
- """
240
- Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
238
+ """Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
241
239
 
242
240
  :param custom_path: path to sequence folder
243
241
  :param report_type: read either the TXT or CSV version
@@ -252,8 +250,7 @@ class HPLCController:
252
250
  ) -> (
253
251
  List[Dict[int, AgilentHPLCChromatogram]] | List[AgilentChannelChromatogramData]
254
252
  ):
255
- """
256
- Returns data for all rows in the last run sequence data.
253
+ """Returns data for all rows in the last run sequence data.
257
254
 
258
255
  :param custom_path: If you want to just load sequence data but from a file path. This file path must be the complete file path.
259
256
  :param read_uv: whether to also read the UV file
@@ -265,11 +262,11 @@ class HPLCController:
265
262
 
266
263
  def check_loaded_sequence(self) -> str:
267
264
  """Returns the name of the currently loaded sequence."""
268
- return self.sequence_controller.check()
265
+ return self.sequence_controller.get_current_sequence_name()
269
266
 
270
267
  def check_loaded_method(self) -> str:
271
268
  """Returns the name of the currently loaded method."""
272
- return self.method_controller.check()
269
+ return self.method_controller.get_current_method_name()
273
270
 
274
271
  def load_method(self) -> MethodDetails:
275
272
  """Returns details of the currently loaded method, such as its starting modifier conditions and timetable."""
@@ -279,7 +276,7 @@ class HPLCController:
279
276
  """Returns the currently loaded sequence."""
280
277
  return self.sequence_controller.load()
281
278
 
282
- def load_injector_program(self) -> InjectorTable:
279
+ def load_injector_program(self) -> InjectorTable | None:
283
280
  return self.method_controller.injector_controller.load()
284
281
 
285
282
  def standby(self):
@@ -0,0 +1,23 @@
1
+ from . import abc_tables
2
+ from . import injector_types
3
+ from . import macro
4
+ from . import method_types
5
+ from . import num_utils
6
+ from . import parsing
7
+ from . import sequence_types
8
+ from . import spec_utils
9
+ from . import table_types
10
+ from . import tray_types
11
+
12
+ __all__ = [
13
+ "abc_tables",
14
+ "injector_types",
15
+ "macro",
16
+ "method_types",
17
+ "num_utils",
18
+ "parsing",
19
+ "sequence_types",
20
+ "spec_utils",
21
+ "table_types",
22
+ "tray_types",
23
+ ]
@@ -18,17 +18,17 @@ from typing import Union
18
18
 
19
19
  from result import Err, Ok, Result
20
20
 
21
- from ...utils.macro import (
22
- HPLCAvailStatus,
23
- Command,
24
- Status,
25
- Response,
26
- )
21
+ from ..macro import HPLCAvailStatus, Command, Response, Status
27
22
 
28
23
 
29
24
  class ABCCommunicationController(abc.ABC):
30
- """
31
- Abstract class representing the communication controller.
25
+ """Abstract class representing the communication controller.
26
+
27
+ :param comm_dir: the complete directory path that was used in the MACRO file, common file that pychemstation and Chemstation use to communicate.
28
+ :param cmd_file: name of the write file that pychemstation writes MACROs to, in `comm_dir`
29
+ :param reply_file: name of the read file that Chemstation replies to, in `comm_dir
30
+ :param offline: whether or not communication with Chemstation is to be established
31
+ :param debug: if True, prints all send MACROs to an out.txt file
32
32
  """
33
33
 
34
34
  # maximum command number
@@ -42,12 +42,6 @@ class ABCCommunicationController(abc.ABC):
42
42
  offline: bool = False,
43
43
  debug: bool = False,
44
44
  ):
45
- """
46
- :param comm_dir:
47
- :param cmd_file: Name of command file
48
- :param reply_file: Name of reply file
49
- :param debug: whether to save log of sent commands
50
- """
51
45
  if not offline:
52
46
  self.debug = debug
53
47
  if os.path.isdir(comm_dir):
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+
5
+ from .table import ABCTableController
6
+ from ..table_types import Table
7
+ from ...control.controllers import CommunicationController
8
+
9
+
10
+ class DeviceController(ABCTableController, ABC):
11
+ """Abstract controller representing tables that contain device information.
12
+
13
+ :param controller: controller for sending MACROs
14
+ :param table: contains register keys for accessing table in Chemstation
15
+ :param offline: whether the communication controller is online.
16
+ """
17
+
18
+ def __init__(
19
+ self, controller: CommunicationController, table: Table, offline: bool
20
+ ):
21
+ super().__init__(controller=controller, table=table)
22
+ self.offline = offline
23
+
24
+ def __new__(cls, *args, **kwargs):
25
+ if cls is ABCTableController:
26
+ raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
27
+ return object.__new__(cls)
@@ -11,60 +11,69 @@ import math
11
11
  import os
12
12
  import time
13
13
  import warnings
14
- from typing import Dict, List, Optional, Tuple, Union
14
+ from typing import Dict, List, Optional, Tuple, Union, Set
15
15
 
16
16
  import polling
17
17
  import rainbow as rb
18
- from result import Err, Result, Ok
18
+ from result import Err, Ok, Result
19
+
20
+ from ..macro import HPLCRunningStatus, Command
21
+ from ..method_types import MethodDetails
22
+ from ..sequence_types import SequenceTable
23
+ from ..table_types import Table, T
24
+ from ...analysis.chromatogram import (
25
+ AgilentChannelChromatogramData,
26
+ AgilentHPLCChromatogram,
27
+ )
19
28
 
20
29
  from .table import ABCTableController
21
- from ....analysis.process_report import (
30
+ from ...analysis.process_report import (
31
+ ReportType,
22
32
  AgilentReport,
23
33
  CSVProcessor,
24
- ReportType,
25
34
  TXTProcessor,
26
35
  )
27
- from ....control.controllers.comm import CommunicationController
28
- from pychemstation.analysis.chromatogram import (
29
- AgilentChannelChromatogramData,
30
- AgilentHPLCChromatogram,
31
- )
32
- from ....utils.macro import HPLCRunningStatus
33
- from ....utils.method_types import MethodDetails
34
- from ....utils.sequence_types import SequenceTable
35
- from ....utils.table_types import Table, T
36
+ from ...control.controllers import CommunicationController
36
37
 
37
38
  TableType = Union[MethodDetails, SequenceTable]
38
39
 
39
40
 
40
41
  class RunController(ABCTableController, abc.ABC):
42
+ """Abstract controller for all tables that can trigger runs on Chemstation.
43
+
44
+ :param controller: the controller for sending MACROs, must be initialized for a run to be triggered.
45
+ :param src: complete directory path where files containing run parameters are stored.
46
+ :param data_dirs: list of complete directories that Chemstation will write data to.
47
+ :param table: contains register keys for accessing table in Chemstation.
48
+ :param offline: whether the communication controller is online.
49
+ """
50
+
41
51
  def __init__(
42
52
  self,
43
53
  controller: Optional[CommunicationController],
44
- src: str,
45
- data_dirs: List[str],
54
+ src: Optional[str],
55
+ data_dirs: Optional[List[str]],
46
56
  table: Table,
47
57
  offline: bool = False,
48
58
  ):
49
59
  super().__init__(controller=controller, table=table)
50
- warnings.warn(
51
- "This abstract class is not meant to be initialized. Use MethodController or SequenceController."
52
- )
53
60
  self.table_state: Optional[TableType] = None
54
61
  self.curr_run_starting_time: Optional[float] = None
55
62
  self.timeout: Optional[float] = None
63
+ self.current_run_child_files: Set[str] = set()
56
64
 
57
65
  if not offline:
58
66
  if src and not os.path.isdir(src):
59
67
  raise FileNotFoundError(f"dir: {src} not found.")
60
-
61
- for d in data_dirs:
62
- if not os.path.isdir(d):
63
- raise FileNotFoundError(f"dir: {d} not found.")
64
- if r"\\" in d:
65
- raise ValueError("Data directories should not be raw strings!")
66
- self.src: str = src
67
- self.data_dirs: List[str] = data_dirs
68
+ if data_dirs:
69
+ for d in data_dirs:
70
+ if not os.path.isdir(d):
71
+ raise FileNotFoundError(f"dir: {d} not found.")
72
+ if r"\\" in d:
73
+ raise ValueError("Data directories should not be raw strings!")
74
+ if src and data_dirs:
75
+ self.src: str = src
76
+ self.data_dirs: List[str] = data_dirs
68
77
 
69
78
  self.spectra: dict[str, AgilentHPLCChromatogram] = {
70
79
  "A": AgilentHPLCChromatogram(),
@@ -79,8 +88,15 @@ class RunController(ABCTableController, abc.ABC):
79
88
  self.uv: Dict[int, AgilentHPLCChromatogram] = {}
80
89
  self.data_files: List = []
81
90
 
91
+ def __new__(cls, *args, **kwargs):
92
+ if cls is RunController:
93
+ raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
94
+ return object.__new__(cls)
95
+
82
96
  @abc.abstractmethod
83
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
97
+ def _fuzzy_match_most_recent_folder(
98
+ self, most_recent_folder: T, child_dirs: Set[str]
99
+ ) -> Result[T, str]:
84
100
  pass
85
101
 
86
102
  @abc.abstractmethod
@@ -120,6 +136,12 @@ class RunController(ABCTableController, abc.ABC):
120
136
 
121
137
  def check_hplc_run_finished(self) -> Tuple[float, bool]:
122
138
  if self.controller:
139
+ try:
140
+ _, current_run_file = self.get_current_run_data_dir_file()
141
+ sample_file, extension, _ = current_run_file.partition(".D")
142
+ self.current_run_child_files.add(sample_file)
143
+ except Exception:
144
+ pass
123
145
  done_running = self.controller.check_if_not_running()
124
146
  if self.curr_run_starting_time and self.timeout:
125
147
  time_passed = time.time() - self.curr_run_starting_time
@@ -136,11 +158,11 @@ class RunController(ABCTableController, abc.ABC):
136
158
  raise ValueError("Controller is offline!")
137
159
 
138
160
  def check_hplc_done_running(self) -> Ok[T] | Err[str]:
139
- """
140
- Checks if ChemStation has finished running and can read data back
161
+ """Checks if ChemStation has finished running and can read data back
141
162
 
142
163
  :return: Data file object containing most recent run file information.
143
164
  """
165
+ self.current_run_child_files = set()
144
166
  if self.timeout is not None:
145
167
  finished_run = False
146
168
  minutes = math.ceil(self.timeout / 60)
@@ -170,7 +192,9 @@ class RunController(ABCTableController, abc.ABC):
170
192
  else:
171
193
  raise ValueError("Timeout value is None, no comparison can be made.")
172
194
 
173
- check_folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
195
+ check_folder = self._fuzzy_match_most_recent_folder(
196
+ self.data_files[-1], self.current_run_child_files
197
+ )
174
198
  if check_folder.is_ok() and finished_run:
175
199
  return check_folder
176
200
  elif check_folder.is_ok():
@@ -183,7 +207,7 @@ class RunController(ABCTableController, abc.ABC):
183
207
  except Exception:
184
208
  self._reset_time()
185
209
  return self.data_files[-1]
186
- return Err("Run did not complete as expected")
210
+ return Err("Run not may not have completed.")
187
211
 
188
212
  def get_uv_spectrum(self, path: str):
189
213
  data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
@@ -212,9 +236,7 @@ class RunController(ABCTableController, abc.ABC):
212
236
  raise ValueError("Expected one of ReportType.TXT or ReportType.CSV")
213
237
 
214
238
  def get_spectrum_at_channels(self, data_path: str):
215
- """
216
- Load chromatogram for any channel in spectra dictionary.
217
- """
239
+ """Load chromatogram for any channel in spectra dictionary."""
218
240
  for channel, spec in self.spectra.items():
219
241
  try:
220
242
  spec.load_spectrum(data_path=data_path, channel=channel)
@@ -226,3 +248,16 @@ class RunController(ABCTableController, abc.ABC):
226
248
  def _reset_time(self):
227
249
  self.curr_run_starting_time = None
228
250
  self.timeout = None
251
+
252
+ def get_current_run_data_dir_file(self) -> Tuple[str, str]:
253
+ self.send(Command.GET_CURRENT_RUN_DATA_DIR)
254
+ full_path_name = self.receive()
255
+ self.send(Command.GET_CURRENT_RUN_DATA_FILE)
256
+ current_sample_file = self.receive()
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.")
@@ -7,33 +7,39 @@ Authors: Lucy Hao
7
7
  from __future__ import annotations
8
8
 
9
9
  import abc
10
- import warnings
11
10
  from typing import Optional, Union
12
11
 
13
12
  from result import Err, Result
14
13
 
15
- from ....control.controllers.comm import CommunicationController
16
- from ....utils.macro import Command, Response
17
- from ....utils.method_types import MethodDetails
18
- from ....utils.sequence_types import SequenceTable
19
- from ....utils.table_types import RegisterFlag, Table, TableOperation
14
+ from ..macro import Command, Response
15
+ from ..method_types import MethodDetails
16
+ from ..sequence_types import SequenceTable
17
+ from ..table_types import Table, RegisterFlag, TableOperation
18
+ from ...control.controllers import CommunicationController
20
19
 
21
20
  TableType = Union[MethodDetails, SequenceTable]
22
21
 
23
22
 
24
23
  class ABCTableController(abc.ABC):
24
+ """Abstract controller for all table-like objects in Chemstation.
25
+ :param controller: controller for sending MACROs to Chemstation
26
+ :param table: contains register keys needed for accessing table in Chemstation.
27
+ """
28
+
25
29
  def __init__(
26
30
  self,
27
31
  controller: Optional[CommunicationController],
28
32
  table: Table,
29
33
  ):
30
- warnings.warn(
31
- "This abstract class is not meant to be initialized. Use MethodController or SequenceController."
32
- )
33
34
  self.controller = controller
34
35
  self.table_locator = table
35
36
  self.table_state: Optional[TableType] = None
36
37
 
38
+ def __new__(cls, *args, **kwargs):
39
+ if cls is ABCTableController:
40
+ raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
41
+ return object.__new__(cls, *args, **kwargs)
42
+
37
43
  def receive(self) -> Result[Response, str]:
38
44
  if self.controller:
39
45
  for _ in range(10):
@@ -59,8 +65,7 @@ class ABCTableController(abc.ABC):
59
65
  raise ValueError("Controller is offline")
60
66
 
61
67
  def sleep(self, seconds: int):
62
- """
63
- Tells the HPLC to wait for a specified number of seconds.
68
+ """Tells the HPLC to wait for a specified number of seconds.
64
69
 
65
70
  :param seconds: number of seconds to wait
66
71
  """
@@ -93,6 +98,8 @@ class ABCTableController(abc.ABC):
93
98
  raise ValueError("Controller is offline")
94
99
 
95
100
  def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
101
+ if not (isinstance(val, int) or isinstance(val, float)):
102
+ raise ValueError(f"{val} must be an int or float.")
96
103
  self.sleepy_send(
97
104
  TableOperation.NEW_COL_VAL.value.format(
98
105
  register=self.table_locator.register,
@@ -103,6 +110,8 @@ class ABCTableController(abc.ABC):
103
110
  )
104
111
 
105
112
  def add_new_col_text(self, col_name: RegisterFlag, val: str):
113
+ if not isinstance(val, str):
114
+ raise ValueError(f"{val} must be a str.")
106
115
  self.sleepy_send(
107
116
  TableOperation.NEW_COL_TEXT.value.format(
108
117
  register=self.table_locator.register,
@@ -115,10 +124,12 @@ class ABCTableController(abc.ABC):
115
124
  def _edit_row_num(
116
125
  self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
117
126
  ):
127
+ if not (isinstance(val, int) or isinstance(val, float)):
128
+ raise ValueError(f"{val} must be an int or float.")
118
129
  if row:
119
130
  num_rows = self.get_num_rows()
120
131
  if num_rows.is_ok():
121
- if num_rows.value.num_response < row:
132
+ if num_rows.ok_value.num_response < row:
122
133
  raise ValueError("Not enough rows to edit!")
123
134
 
124
135
  self.sleepy_send(
@@ -134,10 +145,12 @@ class ABCTableController(abc.ABC):
134
145
  def _edit_row_text(
135
146
  self, col_name: RegisterFlag, val: str, row: Optional[int] = None
136
147
  ):
148
+ if not isinstance(val, str):
149
+ raise ValueError(f"{val} must be a str.")
137
150
  if row:
138
151
  num_rows = self.get_num_rows()
139
152
  if num_rows.is_ok():
140
- if num_rows.value.num_response < row:
153
+ if num_rows.ok_value.num_response < row:
141
154
  raise ValueError("Not enough rows to edit!")
142
155
 
143
156
  self.sleepy_send(
@@ -164,9 +177,7 @@ class ABCTableController(abc.ABC):
164
177
  )
165
178
 
166
179
  def add_row(self):
167
- """
168
- Adds a row to the provided table for currently loaded method or sequence.
169
- """
180
+ """Adds a row to the provided table for currently loaded method or sequence."""
170
181
  self.sleepy_send(
171
182
  TableOperation.NEW_ROW.value.format(
172
183
  register=self.table_locator.register, table_name=self.table_locator.name
@@ -174,9 +185,7 @@ class ABCTableController(abc.ABC):
174
185
  )
175
186
 
176
187
  def delete_table(self):
177
- """
178
- Deletes the table for the current loaded method or sequence.
179
- """
188
+ """Deletes the table."""
180
189
  self.sleepy_send(
181
190
  TableOperation.DELETE_TABLE.value.format(
182
191
  register=self.table_locator.register, table_name=self.table_locator.name
@@ -184,9 +193,7 @@ class ABCTableController(abc.ABC):
184
193
  )
185
194
 
186
195
  def new_table(self):
187
- """
188
- Creates the table for the currently loaded method or sequence.
189
- """
196
+ """Creates the table."""
190
197
  self.send(
191
198
  TableOperation.CREATE_TABLE.value.format(
192
199
  register=self.table_locator.register, table_name=self.table_locator.name