pychemstation 0.10.4__py3-none-any.whl → 0.10.6__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 (34) hide show
  1. pychemstation/analysis/__init__.py +8 -1
  2. pychemstation/analysis/chromatogram.py +20 -0
  3. pychemstation/analysis/process_report.py +125 -63
  4. pychemstation/control/__init__.py +2 -0
  5. pychemstation/control/controllers/__init__.py +2 -3
  6. pychemstation/control/controllers/abc_tables/device.py +15 -0
  7. pychemstation/control/controllers/abc_tables/run.py +228 -0
  8. pychemstation/control/controllers/abc_tables/table.py +221 -0
  9. pychemstation/control/controllers/comm.py +25 -106
  10. pychemstation/control/controllers/data_aq/__init__.py +4 -0
  11. pychemstation/control/controllers/{tables → data_aq}/method.py +52 -95
  12. pychemstation/control/controllers/{tables → data_aq}/sequence.py +199 -141
  13. pychemstation/control/controllers/devices/__init__.py +3 -0
  14. pychemstation/control/controllers/devices/injector.py +69 -24
  15. pychemstation/control/hplc.py +15 -17
  16. pychemstation/utils/injector_types.py +23 -3
  17. pychemstation/utils/macro.py +2 -2
  18. pychemstation/utils/method_types.py +1 -1
  19. pychemstation/utils/mocking/__init__.py +0 -0
  20. pychemstation/utils/mocking/abc_comm.py +160 -0
  21. pychemstation/utils/mocking/mock_comm.py +5 -0
  22. pychemstation/utils/mocking/mock_hplc.py +2 -0
  23. pychemstation/utils/sequence_types.py +19 -0
  24. pychemstation/utils/table_types.py +6 -0
  25. pychemstation/utils/tray_types.py +36 -1
  26. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/METADATA +4 -4
  27. pychemstation-0.10.6.dist-info/RECORD +42 -0
  28. pychemstation/control/controllers/devices/device.py +0 -49
  29. pychemstation/control/controllers/tables/ms.py +0 -24
  30. pychemstation/control/controllers/tables/table.py +0 -375
  31. pychemstation-0.10.4.dist-info/RECORD +0 -37
  32. /pychemstation/control/controllers/{tables → abc_tables}/__init__.py +0 -0
  33. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/WHEEL +0 -0
  34. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,221 @@
