pychemstation 0.10.3__py3-none-any.whl → 0.10.5__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/__init__.py +1 -1
  2. pychemstation/analysis/__init__.py +1 -6
  3. pychemstation/analysis/base_spectrum.py +7 -7
  4. pychemstation/{utils → analysis}/chromatogram.py +24 -4
  5. pychemstation/analysis/process_report.py +189 -90
  6. pychemstation/control/__init__.py +5 -2
  7. pychemstation/control/controllers/__init__.py +2 -7
  8. pychemstation/control/controllers/comm.py +56 -32
  9. pychemstation/control/controllers/devices/device.py +59 -24
  10. pychemstation/control/controllers/devices/injector.py +33 -10
  11. pychemstation/control/controllers/tables/__init__.py +4 -0
  12. pychemstation/control/controllers/tables/method.py +241 -151
  13. pychemstation/control/controllers/tables/sequence.py +226 -107
  14. pychemstation/control/controllers/tables/table.py +216 -132
  15. pychemstation/control/hplc.py +89 -75
  16. pychemstation/generated/__init__.py +0 -2
  17. pychemstation/generated/pump_method.py +15 -19
  18. pychemstation/utils/injector_types.py +1 -1
  19. pychemstation/utils/macro.py +11 -10
  20. pychemstation/utils/method_types.py +2 -1
  21. pychemstation/utils/parsing.py +0 -11
  22. pychemstation/utils/sequence_types.py +2 -3
  23. pychemstation/utils/spec_utils.py +2 -3
  24. pychemstation/utils/table_types.py +10 -9
  25. pychemstation/utils/tray_types.py +45 -36
  26. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/METADATA +5 -4
  27. pychemstation-0.10.5.dist-info/RECORD +36 -0
  28. pychemstation/control/controllers/tables/ms.py +0 -21
  29. pychemstation-0.10.3.dist-info/RECORD +0 -37
  30. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/licenses/LICENSE +0 -0
@@ -9,6 +9,7 @@ been processed.
9
9
 
10
10
  Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
11
11
  """
12
+
12
13
  import os
13
14
  import time
14
15
  from typing import Optional, Union
@@ -34,11 +35,12 @@ class CommunicationController:
34
35
  MAX_CMD_NO = 255
35
36
 
36
37
  def __init__(
37
- self,
38
- comm_dir: str,
39
- cmd_file: str = "cmd",
40
- reply_file: str = "reply",
41
- debug: bool = False
38
+ self,
39
+ comm_dir: str,
40
+ cmd_file: str = "cmd",
41
+ reply_file: str = "reply",
42
+ offline: bool = False,
43
+ debug: bool = False,
42
44
  ):
43
45
  """
44
46
  :param comm_dir:
@@ -46,26 +48,27 @@ class CommunicationController:
46
48
  :param reply_file: Name of reply file
47
49
  :param debug: whether to save log of sent commands
48
50
  """
49
- self.debug = debug
50
- if os.path.isdir(comm_dir):
51
- self.cmd_file = os.path.join(comm_dir, cmd_file)
52
- self.reply_file = os.path.join(comm_dir, reply_file)
53
- self.cmd_no = 0
54
- else:
55
- raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
56
- self._most_recent_hplc_status: Optional[Status] = None
51
+ if not offline:
52
+ self.debug = debug
53
+ if os.path.isdir(comm_dir):
54
+ self.cmd_file = os.path.join(comm_dir, cmd_file)
55
+ self.reply_file = os.path.join(comm_dir, reply_file)
56
+ self.cmd_no = 0
57
+ else:
58
+ raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
57
59
 
58
- # Create files for Chemstation to communicate with Python
59
- open(self.cmd_file, "a").close()
60
- open(self.reply_file, "a").close()
60
+ # Create files for Chemstation to communicate with Python
61
+ open(self.cmd_file, "a").close()
62
+ open(self.reply_file, "a").close()
61
63
 
62
- self.reset_cmd_counter()
64
+ self.reset_cmd_counter()
63
65
 
64
- # Initialize row counter for table operations
65
- self.send('Local Rows')
66
+ # Initialize row counter for table operations
67
+ self._most_recent_hplc_status: Status = self.get_status()
68
+ self.send("Local Rows")
66
69
 
67
70
  def get_num_val(self, cmd: str) -> Union[int, float]:
68
- tries = 5
71
+ tries = 10
69
72
  for _ in range(tries):
70
73
  self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
71
74
  res = self.receive()
@@ -74,7 +77,7 @@ class CommunicationController:
74
77
  raise RuntimeError("Failed to get number.")
75
78
 
76
79
  def get_text_val(self, cmd: str) -> str:
77
- tries = 5
80
+ tries = 10
78
81
  for _ in range(tries):
79
82
  self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
80
83
  res = self.receive()
@@ -91,9 +94,15 @@ class CommunicationController:
91
94
  time.sleep(1)
92
95
 
93
96
  try:
94
- parsed_response = self.receive().value.string_response
95
- self._most_recent_hplc_status = str_to_status(parsed_response)
96
- return self._most_recent_hplc_status
97
+ res = self.receive()
98
+ if res.is_err():
99
+ return HPLCErrorStatus.NORESPONSE
100
+ if res.is_ok():
101
+ parsed_response = self.receive().value.string_response
102
+ self._most_recent_hplc_status = str_to_status(parsed_response)
103
+ return self._most_recent_hplc_status
104
+ else:
105
+ raise RuntimeError("Failed to get status")
97
106
  except IOError:
98
107
  return HPLCErrorStatus.NORESPONSE
99
108
  except IndexError:
@@ -144,7 +153,8 @@ class CommunicationController:
144
153
  :raises IOError: Could not read reply file.
145
154
  :return: Potential ChemStation response
146
155
  """
