pychemstation 0.8.4__py3-none-any.whl → 0.8.7__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 (33) hide show
  1. pychemstation/__init__.py +1 -1
  2. pychemstation/analysis/__init__.py +4 -1
  3. pychemstation/analysis/base_spectrum.py +4 -4
  4. pychemstation/{utils → analysis}/chromatogram.py +4 -7
  5. pychemstation/analysis/process_report.py +173 -77
  6. pychemstation/control/README.md +22 -46
  7. pychemstation/control/__init__.py +5 -0
  8. pychemstation/control/controllers/__init__.py +2 -0
  9. pychemstation/control/controllers/comm.py +41 -18
  10. pychemstation/control/controllers/devices/device.py +27 -14
  11. pychemstation/control/controllers/devices/injector.py +33 -89
  12. pychemstation/control/controllers/tables/method.py +266 -111
  13. pychemstation/control/controllers/tables/ms.py +7 -4
  14. pychemstation/control/controllers/tables/sequence.py +171 -82
  15. pychemstation/control/controllers/tables/table.py +192 -116
  16. pychemstation/control/hplc.py +117 -83
  17. pychemstation/generated/__init__.py +0 -2
  18. pychemstation/generated/dad_method.py +1 -1
  19. pychemstation/generated/pump_method.py +15 -19
  20. pychemstation/utils/injector_types.py +1 -1
  21. pychemstation/utils/macro.py +12 -11
  22. pychemstation/utils/method_types.py +3 -2
  23. pychemstation/{analysis/utils.py → utils/num_utils.py} +2 -2
  24. pychemstation/utils/parsing.py +1 -11
  25. pychemstation/utils/sequence_types.py +4 -5
  26. pychemstation/{analysis → utils}/spec_utils.py +1 -2
  27. pychemstation/utils/table_types.py +10 -9
  28. pychemstation/utils/tray_types.py +48 -38
  29. {pychemstation-0.8.4.dist-info → pychemstation-0.8.7.dist-info}/METADATA +64 -23
  30. pychemstation-0.8.7.dist-info/RECORD +37 -0
  31. pychemstation-0.8.4.dist-info/RECORD +0 -37
  32. {pychemstation-0.8.4.dist-info → pychemstation-0.8.7.dist-info}/WHEEL +0 -0
  33. {pychemstation-0.8.4.dist-info → pychemstation-0.8.7.dist-info}/licenses/LICENSE +0 -0
@@ -9,13 +9,21 @@ 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
- from typing import Optional
15
+ from typing import Optional, Union
15
16
 
16
- from result import Result, Ok, Err
17
+ from result import Err, Ok, Result
17
18
 
18
- from ...utils.macro import *
19
+ from ...utils.macro import (
20
+ str_to_status,
21
+ HPLCAvailStatus,
22
+ HPLCErrorStatus,
23
+ Command,
24
+ Status,
25
+ Response,
26
+ )
19
27
 
20
28
 
21
29
  class CommunicationController:
@@ -27,11 +35,11 @@ class CommunicationController:
27
35
  MAX_CMD_NO = 255
28
36
 
29
37
  def __init__(
30
- self,
31
- comm_dir: str,
32
- cmd_file: str = "cmd",
33
- reply_file: str = "reply",
34
- debug: bool = False
38
+ self,
39
+ comm_dir: str,
40
+ cmd_file: str = "cmd",
41
+ reply_file: str = "reply",
42
+ debug: bool = False,
35
43
  ):
36
44
  """
37
45
  :param comm_dir:
@@ -55,7 +63,7 @@ class CommunicationController:
55
63
  self.reset_cmd_counter()
56
64
 
57
65
  # Initialize row counter for table operations
58
- self.send('Local Rows')
66
+ self.send("Local Rows")
59
67
 
60
68
  def get_num_val(self, cmd: str) -> Union[int, float]:
61
69
  tries = 5
@@ -138,6 +146,7 @@ class CommunicationController:
138
146
  :return: Potential ChemStation response
139
147
  """
140
148
  err: Optional[Union[OSError, IndexError]] = None
149
+ err_msg = ""
141
150
  for _ in range(num_attempts):
142
151
  time.sleep(1)
143
152
 
@@ -150,7 +159,11 @@ class CommunicationController:
150
159
 
151
160
  try:
152
161
  first_line = response.splitlines()[0]
153
- response_no = int(first_line.split()[0])
162
+ try:
163
+ response_no = int(first_line.split()[0])
164
+ except ValueError as e:
165
+ err = e
166
+ err_msg = f"Caused by {first_line}"
154
167
  except IndexError as e:
155
168
  err = e
156
169
  continue
@@ -161,7 +174,9 @@ class CommunicationController:
161
174
  else:
162
175
  continue
163
176
  else:
164
- return Err(f"Failed to receive reply to command #{cmd_no} due to {err}.")
177
+ return Err(
178
+ f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
179
+ )
165
180
 
166
181
  def sleepy_send(self, cmd: Union[Command, str]):