1
+ """
2
+ Abstract module containing shared logic for Method and Sequence tables.
3
+
4
+ Authors: Lucy Hao
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import abc
10
+ import warnings
11
+ from typing import Optional, Union
12
+
13
+ from result import Err, Result
14
+
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
20
+
21
+ TableType = Union[MethodDetails, SequenceTable]
22
+
23
+
24
+ class ABCTableController(abc.ABC):
25
+ def __init__(
26
+ self,
27
+ controller: Optional[CommunicationController],
28
+ table: Table,
29
+ ):
30
+ warnings.warn(
31
+ "This abstract class is not meant to be initialized. Use MethodController or SequenceController."
32
+ )
33
+ self.controller = controller
34
+ self.table_locator = table
35
+ self.table_state: Optional[TableType] = None
36
+
37
+ def receive(self) -> Result[Response, str]:
38
+ if self.controller:
39
+ for _ in range(10):
40
+ try:
41
+ return self.controller.receive()
42
+ except IndexError:
43
+ continue
44
+ return Err("Could not parse response")
45
+ else:
46
+ raise ValueError("Controller is offline!")
47
+
48
+ def send(self, cmd: Union[Command, str]):
49
+ if not self.controller:
50
+ raise RuntimeError(
51
+ "Communication controller must be initialized before sending command. It is currently in offline mode."
52
+ )
53
+ self.controller.send(cmd)
54
+
55
+ def sleepy_send(self, cmd: Union[Command, str]):
56
+ if self.controller:
57
+ self.controller.sleepy_send(cmd)
58
+ else:
59
+ raise ValueError("Controller is offline")
60
+
61
+ def sleep(self, seconds: int):
62
+ """
63
+ Tells the HPLC to wait for a specified number of seconds.
64
+
65
+ :param seconds: number of seconds to wait
66
+ """
67
+ self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
68
+
69
+ def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
70
+ if self.controller:
71
+ return self.controller.get_num_val(
72
+ TableOperation.GET_ROW_VAL.value.format(
73
+ register=self.table_locator.register,
74
+ table_name=self.table_locator.name,
75
+ row=row,
76
+ col_name=col_name.value,
77
+ )
78
+ )
79
+ else:
80
+ raise ValueError("Controller is offline")
81
+
82
+ def get_text(self, row: int, col_name: RegisterFlag) -> str:
83
+ if self.controller:
84
+ return self.controller.get_text_val(
85
+ TableOperation.GET_ROW_TEXT.value.format(
86
+ register=self.table_locator.register,
87
+ table_name=self.table_locator.name,
88
+ row=row,
89
+ col_name=col_name.value,
90
+ )
91
+ )
92
+ else:
93
+ raise ValueError("Controller is offline")
94
+
95
+ def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
96
+ self.sleepy_send(
97
+ TableOperation.NEW_COL_VAL.value.format(
98
+ register=self.table_locator.register,
99
+ table_name=self.table_locator.name,
100
+ col_name=col_name,
101
+ val=val,
102
+ )
103
+ )
104
+
105
+ def add_new_col_text(self, col_name: RegisterFlag, val: str):
106
+ self.sleepy_send(
107
+ TableOperation.NEW_COL_TEXT.value.format(
108
+ register=self.table_locator.register,
109
+ table_name=self.table_locator.name,
110
+ col_name=col_name,
111
+ val=val,
112
+ )
113
+ )
114
+
115
+ def _edit_row_num(
116
+ self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
117
+ ):
118
+ if row:
119
+ num_rows = self.get_num_rows()
120
+ if num_rows.is_ok():
121
+ if num_rows.value.num_response < row:
122
+ raise ValueError("Not enough rows to edit!")
123
+
124
+ self.sleepy_send(
125
+ TableOperation.EDIT_ROW_VAL.value.format(
126
+ register=self.table_locator.register,
127
+ table_name=self.table_locator.name,
128
+ row=row if row is not None else "Rows",
129
+ col_name=col_name,
130
+ val=val,
131
+ )
132
+ )
133
+
134
+ def _edit_row_text(
135
+ self, col_name: RegisterFlag, val: str, row: Optional[int] = None
136
+ ):
137
+ if row:
138
+ num_rows = self.get_num_rows()
139
+ if num_rows.is_ok():
140
+ if num_rows.value.num_response < row:
141
+ raise ValueError("Not enough rows to edit!")
142
+
143
+ self.sleepy_send(
144
+ TableOperation.EDIT_ROW_TEXT.value.format(
145
+ register=self.table_locator.register,
146
+ table_name=self.table_locator.name,
147
+ row=row if row is not None else "Rows",
148
+ col_name=col_name,
149
+ val=val,
150
+ )
151
+ )
152
+
153
+ @abc.abstractmethod
154
+ def get_row(self, row: int):
155
+ pass
156
+
157
+ def delete_row(self, row: int):
158
+ self.sleepy_send(
159
+ TableOperation.DELETE_ROW.value.format(
160
+ register=self.table_locator.register,
161
+ table_name=self.table_locator.name,
162
+ row=row,
163
+ )
164
+ )
165
+
166
+ def add_row(self):
167
+ """
168
+ Adds a row to the provided table for currently loaded method or sequence.
169
+ """
170
+ self.sleepy_send(
171
+ TableOperation.NEW_ROW.value.format(
172
+ register=self.table_locator.register, table_name=self.table_locator.name
173
+ )
174
+ )
175
+
176
+ def delete_table(self):
177
+ """
178
+ Deletes the table for the current loaded method or sequence.
179
+ """
180
+ self.sleepy_send(
181
+ TableOperation.DELETE_TABLE.value.format(
182
+ register=self.table_locator.register, table_name=self.table_locator.name
183
+ )
184
+ )
185
+
186
+ def new_table(self):
187
+ """
188
+ Creates the table for the currently loaded method or sequence.
189
+ """
190
+ self.send(
191
+ TableOperation.CREATE_TABLE.value.format(
192
+ register=self.table_locator.register, table_name=self.table_locator.name
193
+ )
194
+ )
195
+
196
+ def get_num_rows(self) -> Result[Response, str]:
197
+ self.send(
198
+ TableOperation.GET_NUM_ROWS.value.format(
199
+ register=self.table_locator.register,
200
+ table_name=self.table_locator.name,
201
+ col_name=RegisterFlag.NUM_ROWS,
202
+ )
203
+ )
204
+ self.send(
205
+ Command.GET_ROWS_CMD.value.format(
206
+ register=self.table_locator.register,
207
+ table_name=self.table_locator.name,
208
+ col_name=RegisterFlag.NUM_ROWS,
209
+ )
210
+ )
211
+ if self.controller:
212
+ res = self.controller.receive()
213
+ else:
214
+ raise ValueError("Controller is offline")
215
+
216
+ if res.is_ok():
217
+ self.send("Sleep 0.1")
218
+ self.send("Print Rows")
219
+ return res
220
+ else:
221
+ return Err("No rows could be read.")
@@ -10,7 +10,6 @@ been processed.
10
10
  Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
11
  """
