pychemstation 0.10.7__py3-none-any.whl → 0.10.8__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 +10 -16
  4. pychemstation/control/README.md +1 -1
  5. pychemstation/control/controllers/__init__.py +2 -1
  6. pychemstation/control/controllers/comm.py +33 -27
  7. pychemstation/control/controllers/data_aq/method.py +233 -79
  8. pychemstation/control/controllers/data_aq/sequence.py +104 -84
  9. pychemstation/control/controllers/devices/injector.py +3 -3
  10. pychemstation/control/hplc.py +53 -41
  11. pychemstation/utils/__init__.py +23 -0
  12. pychemstation/{control/controllers → utils}/abc_tables/abc_comm.py +15 -13
  13. pychemstation/{control/controllers → utils}/abc_tables/device.py +9 -2
  14. pychemstation/{control/controllers → utils}/abc_tables/run.py +45 -37
  15. pychemstation/{control/controllers → utils}/abc_tables/table.py +32 -29
  16. pychemstation/utils/macro.py +7 -2
  17. pychemstation/utils/method_types.py +13 -14
  18. pychemstation/utils/mocking/mock_comm.py +25 -2
  19. pychemstation/utils/mocking/mock_hplc.py +29 -1
  20. pychemstation/utils/num_utils.py +3 -3
  21. pychemstation/utils/sequence_types.py +30 -14
  22. pychemstation/utils/spec_utils.py +42 -66
  23. pychemstation/utils/table_types.py +15 -2
  24. pychemstation/utils/tray_types.py +28 -16
  25. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/METADATA +1 -7
  26. pychemstation-0.10.8.dist-info/RECORD +41 -0
  27. pychemstation/utils/pump_types.py +0 -7
  28. pychemstation-0.10.7.dist-info/RECORD +0 -42
  29. /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
  30. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/licenses/LICENSE +0 -0
@@ -18,12 +18,17 @@ from typing import Union
18
18
 
19
19
  from result import Err, Ok, Result
20
20
 
21
- from ....utils.macro import Status, HPLCAvailStatus, Command, Response
21
+ from ..macro import HPLCAvailStatus, Command, Response, Status
22
22
 
23
23
 
24
24
  class ABCCommunicationController(abc.ABC):
25
- """
26
- 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
27
32
  """
28
33
 
29
34
  # maximum command number
@@ -37,12 +42,6 @@ class ABCCommunicationController(abc.ABC):
37
42
  offline: bool = False,
38
43
  debug: bool = False,
39
44
  ):
40
- """
41
- :param comm_dir:
42
- :param cmd_file: Name of command file
43
- :param reply_file: Name of reply file
44
- :param debug: whether to save log of sent commands
45
- """
46
45
  if not offline:
47
46
  self.debug = debug
48
47
  if os.path.isdir(comm_dir):
@@ -58,7 +57,6 @@ class ABCCommunicationController(abc.ABC):
58
57
 
59
58
  self.reset_cmd_counter()
60
59
  self._most_recent_hplc_status: Status = self.get_status()
61
- self.send("Local Rows")
62
60
 
63
61
  @abstractmethod
64
62
  def get_num_val(self, cmd: str) -> Union[int, float]:
@@ -90,15 +88,19 @@ class ABCCommunicationController(abc.ABC):
90
88
  :return: whether the HPLC machine is in a safe state to retrieve data back."""
91
89
  self.set_status()
92
90
  hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
93
- time.sleep(5)
91
+ time.sleep(10)
94
92
  self.set_status()
95
93
  hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
96
- return hplc_avail and hplc_actually_avail
94
+ time.sleep(10)
95
+ self.set_status()
96
+ hplc_final_check_avail = isinstance(
97
+ self._most_recent_hplc_status, HPLCAvailStatus
98
+ )
99
+ return hplc_avail and hplc_actually_avail and hplc_final_check_avail
97
100
 
98
101
  def sleepy_send(self, cmd: Union[Command, str]):
99
102
  self.send("Sleep 0.1")
100
103
  self.send(cmd)
101
- self.send("Sleep 0.1")
102
104
 
103
105
  def send(self, cmd: Union[Command, str]):
104
106
  """Sends a command to Chemstation.
@@ -2,12 +2,19 @@ from __future__ import annotations
2
2
 
3
3
  from abc import ABC
4
4
 
5
- from ....control.controllers import CommunicationController
6
- from ....utils.table_types import Table
7
5
  from .table import ABCTableController
6
+ from ..table_types import Table
7
+ from ...control.controllers import CommunicationController
8
8
 
9
9
 