147
- err: Optional[Union[OSError, IndexError]] = None
156
+ err: Optional[Union[OSError, IndexError, ValueError]] = None
157
+ err_msg = ""
148
158
  for _ in range(num_attempts):
149
159
  time.sleep(1)
150
160
 
@@ -157,7 +167,11 @@ class CommunicationController:
157
167
 
158
168
  try:
159
169
  first_line = response.splitlines()[0]
160
- response_no = int(first_line.split()[0])
170
+ try:
171
+ response_no = int(first_line.split()[0])
172
+ except ValueError as e:
173
+ err = e
174
+ err_msg = f"Caused by {first_line}"
161
175
  except IndexError as e:
162
176
  err = e
163
177
  continue
@@ -168,7 +182,9 @@ class CommunicationController:
168
182
  else:
169
183
  continue
170
184
  else:
171
- return Err(f"Failed to receive reply to command #{cmd_no} due to {err}.")
185
+ return Err(
186
+ f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
187
+ )
172
188
 
173
189
  def sleepy_send(self, cmd: Union[Command, str]):
174
190
  self.send("Sleep 0.1")
@@ -194,7 +210,7 @@ class CommunicationController:
194
210
  def receive(self) -> Result[Response, str]:
195
211
  """Returns messages received in reply file.
196
212
 
197
- :return: ChemStation response
213
+ :return: ChemStation response
198
214
  """
199
215
  num_response_prefix = "Numerical Responses:"
200
216
  str_response_prefix = "String Responses:"
@@ -203,10 +219,18 @@ class CommunicationController:
203
219
  lines = possible_response.ok_value.splitlines()
204
220
  for line in lines:
205
221
  if str_response_prefix in line and num_response_prefix in line:
206
- string_responses_dirty, _, numerical_responses = line.partition(num_response_prefix)
207
- _, _, string_responses = string_responses_dirty.partition(str_response_prefix)
208
- return Ok(Response(string_response=string_responses.strip(),
209
- num_response=float(numerical_responses.strip())))
222
+ string_responses_dirty, _, numerical_responses = line.partition(
223
+ num_response_prefix
224
+ )
225
+ _, _, string_responses = string_responses_dirty.partition(
226
+ str_response_prefix
227
+ )
228
+ return Ok(
229
+ Response(
230
+ string_response=string_responses.strip(),
231
+ num_response=float(numerical_responses.strip()),
232
+ )
233
+ )
210
234
  return Err("Could not retrieve HPLC response")
211
235
  else:
212
236
  return Err(f"Could not establish response to HPLC: {possible_response}")
@@ -1,39 +1,74 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
- from typing import List, Union, Dict, Optional
4
+ from typing import Union
3
5
 
4
- from result import Result
6
+ from result import Err, Ok
5
7
 
6
- from ....analysis.process_report import AgilentReport, ReportType
7
8
  from ....control.controllers import CommunicationController
8
- from ....control.controllers.tables.table import TableController
9
- from ....utils.chromatogram import AgilentChannelChromatogramData, AgilentHPLCChromatogram
10
- from ....utils.table_types import T, Table
11
-
9
+ from ....utils.macro import Command, Response
10
+ from ....utils.table_types import RegisterFlag, Table, TableOperation
12
11
 
13
- class DeviceController(TableController, abc.ABC):
14
12
 
15
- def __init__(self, controller: CommunicationController, table: Table, offline: bool):
16
- super().__init__(controller=controller,
17
- src=None,
18
- data_dirs=[],
19
- table=table,
20
- offline=offline)
13
+ class DeviceController(abc.ABC):
14
+ def __init__(
15
+ self, controller: CommunicationController, table: Table, offline: bool
16
+ ):
17
+ self.table_locator = table
18
+ self.controller = controller
19
+ self.offline = offline
21
20
 
22
21
  @abc.abstractmethod
23
22
  def get_row(self, row: int):
24
23
  pass
25
24
 
26
- def retrieve_recent_data_files(self):
27
- raise NotImplementedError
25
+ def get_text(self, row: int, col_name: RegisterFlag) -> str:
26
+ return self.controller.get_text_val(
27
+ TableOperation.GET_ROW_TEXT.value.format(
28
+ register=self.table_locator.register,
29
+ table_name=self.table_locator.name,
30
+ row=row,
31
+ col_name=col_name.value,
32
+ )
33
+ )
28
34
 