12
12
 
13
- import os
14
13
  import time
15
14
  from typing import Optional, Union
16
15
 
@@ -18,27 +17,24 @@ from result import Err, Ok, Result
18
17
 
19
18
  from ...utils.macro import (
20
19
  str_to_status,
21
- HPLCAvailStatus,
22
20
  HPLCErrorStatus,
23
21
  Command,
24
22
  Status,
25
- Response,
26
23
  )
24
+ from ...utils.mocking.abc_comm import ABCCommunicationController
27
25
 
28
26
 
29
- class CommunicationController:
27
+ class CommunicationController(ABCCommunicationController):
30
28
  """
31
29
  Class that communicates with Agilent using Macros
32
30
  """
33
31
 
34
- # maximum command number
35
- MAX_CMD_NO = 255
36
-
37
32
  def __init__(
38
33
  self,
39
34
  comm_dir: str,
40
35
  cmd_file: str = "cmd",
41
36
  reply_file: str = "reply",
37
+ offline: bool = False,
42
38
  debug: bool = False,
43
39
  ):
44
40
  """
@@ -47,26 +43,10 @@ class CommunicationController:
47
43
  :param reply_file: Name of reply file
48
44
  :param debug: whether to save log of sent commands
49
45
  """
50
- self.debug = debug
51
- if os.path.isdir(comm_dir):
52
- self.cmd_file = os.path.join(comm_dir, cmd_file)
53
- self.reply_file = os.path.join(comm_dir, reply_file)
54
- self.cmd_no = 0
55
- else:
56
- raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
57
- self._most_recent_hplc_status: Optional[Status] = None
58
-
59
- # Create files for Chemstation to communicate with Python
60
- open(self.cmd_file, "a").close()
61
- open(self.reply_file, "a").close()
62
-
63
- self.reset_cmd_counter()
64
-
65
- # Initialize row counter for table operations
66
- self.send("Local Rows")
46
+ super().__init__(comm_dir, cmd_file, reply_file, offline, debug)
67
47
 
68
48
  def get_num_val(self, cmd: str) -> Union[int, float]:
69
- tries = 5
49
+ tries = 10
70
50
  for _ in range(tries):
71
51
  self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
72
52
  res = self.receive()
@@ -75,7 +55,7 @@ class CommunicationController:
75
55
  raise RuntimeError("Failed to get number.")
76
56
 
77
57
  def get_text_val(self, cmd: str) -> str:
78
- tries = 5
58
+ tries = 10
79
59
  for _ in range(tries):
80
60
  self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
81
61
  res = self.receive()
@@ -92,29 +72,20 @@ class CommunicationController:
92
72
  time.sleep(1)
93
73
 
94
74
  try:
95
- parsed_response = self.receive().value.string_response
96
- self._most_recent_hplc_status = str_to_status(parsed_response)
97
- return self._most_recent_hplc_status
75
+ res = self.receive()
76
+ if res.is_err():
77
+ return HPLCErrorStatus.NORESPONSE
78
+ if res.is_ok():
79
+ parsed_response = self.receive().value.string_response
80
+ self._most_recent_hplc_status = str_to_status(parsed_response)
81
+ return self._most_recent_hplc_status
82
+ else:
83
+ raise RuntimeError("Failed to get status")
98
84
  except IOError:
99
85
  return HPLCErrorStatus.NORESPONSE
100
86
  except IndexError:
101
87
  return HPLCErrorStatus.MALFORMED
102
88
 