10
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
+
11
18
  def __init__(
12
19
  self, controller: CommunicationController, table: Table, offline: bool
13
20
  ):
@@ -17,33 +17,42 @@ import polling
17
17
  import rainbow as rb
18
18
  from result import Err, Ok, Result
19
19
 
20
- from pychemstation.analysis.chromatogram import (
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 (
21
25
  AgilentChannelChromatogramData,
22
26
  AgilentHPLCChromatogram,
23
27
  )
24
28
 
25
- from ....analysis.process_report import (
29
+ from .table import ABCTableController
30
+ from ...analysis.process_report import (
31
+ ReportType,
26
32
  AgilentReport,
27
33
  CSVProcessor,
28
- ReportType,
29
34
  TXTProcessor,
30
35
  )
31
- from ....control.controllers.comm import CommunicationController
32
- from ....utils.macro import Command, HPLCRunningStatus
33
- from ....utils.method_types import MethodDetails
34
- from ....utils.sequence_types import SequenceTable
35
- from ....utils.table_types import T, Table
36
- from .table import ABCTableController
36
+ from ...control.controllers import CommunicationController
37
37
 
38
38
  TableType = Union[MethodDetails, SequenceTable]
39
39
 
40
40
 
41
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
+
42
51
  def __init__(
43
52
  self,
44
53
  controller: Optional[CommunicationController],
45
- src: str,
46
- data_dirs: List[str],
54
+ src: Optional[str],
55
+ data_dirs: Optional[List[str]],
47
56
  table: Table,
48
57
  offline: bool = False,
49
58
  ):
@@ -56,14 +65,15 @@ class RunController(ABCTableController, abc.ABC):
56
65
  if not offline:
57
66
  if src and not os.path.isdir(src):
58
67
  raise FileNotFoundError(f"dir: {src} not found.")
59
-
60
- for d in data_dirs:
61
- if not os.path.isdir(d):
62
- raise FileNotFoundError(f"dir: {d} not found.")
63
- if r"\\" in d:
64
- raise ValueError("Data directories should not be raw strings!")
65
- self.src: str = src
66
- 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
67
77
 
68
78
  self.spectra: dict[str, AgilentHPLCChromatogram] = {
69
79
  "A": AgilentHPLCChromatogram(),
@@ -84,9 +94,7 @@ class RunController(ABCTableController, abc.ABC):
84
94
  return object.__new__(cls)
85
95
 
86
96
  @abc.abstractmethod
87
- def fuzzy_match_most_recent_folder(
88
- self, most_recent_folder: T, child_files: Set[str]
89
- ) -> Result[T, str]:
97
+ def _fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
90
98
  pass
91
99
 
92
100
  @abc.abstractmethod
@@ -148,8 +156,7 @@ class RunController(ABCTableController, abc.ABC):
148
156
  raise ValueError("Controller is offline!")
149
157
 
150
158
  def check_hplc_done_running(self) -> Ok[T] | Err[str]:
151
- """
152
- Checks if ChemStation has finished running and can read data back
159
+ """Checks if ChemStation has finished running and can read data back
153
160
 
154
161
  :return: Data file object containing most recent run file information.
155
162
  """
@@ -183,9 +190,7 @@ class RunController(ABCTableController, abc.ABC):
183
190
  else:
184
191
  raise ValueError("Timeout value is None, no comparison can be made.")
185
192
 
186
- check_folder = self.fuzzy_match_most_recent_folder(
187
- self.data_files[-1], self.current_run_child_files
188
- )
193
+ check_folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
189
194
  if check_folder.is_ok() and finished_run:
190
195
  return check_folder
191
196
  elif check_folder.is_ok():
@@ -198,7 +203,7 @@ class RunController(ABCTableController, abc.ABC):
198
203
  except Exception:
199
204
  self._reset_time()
200
205
  return self.data_files[-1]
201
- return Err("Run did not complete as expected")
206
+ return Err("Run not may not have completed.")
202
207
 
203
208
  def get_uv_spectrum(self, path: str):
204
209
  data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
@@ -227,9 +232,7 @@ class RunController(ABCTableController, abc.ABC):
227
232
  raise ValueError("Expected one of ReportType.TXT or ReportType.CSV")
228
233
 
229
234
  def get_spectrum_at_channels(self, data_path: str):
230
- """
231
- Load chromatogram for any channel in spectra dictionary.
232
- """
235
+ """Load chromatogram for any channel in spectra dictionary."""
233
236
  for channel, spec in self.spectra.items():
234
237
  try:
235
238
  spec.load_spectrum(data_path=data_path, channel=channel)
@@ -248,9 +251,14 @@ class RunController(ABCTableController, abc.ABC):
248
251
  self.send(Command.GET_CURRENT_RUN_DATA_FILE)
249
252
  current_sample_file = self.receive()
250
253
  if full_path_name.is_ok() and current_sample_file.is_ok():
251
- return (
252
- full_path_name.ok_value.string_response,
253
- current_sample_file.ok_value.string_response,
254
- )
255
- else:
256
- raise ValueError("Couldn't read data dir and file.")
254
+ if os.path.isdir(full_path_name.ok_value.string_response) and os.path.isdir(
255
+ os.path.join(
256
+ full_path_name.ok_value.string_response,
257
+ current_sample_file.ok_value.string_response,
258
+ )
259
+ ):
260
+ return (
261
+ full_path_name.ok_value.string_response,
262
+ current_sample_file.ok_value.string_response,
263
+ )
264
+ raise ValueError("Couldn't read data dir and file or doesn't exist yet.")
@@ -11,16 +11,21 @@ from typing import Optional, Union
11
11
 
12
12
  from result import Err, Result
13
13
 
14
- from ....control.controllers.comm import CommunicationController
15
- from ....utils.macro import Command, Response
16
- from ....utils.method_types import MethodDetails
17
- from ....utils.sequence_types import SequenceTable
18
- 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
19
19
 
20
20
  TableType = Union[MethodDetails, SequenceTable]
21
21
 
22
22
 
23
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
+
24
29
  def __init__(
25
30
  self,
26
31
  controller: Optional[CommunicationController],
@@ -60,8 +65,7 @@ class ABCTableController(abc.ABC):
60
65
  raise ValueError("Controller is offline")
61
66
 
62
67
  def sleep(self, seconds: int):
63
- """
64
- Tells the HPLC to wait for a specified number of seconds.
68
+ """Tells the HPLC to wait for a specified number of seconds.
65
69
 
66
70
  :param seconds: number of seconds to wait
67
71
  """
@@ -122,8 +126,8 @@ class ABCTableController(abc.ABC):
122
126
  ):
123
127
  if not (isinstance(val, int) or isinstance(val, float)):
124
128
  raise ValueError(f"{val} must be an int or float.")
129
+ num_rows = self.get_num_rows()
125
130
  if row:
126
- num_rows = self.get_num_rows()
127
131
  if num_rows.is_ok():
128
132
  if num_rows.ok_value.num_response < row:
129
133
  raise ValueError("Not enough rows to edit!")
@@ -132,7 +136,7 @@ class ABCTableController(abc.ABC):
132
136
  TableOperation.EDIT_ROW_VAL.value.format(
133
137
  register=self.table_locator.register,
134
138
  table_name=self.table_locator.name,
135
- row=row if row is not None else "Rows",
139
+ row=row if row is not None else "response_num",
136
140
  col_name=col_name,
137
141
  val=val,
138
142
  )
@@ -143,8 +147,8 @@ class ABCTableController(abc.ABC):
143
147
  ):
144
148
  if not isinstance(val, str):
145
149
  raise ValueError(f"{val} must be a str.")
150
+ num_rows = self.get_num_rows()
146
151
  if row:
147
- num_rows = self.get_num_rows()
148
152
  if num_rows.is_ok():
149
153
  if num_rows.ok_value.num_response < row:
150
154
  raise ValueError("Not enough rows to edit!")
@@ -153,7 +157,7 @@ class ABCTableController(abc.ABC):
153
157
  TableOperation.EDIT_ROW_TEXT.value.format(
154
158
  register=self.table_locator.register,
155
159
  table_name=self.table_locator.name,
156
- row=row if row is not None else "Rows",
160
+ row=row if row is not None else "response_num",
157
161
  col_name=col_name,
158
162
  val=val,
159
163
  )
@@ -173,19 +177,19 @@ class ABCTableController(abc.ABC):
173
177
  )