167
182
  self.send("Sleep 0.1")
@@ -187,20 +202,28 @@ class CommunicationController:
187
202
  def receive(self) -> Result[Response, str]:
188
203
  """Returns messages received in reply file.
189
204
 
190
- :return: ChemStation response
205
+ :return: ChemStation response
191
206
  """
192
207
  num_response_prefix = "Numerical Responses:"
193
208
  str_response_prefix = "String Responses:"
194
209
  possible_response = self._receive(self.cmd_no)
195
210
  if possible_response.is_ok():
196
- lines = possible_response.value.splitlines()
211
+ lines = possible_response.ok_value.splitlines()
197
212
  for line in lines:
198
213
  if str_response_prefix in line and num_response_prefix in line:
199
- string_responses_dirty, _, numerical_responses = line.partition(num_response_prefix)
200
- _, _, string_responses = string_responses_dirty.partition(str_response_prefix)
201
- return Ok(Response(string_response=string_responses.strip(),
202
- num_response=float(numerical_responses.strip())))
203
- return Err(f"Could not retrieve HPLC response")
214
+ string_responses_dirty, _, numerical_responses = line.partition(
215
+ num_response_prefix
216
+ )
217
+ _, _, string_responses = string_responses_dirty.partition(
218
+ str_response_prefix
219
+ )
220
+ return Ok(
221
+ Response(
222
+ string_response=string_responses.strip(),
223
+ num_response=float(numerical_responses.strip()),
224
+ )
225
+ )
226
+ return Err("Could not retrieve HPLC response")
204
227
  else:
205
228
  return Err(f"Could not establish response to HPLC: {possible_response}")
206
229
 
@@ -1,23 +1,25 @@
1
1
  import abc
2
- from typing import Union, List
2
+ from typing import List, Union, Dict, Optional
3
3
 
4
4
  from result import Result
5
5
 
6
- from ....analysis.process_report import ReportType, AgilentReport
6
+ from ....analysis.process_report import AgilentReport, ReportType
7
7
  from ....control.controllers import CommunicationController
8
8
  from ....control.controllers.tables.table import TableController
9
- from ....utils.chromatogram import AgilentChannelChromatogramData
10
- from ....utils.table_types import Table, T
9
+ from pychemstation.analysis.chromatogram import (
10
+ AgilentChannelChromatogramData,
11
+ AgilentHPLCChromatogram,
12
+ )
13
+ from ....utils.table_types import T, Table
11
14
 
12
15
 
13
16
  class DeviceController(TableController, abc.ABC):
14
-
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)
17
+ def __init__(
18
+ self, controller: CommunicationController, table: Table, offline: bool
19
+ ):
20
+ super().__init__(
21
+ controller=controller, src=None, data_dirs=[], table=table, offline=offline
22
+ )
21
23
 
22
24
  @abc.abstractmethod
23
25
  def get_row(self, row: int):
@@ -26,11 +28,22 @@ class DeviceController(TableController, abc.ABC):
26
28
  def retrieve_recent_data_files(self):
27
29
  raise NotImplementedError
28
30
 
29
- def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
31
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
30
32
  raise NotImplementedError
31
33
 
32
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
34
+ def get_report(
35
+ self, report_type: ReportType = ReportType.TXT
36
+ ) -> List[AgilentReport]:
37
+ raise NotImplementedError
38
+
39
+ def get_data_uv(
40
+ self,
41
+ ) -> Union[
42
+ List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]
43
+ ]:
33
44
  raise NotImplementedError
34
45
 
35
- def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
46
+ def get_data(
47
+ self, custom_path: Optional[str] = None
48
+ ) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
36
49
  raise NotImplementedError
@@ -1,13 +1,24 @@
1
1
  from ....control.controllers import CommunicationController
2
+ from ....utils.injector_types import (
3
+ Draw,
4
+ Inject,
5
+ InjectorFunction,
6
+ InjectorTable,
7
+ Mode,
8
+ Remote,
9
+ RemoteCommand,
10
+ SourceType,
11
+ Wait,
12
+ )
13
+ from ....utils.table_types import RegisterFlag, Table
14
+ from ....utils.tray_types import Tray
2
15
  from .device import DeviceController
3
- from ....utils.injector_types import *
4
- from ....utils.macro import Command
5
- from ....utils.table_types import Table, RegisterFlag
6
16
 
7
17
 
8
18
  class InjectorController(DeviceController):
9
-
10
- def __init__(self, controller: CommunicationController, table: Table, offline: bool):
19
+ def __init__(
20
+ self, controller: CommunicationController, table: Table, offline: bool
21
+ ):
11
22
  super().__init__(controller, table, offline)
12
23
 
13
24
  def get_row(self, row: int) -> InjectorFunction:
@@ -23,95 +34,28 @@ class InjectorController(DeviceController):
23
34
  # TODO: better error handling
24
35
  is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
25
36
  is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