103
- def set_status(self):
104
- """Updates current status of HPLC machine"""
105
- self._most_recent_hplc_status = self.get_status()
106
-
107
- def check_if_not_running(self) -> bool:
108
- """Checks if HPLC machine is in an available state, meaning a state that data is not being written.
109
-
110
- :return: whether the HPLC machine is in a safe state to retrieve data back."""
111
- self.set_status()
112
- hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
113
- time.sleep(5)
114
- self.set_status()
115
- hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
116
- return hplc_avail and hplc_actually_avail
117
-
118
89
  def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
119
90
  """Low-level execution primitive. Sends a command string to HPLC.
120
91
 
@@ -145,7 +116,8 @@ class CommunicationController:
145
116
  :raises IOError: Could not read reply file.
146
117
  :return: Potential ChemStation response
147
118
  """
148
- err: Optional[Union[OSError, IndexError]] = None
119
+ err: Optional[Union[OSError, IndexError, ValueError]] = None
120
+ err_msg = ""
149
121
  for _ in range(num_attempts):
150
122
  time.sleep(1)
151
123
 
@@ -158,7 +130,11 @@ class CommunicationController:
158
130
 
159
131
  try:
160
132
  first_line = response.splitlines()[0]
161
- response_no = int(first_line.split()[0])
133
+ try:
134
+ response_no = int(first_line.split()[0])
135
+ except ValueError as e:
136
+ err = e
137
+ err_msg = f"Caused by {first_line}"
162
138
  except IndexError as e:
163
139
  err = e
164
140
  continue
@@ -169,63 +145,6 @@ class CommunicationController:
169
145
  else:
170
146
  continue
171
147
  else:
172
- return Err(f"Failed to receive reply to command #{cmd_no} due to {err}.")
173
-
174
- def sleepy_send(self, cmd: Union[Command, str]):
175
- self.send("Sleep 0.1")
176
- self.send(cmd)
177
- self.send("Sleep 0.1")
178
-
179
- def send(self, cmd: Union[Command, str]):
180
- """Sends a command to Chemstation.
181
-
182
- :param cmd: Command to be sent to HPLC
183
- """
184
- if self.cmd_no == self.MAX_CMD_NO:
185
- self.reset_cmd_counter()
186
-
187
- cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
188
- self.cmd_no += 1
189
- self._send(cmd_to_send, self.cmd_no)
190
- if self.debug:
191
- f = open("out.txt", "a")
192
- f.write(cmd_to_send + "\n")
193
- f.close()
194
-
195
- def receive(self) -> Result[Response, str]:
196
- """Returns messages received in reply file.
197
-
198
- :return: ChemStation response
199
- """
200
- num_response_prefix = "Numerical Responses:"
201
- str_response_prefix = "String Responses:"
202
- possible_response = self._receive(self.cmd_no)
203
- if possible_response.is_ok():
204
- lines = possible_response.ok_value.splitlines()
205
- for line in lines:
206
- if str_response_prefix in line and num_response_prefix in line:
207
- string_responses_dirty, _, numerical_responses = line.partition(
208
- num_response_prefix
209
- )
210
- _, _, string_responses = string_responses_dirty.partition(
211
- str_response_prefix
212
- )
213
- return Ok(
214
- Response(
215
- string_response=string_responses.strip(),
216
- num_response=float(numerical_responses.strip()),
217
- )
218
- )
219
- return Err("Could not retrieve HPLC response")
220
- else:
221
- return Err(f"Could not establish response to HPLC: {possible_response}")
222
-
223
- def reset_cmd_counter(self):
224
- """Resets the command counter."""
225
- self._send(Command.RESET_COUNTER_CMD.value, cmd_no=self.MAX_CMD_NO + 1)
226
- self._receive(cmd_no=self.MAX_CMD_NO + 1)
227
- self.cmd_no = 0
228
-
229
- def stop_macro(self):
230
- """Stops Macro execution. Connection will be lost."""
231
- self.send(Command.STOP_MACRO_CMD)
148
+ return Err(
149
+ f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
150
+ )
@@ -0,0 +1,4 @@
1
+ from .method import MethodController
2
+ from .sequence import SequenceController
3
+
4
+ __all__ = ["MethodController", "SequenceController"]
@@ -1,14 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import time
3
5
  import warnings
4
- from typing import Dict, List, Optional, Union
6
+ from typing import List, Optional, Union, Dict
5
7
 
6
8
  from result import Err, Ok, Result
7
- from xsdata.formats.dataclass.parsers import XmlParser
8
9
 
10
+ from ..abc_tables.run import RunController
9
11
  from ....analysis.process_report import AgilentReport, ReportType