29
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
30
- raise NotImplementedError
35
+ def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
36
+ return self.controller.get_num_val(
37
+ TableOperation.GET_ROW_VAL.value.format(
38
+ register=self.table_locator.register,
39
+ table_name=self.table_locator.name,
40
+ row=row,
41
+ col_name=col_name.value,
42
+ )
43
+ )
31
44
 
32
- def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
33
- raise NotImplementedError
45
+ def get_num_rows(self) -> Ok[Response] | Err[str]:
46
+ self.send(
47
+ TableOperation.GET_NUM_ROWS.value.format(
48
+ register=self.table_locator.register,
49
+ table_name=self.table_locator.name,
50
+ col_name=RegisterFlag.NUM_ROWS,
51
+ )
52
+ )
53
+ self.send(
54
+ Command.GET_ROWS_CMD.value.format(
55
+ register=self.table_locator.register,
56
+ table_name=self.table_locator.name,
57
+ col_name=RegisterFlag.NUM_ROWS,
58
+ )
59
+ )
60
+ res = self.controller.receive()
34
61
 
35
- def get_data_uv(self) -> Union[List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]]:
36
- raise NotImplementedError
62
+ if res.is_ok():
63
+ self.send("Sleep 0.1")
64
+ self.send("Print Rows")
65
+ return res
66
+ else:
67
+ return Err("No rows could be read.")
37
68
 
38
- def get_data(self, custom_path: Optional[str] = None) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
39
- raise NotImplementedError
69
+ def send(self, cmd: Union[Command, str]):
70
+ if not self.controller:
71
+ raise RuntimeError(
72
+ "Communication controller must be initialized before sending command. It is currently in offline mode."
73
+ )
74
+ self.controller.send(cmd)
@@ -1,4 +1,3 @@
1
-
2
1
  from ....control.controllers import CommunicationController
3
2
  from ....utils.injector_types import (
4
3
  Draw,
@@ -11,19 +10,26 @@ from ....utils.injector_types import (
11
10
  SourceType,
12
11
  Wait,
13
12
  )
13
+ from ....utils.macro import Response
14
14
  from ....utils.table_types import RegisterFlag, Table
15
15
  from ....utils.tray_types import Tray
16
16
  from .device import DeviceController
17
17
 
18
18
 
19
19
  class InjectorController(DeviceController):
20
-
21
- def __init__(self, controller: CommunicationController, table: Table, offline: bool):
20
+ def __init__(
21
+ self, controller: CommunicationController, table: Table, offline: bool
22
+ ):
22
23
  super().__init__(controller, table, offline)
23
24
 
24
25
  def get_row(self, row: int) -> InjectorFunction:
25
26
  def return_tray_loc() -> Tray:
26
- pass
27
+ raise NotImplementedError
28
+ # unit = self.get_text(row, RegisterFlag.DRAW_LOCATION_UNIT)
29
+ # tray = self.get_text(row, RegisterFlag.DRAW_LOCATION_TRAY)
30
+ # x = self.get_text(row, RegisterFlag.DRAW_LOCATION_ROW)
31
+ # y = self.get_text(row, RegisterFlag.DRAW_LOCATION_COLUMN)
32
+ # return FiftyFourVialPlate.from_str("P1-A1")
27
33
 
28
34
  function = self.get_text(row, RegisterFlag.FUNCTION)
29
35
  if function == "Wait":
@@ -34,17 +40,34 @@ class InjectorController(DeviceController):
34
40
  # TODO: better error handling
35
41
  is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
36
42
  is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
37
- vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE) if is_volume == Mode.SET else None
43
+ vol = (
44
+ self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
45
+ if is_volume == Mode.SET
46
+ else None
47
+ )
38
48
  if is_source is SourceType.SPECIFIC_LOCATION:
39
49
  return Draw(amount=vol, source=return_tray_loc())
40
50
  elif is_source is SourceType.LOCATION:
41
- return Draw(amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION))
51
+ return Draw(
52
+ amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
53
+ )
42
54
  elif function == "Remote":
43
- return Remote(command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
44
- duration=self.get_num(row, RegisterFlag.REMOTE_DUR))
55
+ return Remote(
56
+ command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
57
+ duration=int(self.get_num(row, RegisterFlag.REMOTE_DUR)),
58
+ )
59
+ raise ValueError("No valid function found.")
45
60
 
46
61
  def load(self) -> InjectorTable:
47
62
  rows = self.get_num_rows()
48
63
  if rows.is_ok():
49
- return InjectorTable(functions=[self.get_row(i) for i in range(int(rows.ok_value.num_response))])
50
-
64
+ row_response = rows.value
65
+ if isinstance(row_response, Response):
66
+ return InjectorTable(
67
+ functions=[
68
+ self.get_row(i) for i in range(int(row_response.num_response))
69
+ ]
70
+ )
71
+ elif rows.is_err():
72
+ return InjectorTable(functions=[])
73
+ raise ValueError("Unexpected error")
@@ -0,0 +1,4 @@
1
+ from .method import MethodController
2
+ from .sequence import SequenceController
3
+
4
+ __all__ = ["MethodController", "SequenceController"]