26
- vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE) if is_volume == Mode.SET else None
37
+ vol = (
38
+ self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
39
+ if is_volume == Mode.SET
40
+ else None
41
+ )
27
42
  if is_source is SourceType.SPECIFIC_LOCATION:
28
43
  return Draw(amount=vol, source=return_tray_loc())
29
44
  elif is_source is SourceType.LOCATION:
30
- return Draw(amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION))
45
+ return Draw(
46
+ amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
47
+ )
31
48
  elif function == "Remote":
32
- return Remote(command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
33
- duration=self.get_num(row, RegisterFlag.REMOTE_DUR))
49
+ return Remote(
50
+ command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
51
+ duration=self.get_num(row, RegisterFlag.REMOTE_DUR),
52
+ )
34
53
 
35
54
  def load(self) -> InjectorTable:
36
55
  rows = self.get_num_rows()
37
56
  if rows.is_ok():
38
- return InjectorTable(functions=[self.get_row(i) for i in range(int(rows.ok_value.num_response))])
39
-
40
- def edit(self, injector_table: InjectorTable):
41
- columns_added = set()
42
-
43
- def add_table_val(col_name: RegisterFlag, val: Union[str, int, float]):
44
- nonlocal columns_added
45
- if True:
46
- if isinstance(val, str):
47
- self._edit_row_text(col_name=col_name, val=val)
48
- else:
49
- self._edit_row_num(col_name=col_name, val=val)
50
- else:
51
- if isinstance(val, str):
52
- self.add_new_col_text(col_name=col_name, val=val)
53
- else:
54
- self.add_new_col_num(col_name=col_name, val=val)
55
- columns_added.add(col_name)
56
-
57
- def add_inject(inject: Inject):
58
- add_table_val(col_name=RegisterFlag.FUNCTION, val=inject.__class__.__name__)
59
-
60
- def add_draw(draw: Draw):
61
- add_table_val(col_name=RegisterFlag.FUNCTION, val=draw.__class__.__name__)
62
- add_table_val(col_name=RegisterFlag.DRAW_SPEED, val=SourceType.DEFAULT.value)
63
- add_table_val(col_name=RegisterFlag.DRAW_OFFSET, val=SourceType.DEFAULT.value)
64
-
65
- if draw.amount:
66
- add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.SET.value)
67
- add_table_val(col_name=RegisterFlag.DRAW_VOLUME_VALUE, val=draw.amount)
68
- else:
69
- add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.DEFAULT.value)
70
-
71
- if draw.location:
72
- add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.LOCATION.value)
73
- add_table_val(col_name=RegisterFlag.DRAW_LOCATION, val=draw.location)
74
- elif draw.source:
75
- add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.SPECIFIC_LOCATION.value)
76
- add_table_val(col_name=RegisterFlag.DRAW_LOCATION_UNIT, val=1)
77
- add_table_val(col_name=RegisterFlag.DRAW_LOCATION_TRAY, val=1)
78
- add_table_val(col_name=RegisterFlag.DRAW_LOCATION_ROW, val=1)
79
- add_table_val(col_name=RegisterFlag.DRAW_LOCATION_COLUMN, val=1)
80
- else:
81
- add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.DEFAULT.value)
82
-
83
- def add_wait(wait: Wait):
84
- add_table_val(col_name=RegisterFlag.FUNCTION, val=wait.__class__.__name__)
85
- add_table_val(col_name=RegisterFlag.TIME, val=wait.duration)
86
-
87
- def add_remote(remote: Remote):
88
- add_table_val(col_name=RegisterFlag.FUNCTION, val=remote.__class__.__name__)
89
- add_table_val(col_name=RegisterFlag.REMOTE, val=remote.command.value)
90
- add_table_val(col_name=RegisterFlag.REMOTE_DUR, val=remote.duration)
91
-
92
- self.send(Command.SAVE_METHOD_CMD)
93
- rows = self.get_num_rows()
94
- if rows.is_ok():
95
- existing_row_num = rows.value.num_response
96
- for i, function in enumerate(injector_table.functions):
97
- if (i+1) > existing_row_num:
98
- self.add_row()
99
- if isinstance(function, Inject):
100
- add_inject(function)
101
- elif isinstance(function, Draw):
102
- add_draw(function)
103
- elif isinstance(function, Wait):
104
- add_wait(function)
105
- elif isinstance(function, Remote):
106
- add_remote(function)
107
- self.download()
108
- self.send(Command.SAVE_METHOD_CMD)
109
- self.send(Command.SWITCH_METHOD_CMD)
110
- existing_row_num = self.get_num_rows().ok_value.num_response
111
-
112
- def download(self):
113
- self.send('Sleep 1')
114
- self.sleepy_send("DownloadRCMethod WLS1")
115
- self.send('Sleep 1')
116
- self.sleepy_send("DownloadLWls 1")
117
- self.send('Sleep 1')
57
+ return InjectorTable(
58
+ functions=[
59
+ self.get_row(i) for i in range(int(rows.ok_value.num_response))
60
+ ]
61
+ )