10
12
  from ....control.controllers import CommunicationController
11
- from ....generated import DadMethod, PumpMethod, SolventElement
12
13
  from pychemstation.analysis.chromatogram import (
13
14
  TIME_FORMAT,
14
15
  AgilentChannelChromatogramData,
@@ -22,12 +23,11 @@ from ....utils.method_types import (
22
23
  PType,
23
24
  TimeTableEntry,
24
25
  )
25
- from ....utils.table_types import RegisterFlag, T, Table, TableOperation
26
+ from ....utils.table_types import RegisterFlag, Table, TableOperation, T
26
27
  from ..devices.injector import InjectorController
27
- from .table import TableController
28
28
 
29
29
 
30
- class MethodController(TableController):
30
+ class MethodController(RunController):
31
31
  """
32
32
  Class containing method related logic
33
33
  """
@@ -61,20 +61,22 @@ class MethodController(TableController):
61
61
  return "ERROR"
62
62
 
63
63
  def get_method_params(self) -> HPLCMethodParams:
64
- return HPLCMethodParams(
65
- organic_modifier=self.controller.get_num_val(
66
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
67
- register=self.table_locator.register,
68
- register_flag=RegisterFlag.SOLVENT_B_COMPOSITION,
69
- )
70
- ),
71
- flow=self.controller.get_num_val(
72
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
73
- register=self.table_locator.register,
74
- register_flag=RegisterFlag.FLOW,
75
- )
76
- ),
77
- )
64
+ if self.controller:
65
+ return HPLCMethodParams(
66
+ organic_modifier=self.controller.get_num_val(
67
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
68
+ register=self.table_locator.register,
69
+ register_flag=RegisterFlag.SOLVENT_B_COMPOSITION,
70
+ )
71
+ ),
72
+ flow=self.controller.get_num_val(
73
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
74
+ register=self.table_locator.register,
75
+ register_flag=RegisterFlag.FLOW,
76
+ )
77
+ ),
78
+ )
79
+ raise ValueError("Communication controller is offline!")
78
80
 
79
81
  def get_row(self, row: int) -> TimeTableEntry:
80
82
  flow = None
@@ -89,6 +91,9 @@ class MethodController(TableController):
89
91
  except RuntimeError:
90
92
  pass
91
93
 
94
+ if om is None and flow is None:
95
+ raise ValueError("Both flow and organic modifier is None")
96
+
92
97
  return TimeTableEntry(
93
98
  start_time=self.get_num(row, RegisterFlag.TIME),
94
99
  organic_modifer=om,
@@ -97,7 +102,7 @@ class MethodController(TableController):
97
102
 
98
103
  def get_timetable(self, rows: int):
99
104
  uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
100
- timetable_rows = {}
105
+ timetable_rows: Dict[str, TimeTableEntry] = {}
101
106
  for row in uncoalesced_timetable_rows:
102
107
  time_key = str(row.start_time)
103
108
  if time_key not in timetable_rows.keys():
@@ -141,20 +146,24 @@ class MethodController(TableController):
141
146
  return method_name
142
147
 
143
148
  def get_post_time(self) -> Union[int, float]:
144
- return self.controller.get_num_val(
145
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
146
- register=self.table_locator.register,
147
- register_flag=RegisterFlag.POST_TIME,
149
+ if self.controller:
150
+ return self.controller.get_num_val(
151
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
152
+ register=self.table_locator.register,
153
+ register_flag=RegisterFlag.POST_TIME,
154
+ )
148
155
  )
149
- )
156
+ raise ValueError("Communication controller is not online!")
150
157
 
151
158
  def get_stop_time(self) -> Union[int, float]:
152
- return self.controller.get_num_val(
153
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
154
- register=self.table_locator.register,
155
- register_flag=RegisterFlag.MAX_TIME,
159
+ if self.controller:
160
+ return self.controller.get_num_val(
161
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
162
+ register=self.table_locator.register,
163
+ register_flag=RegisterFlag.MAX_TIME,
164
+ )
156
165
  )
157
- )
166
+ raise ValueError("Communication controller is not online!")
158
167
 
159
168
  def get_total_runtime(self) -> Union[int, float]:
160
169
  """Returns total method runtime in minutes."""
@@ -198,60 +207,6 @@ class MethodController(TableController):
198
207
  assert parsed_response == f"{method_name}.M", "Switching Methods failed."
199
208
  self.table_state = None
200
209
 