174
178
 
175
179
  def add_row(self):
176
- """
177
- Adds a row to the provided table for currently loaded method or sequence.
178
- """
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
179
182
  self.sleepy_send(
180
183
  TableOperation.NEW_ROW.value.format(
181
184
  register=self.table_locator.register, table_name=self.table_locator.name
182
185
  )
183
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.")
184
190
 
185
191
  def delete_table(self):
186
- """
187
- Deletes the table for the current loaded method or sequence.
188
- """
192
+ """Deletes the table."""
189
193
  self.sleepy_send(
190
194
  TableOperation.DELETE_TABLE.value.format(
191
195
  register=self.table_locator.register, table_name=self.table_locator.name
@@ -193,9 +197,7 @@ class ABCTableController(abc.ABC):
193
197
  )
194
198
 
195
199
  def new_table(self):
196
- """
197
- Creates the table for the currently loaded method or sequence.
198
- """
200
+ """Creates the table."""
199
201
  self.send(
200
202
  TableOperation.CREATE_TABLE.value.format(
201
203
  register=self.table_locator.register, table_name=self.table_locator.name
@@ -203,13 +205,6 @@ class ABCTableController(abc.ABC):
203
205
  )
204
206
 
205
207
  def get_num_rows(self) -> Result[Response, str]:
206
- self.send(
207
- TableOperation.GET_NUM_ROWS.value.format(
208
- register=self.table_locator.register,
209
- table_name=self.table_locator.name,
210
- col_name=RegisterFlag.NUM_ROWS,
211
- )
212
- )
213
208
  self.send(
214
209
  Command.GET_ROWS_CMD.value.format(
215
210
  register=self.table_locator.register,
@@ -223,8 +218,16 @@ class ABCTableController(abc.ABC):
223
218
  raise ValueError("Controller is offline")
224
219
 
225
220
  if res.is_ok():
226
- self.send("Sleep 0.1")
227
- self.send("Print Rows")
228
221
  return res
229
222
  else:
230
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
+ )
@@ -7,6 +7,8 @@ from typing import Union
7
7
 
8
8
  @dataclass
9
9
  class Response:
10
+ """Class representing a response from Chemstation"""
11
+
10
12
  string_response: str
11
13
  num_response: Union[int, float]
12
14
 
@@ -53,12 +55,15 @@ class Command(Enum):
53
55
  # Get directories
54
56
  GET_METHOD_DIR = "response$ = _METHPATH$"
55
57
  GET_SEQUENCE_DIR = "response$ = _SEQUENCEPATHS$"
58
+ CONFIG_MET_PATH = "_CONFIGMETPATH"
59
+ CONFIG_SEQ_PATH = "_CONFIGSEQPATH"
60
+ GET_RUNNING_SEQUENCE_DIR = "response$ = _SEQPATHS$"
56
61
  GET_DATA_DIRS = "response$ = _DATAPATHS$"
57
62
  GET_CURRENT_RUN_DATA_DIR = "response$ = _DATAPath$"
58
63
  GET_CURRENT_RUN_DATA_FILE = "response$ = _DATAFILE1$"
59
64
 
60
- # Debuggng
61
- ERROR = "response$ = _ERROR$"
65
+ # Errors
66
+ ERROR = "response$ = _ERRMSG$"
62
67
 
63
68
 
64
69
  class HPLCRunningStatus(Enum):
@@ -4,8 +4,6 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Any, Optional, Union
6
6
 
7
- from ..generated import Signal
8
- from .injector_types import InjectorTable
9
7
  from .table_types import RegisterFlag
10
8
 
11
9
 
@@ -23,6 +21,8 @@ class Param:
23
21
 
24
22
  @dataclass
25
23
  class HPLCMethodParams:
24
+ """The starting conditions of a run that are displayed in the Chemstation GUI for the pump."""
25
+
26
26
  organic_modifier: float
27
27
  flow: float
28
28
  pressure: Optional[float] = None # TODO: find this
@@ -30,28 +30,27 @@ class HPLCMethodParams:
30
30
 
31
31
  @dataclass
32
32
  class TimeTableEntry:
33
+ """Row in a method timetable."""
34
+
33
35
  start_time: float
34
- organic_modifer: Optional[float]
36
+ organic_modifer: Optional[float] = None
35
37
  flow: Optional[float] = None
36
38
 
37
39
 
38
40
  @dataclass
39
41
  class MethodDetails:
40
- """An Agilent Chemstation method, TODO is to add MS parameters, injector parameters
41
-
42
- :attribute name: the name of the method, should be the same as the Chemstation method name.
43
- :attribute timetable: list of entries in the method timetable
44
- :attribute stop_time: the time the method stops running after the last timetable entry.
45
- :attribute post_time: the amount of time after the stoptime that the pumps keep running,
46
- based on settings in the first row of the timetable.
47
- :attribute params: the organic modifier (pump B) and flow rate displayed for the method (the time 0 settings)
48
- :attribute dad_wavelengthes:
42
+ """An Agilent Chemstation method
43
+
44
+ :param name: the name of the method, should be the same as the Chemstation method name.
45
+ :param timetable: list of entries in the method timetable
46
+ :param stop_time: the time the method stops running after the last timetable entry. If `None`, won't be set.
47
+ :param post_time: the amount of time after the stoptime that the pumps keep running,
48
+ based on settings in the first row of the timetable. If `None`, won't be set.
49
+ :param params: the organic modifier (pump B) and flow rate displayed for the method (the time 0 settings)
49
50
  """
50
51
 
51
52
  name: str
52
53
  params: HPLCMethodParams
53
54
  timetable: list[TimeTableEntry]
54
- injector_program: Optional[InjectorTable] = None
55
55
  stop_time: Optional[float] = None
56
56
  post_time: Optional[float] = None
57
- dad_wavelengthes: Optional[list[Signal]] = None
@@ -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
@@ -18,7 +18,7 @@ def find_nearest_value_index(array, value) -> Tuple[float, int]:
18
18
  return array[index_], index_
19
19
 
20
20
 
21
- def interpolate_to_index(array, ids, precision: int = 100) -> np.array:
21
+ def interpolate_to_index(array, ids, precision: int = 100) -> np.ndarray:
22
22
  """Find value in between arrays elements.
23
23
 
24
24
  Constructs linspace of size "precision" between index+1 and index to
@@ -37,8 +37,8 @@ def interpolate_to_index(array, ids, precision: int = 100) -> np.array:
37
37
  :return: New array with interpolated values according to provided indexes "ids".
38
38
 
39
39
  Example:
40
- >>> interpolate_to_index(np.array([1.5]), np.array([1,2,3], 100))
41
- array([2.50505051])
40
+ >>> interpolate_to_index(np.array([1.5]), np.array([1, 2, 3], 100))
41
+ ... array([2.50505051])
42
42
  """
43
43
 
44
44
  # breaking ids into fractional and integral parts
@@ -9,9 +9,15 @@ from pychemstation.utils.tray_types import Tray
9
9
 
10
10
  @dataclass
11
11
  class SequenceDataFiles:
12
+ """Class to represent files generated during a sequence.
13
+
14
+ :param sequence_name: the name of the sequence that is running
15
+ :param dir: the complete path of the directory generated for the sequence
16
+ :param child_dirs: the complete path of the files for sequence runs, contains the Chemstation data, `dir` and the sample run file.
17
+ """
18
+
12
19
  sequence_name: str
13
20
  dir: str
14
- _data_files: List[str] = field(default_factory=list)
15
21
  child_dirs: List[str] = field(default_factory=list)
16
22
 
17
23
 
@@ -39,6 +45,8 @@ class InjectionSource(Enum):
39
45
 
40
46
  @dataclass
41
47
  class SequenceEntry:
48
+ """Class to represent each row of a sequence file, maps one to one to Chemstation."""
49
+
42
50
  data_file: str
43
51
  vial_location: Tray
44
52
  sample_name: Optional[str] = None
@@ -51,23 +59,31 @@ class SequenceEntry:
51
59
 
52
60
  @dataclass
53
61
  class SequenceTable:
62
+ """Class to represent a sequence file.
63
+
64
+ :param name: name of the sequence
65
+ :param rows: the entries
66
+ """
67
+
54
68
  name: str
55
69
  rows: list[SequenceEntry]
56
70
 
57
- def __eq__(self, other):
58
- equal = False
71
+ def __eq__(self, other) -> bool:
59
72
  if not isinstance(other, SequenceTable):
60
73
  return False
61
74
 
75
+ equal = True
62
76
  for self_row, other_row in zip(self.rows, other.rows):
63
- equal |= self_row.vial_location == other_row.vial_location
64
- equal |= self_row.data_file == other_row.data_file
65
- equal |= (
66
- os.path.split(os.path.normpath(self_row.method))[-1]
67
- == os.path.split(os.path.normpath(other_row.method))[-1]
68
- )
69
- equal |= self_row.num_inj == other_row.num_inj
70
- equal |= self_row.inj_vol == other_row.inj_vol
71
- equal |= self_row.inj_source == other_row.inj_source
72
- equal |= self_row.sample_name == other_row.sample_name
73
- equal |= self_row.sample_type == other_row.sample_type
77
+ equal &= self_row.vial_location == other_row.vial_location
78
+ equal &= self_row.data_file == other_row.data_file
79
+ if self_row.method and other_row.method:
80
+ equal &= (
81
+ os.path.split(os.path.normpath(self_row.method))[-1]
82
+ == os.path.split(os.path.normpath(other_row.method))[-1]
83
+ )
84
+ equal &= self_row.num_inj == other_row.num_inj
85
+ equal &= self_row.inj_vol == other_row.inj_vol
86
+ equal &= self_row.inj_source == other_row.inj_source
87
+ equal &= self_row.sample_name == other_row.sample_name
88
+ equal &= self_row.sample_type == other_row.sample_type
89
+ return equal