201
- def load_from_disk(self, method_name: str) -> MethodDetails:
202
- """
203
- Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
204
- organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
205
- only two solvents are being used.
206
-
207
- :param method_name: name of method to load details of
208
- :raises FileNotFoundError: Method does not exist
209
- :return: method details
210
- """
211
- warnings.warn("This method is not actively maintained.")
212
- method_folder = f"{method_name}.M"
213
- method_path = os.path.join(
214
- self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml"
215
- )
216
- dad_path = os.path.join(
217
- self.src,
218
- method_folder,
219
- "Agilent1200erDadDriver1.RapidControl.MethodXML.xml",
220
- )
221
-
222
- if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
223
- parser = XmlParser()
224
- method = parser.parse(method_path, PumpMethod)
225
- dad = parser.parse(dad_path, DadMethod)
226
-
227
- organic_modifier: Optional[SolventElement] = None
228
-
229
- if len(method.solvent_composition.solvent_element) == 2:
230
- for solvent in method.solvent_composition.solvent_element:
231
- if solvent.channel == "Channel_B":
232
- organic_modifier = solvent
233
-
234
- self.table_state = MethodDetails(
235
- name=method_name,
236
- params=HPLCMethodParams(
237
- organic_modifier=organic_modifier.percentage, flow=method.flow
238
- ),
239
- stop_time=method.stop_time.stop_time_value,
240
- post_time=method.post_time.post_time_value,
241
- timetable=[
242
- TimeTableEntry(
243
- start_time=tte.time,
244
- organic_modifer=tte.percent_b,
245
- flow=method.flow,
246
- )
247
- for tte in method.timetable.timetable_entry
248
- ],
249
- dad_wavelengthes=dad.signals.signal,
250
- )
251
- return self.table_state
252
- else:
253
- raise FileNotFoundError
254
-
255
210
  def edit(self, updated_method: MethodDetails, save: bool):
256
211
  """Updated the currently loaded method in ChemStation with provided values.
257
212
 
@@ -317,13 +272,13 @@ class MethodController(TableController):
317
272
  self,
318
273
  new_flow: Union[int, float],
319
274
  new_initial_om: Union[int, float],
320
- new_stop_time: Union[int, float],
321
- new_post_time: Union[int, float],
275
+ new_stop_time: Union[int, float] | None,
276
+ new_post_time: Union[int, float] | None,
322
277
  ):
323
278
  self.delete_table()
324
279
  self.edit_initial_om(new_initial_om)
325
280
  self.edit_flow(new_flow)
326
- if self.table_state.stop_time:
281
+ if new_stop_time:
327
282
  self.edit_stop_time(new_stop_time)
328
283
  else:
329
284
  self._update_param(
@@ -333,7 +288,7 @@ class MethodController(TableController):
333
288
  ptype=PType.STR,
334
289
  )
335
290
  )
336
- if self.table_state.post_time:
291
+ if new_post_time:
337
292
  self.edit_post_time(new_post_time)
338
293
  else:
339
294
  self._update_param(
@@ -502,21 +457,23 @@ class MethodController(TableController):
502
457
  warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
503
458
  warnings.warn(warning)
504
459
 
505
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
506
- if os.path.exists(most_recent_folder):
507
- return Ok(most_recent_folder)
508
- return Err("Folder not found!")
460
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[str, str]:
461
+ if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
462
+ if os.path.exists(most_recent_folder):
463
+ return Ok(most_recent_folder)
464
+ return Err("Folder not found!")
465
+ raise ValueError("Folder is not a str or byte type.")
509
466
 
510
467
  def get_data(
511
468
  self, custom_path: Optional[str] = None
512
469
  ) -> AgilentChannelChromatogramData:
513
470
  custom_path = custom_path if custom_path else self.data_files[-1]
514
471
  self.get_spectrum_at_channels(custom_path)
515
- return AgilentChannelChromatogramData(**self.spectra)
472
+ return AgilentChannelChromatogramData.from_dict(self.spectra)
516
473
 
517
474
  def get_data_uv(
518
475
  self, custom_path: Optional[str] = None
519
- ) -> Dict[str, AgilentHPLCChromatogram]:
476
+ ) -> dict[int, AgilentHPLCChromatogram]:
520
477
  custom_path = custom_path if custom_path else self.data_files[-1]
521
478
  self.get_uv_spectrum(custom_path)
522
479
  return self.uv