pychemstation 0.5.12__py3-none-any.whl → 0.5.13.dev2__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.
@@ -1,4 +1,9 @@
1
- from .method import MethodController
2
- from .sequence import SequenceController
3
- from .table_controller import TableController
1
+ """
2
+ .. include:: README.md
3
+ """
4
+
4
5
  from .comm import CommunicationController
6
+ from .devices.pump import PumpController
7
+ from .devices.column import ColumnController
8
+ from .tables.method import MethodController
9
+ from .tables.sequence import SequenceController
File without changes
@@ -0,0 +1,12 @@
1
+ from ....control.controllers import CommunicationController
2
+ from .device import DeviceController
3
+ from ....utils.table_types import Table
4
+
5
+
6
+ class ColumnController(DeviceController):
7
+
8
+ def __init__(self, controller: CommunicationController, table: Table):
9
+ super().__init__(controller, table)
10
+
11
+ def get_row(self, row: int):
12
+ pass
@@ -0,0 +1,23 @@
1
+ import abc
2
+ from typing import Union
3
+
4
+ from ....control.controllers import CommunicationController
5
+ from ....control.controllers.tables.table import TableController
6
+ from ....utils.chromatogram import AgilentChannelChromatogramData
7
+ from ....utils.table_types import Table
8
+
9
+
10
+ class DeviceController(TableController, abc.ABC):
11
+
12
+ def __init__(self, controller: CommunicationController, table: Table):
13
+ super().__init__(controller, None, None, table)
14
+
15
+ @abc.abstractmethod
16
+ def get_row(self, row: int):
17
+ pass
18
+
19
+ def retrieve_recent_data_files(self):
20
+ raise NotImplementedError
21
+
22
+ def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
23
+ raise NotImplementedError
@@ -0,0 +1,43 @@
1
+ from ....control.controllers import CommunicationController
2
+ from .device import DeviceController
3
+ from ....utils.pump_types import Pump
4
+ from ....utils.table_types import Table
5
+
6
+
7
+ class PumpController(DeviceController):
8
+
9
+ def __init__(self, controller: CommunicationController, table: Table):
10
+ super().__init__(controller, table)
11
+ self.A1 = Pump(in_use=True, solvent="A1")
12
+ self.B1 = Pump(in_use=True, solvent="B1")
13
+ self.A2 = Pump(in_use=False, solvent="A2")
14
+ self.B2 = Pump(in_use=False, solvent="B2")
15
+
16
+ def validate_pumps(self):
17
+ invalid_A_pump_usage = self.A1.in_use and self.A2.in_use
18
+ invalid_B_pump_usage = self.B1.in_use and self.B2.in_use
19
+ if invalid_A_pump_usage or invalid_B_pump_usage:
20
+ raise AttributeError
21
+
22
+ def switch_pump(self, num: int, pump: str):
23
+ if pump == "A":
24
+ if num == 1:
25
+ self.A1.in_use = True
26
+ self.A2.in_use = False
27
+ elif num == 2:
28
+ self.A1.in_use = False
29
+ self.A2.in_use = True
30
+ elif pump == "B":
31
+ if num == 1:
32
+ self.B1.in_use = True
33
+ self.B2.in_use = False
34
+ elif num == 2:
35
+ self.B1.in_use = False
36
+ self.B2.in_use = True
37
+ self.purge()
38
+
39
+ def purge(self):
40
+ pass
41
+
42
+ def get_row(self, row: int):
43
+ pass
File without changes
@@ -0,0 +1,346 @@
1
+ import os
2
+ import time
3
+
4
+ from xsdata.formats.dataclass.parsers import XmlParser
5
+
6
+ from .table import TableController
7
+ from ....control.controllers import CommunicationController
8
+ from ....generated import PumpMethod, DadMethod, SolventElement
9
+ from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
10
+ from ....utils.macro import *
11
+ from ....utils.method_types import *
12
+ from ....utils.table_types import *
13
+
14
+
15
+ class MethodController(TableController):
16
+ """
17
+ Class containing method related logic
18
+ """
19
+
20
+ def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
21
+ super().__init__(controller, src, data_dir, table)
22
+
23
+ def check(self) -> str:
24
+ time.sleep(2)
25
+ self.send(Command.GET_METHOD_CMD)
26
+ time.sleep(2)
27
+ res = self.receive()
28
+ if res.is_ok():
29
+ return res.ok_value.string_response
30
+ return "ERROR"
31
+
32
+ def get_method_params(self) -> HPLCMethodParams:
33
+ return HPLCMethodParams(organic_modifier=self.controller.get_num_val(
34
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
35
+ register=self.table.register,
36
+ register_flag=RegisterFlag.SOLVENT_B_COMPOSITION
37
+ )
38
+ ),
39
+ flow=self.controller.get_num_val(
40
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
41
+ register=self.table.register,
42
+ register_flag=RegisterFlag.FLOW
43
+ )
44
+ ),
45
+ )
46
+
47
+ def get_row(self, row: int) -> TimeTableEntry:
48
+ flow = None
49
+ om = None
50
+
51
+ try:
52
+ flow = self.get_num(row, RegisterFlag.TIMETABLE_FLOW)
53
+ except RuntimeError:
54
+ pass
55
+ try:
56
+ om = self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION)
57
+ except RuntimeError:
58
+ pass
59
+
60
+ return TimeTableEntry(start_time=self.get_num(row, RegisterFlag.TIME),
61
+ organic_modifer=om,
62
+ flow=flow)
63
+
64
+ def get_timetable(self, rows: int):
65
+ uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
66
+ timetable_rows = {}
67
+ for row in uncoalesced_timetable_rows:
68
+ time_key = str(row.start_time)
69
+ if time_key not in timetable_rows.keys():
70
+ timetable_rows[time_key] = TimeTableEntry(start_time=row.start_time,
71
+ flow=row.flow,
72
+ organic_modifer=row.organic_modifer)
73
+ else:
74
+ if row.flow:
75
+ timetable_rows[time_key].flow = row.flow
76
+ if row.organic_modifer:
77
+ timetable_rows[time_key].organic_modifer = row.organic_modifer
78
+ entries = list(timetable_rows.values())
79
+ entries.sort(key=lambda e: e.start_time)
80
+ return entries
81
+
82
+ def load(self) -> MethodDetails:
83
+ rows = self.get_num_rows()
84
+ if rows.is_ok():
85
+ self.send(Command.GET_METHOD_CMD)
86
+ res = self.receive()
87
+ method_name = res.ok_value.string_response
88
+ timetable_rows = self.get_timetable(int(rows.ok_value.num_response))
89
+ params = self.get_method_params()
90
+ stop_time = self.controller.get_num_val(
91
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
92
+ register=self.table.register,
93
+ register_flag=RegisterFlag.MAX_TIME))
94
+ post_time = self.controller.get_num_val(
95
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
96
+ register=self.table.register,
97
+ register_flag=RegisterFlag.POST_TIME))
98
+ self.table_state = MethodDetails(name=method_name,
99
+ timetable=timetable_rows,
100
+ stop_time=stop_time,
101
+ post_time=post_time,
102
+ params=params)
103
+ return self.table_state
104
+ else:
105
+ raise RuntimeError(rows.err_value)
106
+
107
+ def current_method(self, method_name: str):
108
+ """
109
+ Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
110
+
111
+ :param method_name: a Chemstation method
112
+ :return: True if method is already loaded
113
+ """
114
+ self.send(Command.GET_METHOD_CMD)
115
+ parsed_response = self.receive()
116
+ return method_name in parsed_response
117
+
118
+ def switch(self, method_name: str):
119
+ """
120
+ Allows the user to switch between pre-programmed methods. No need to append '.M'
121
+ to the end of the method name. For example. for the method named 'General-Poroshell.M',
122
+ only 'General-Poroshell' is needed.
123
+
124
+ :param method_name: any available method in Chemstation method directory
125
+ :raise IndexError: Response did not have expected format. Try again.
126
+ :raise AssertionError: The desired method is not selected. Try again.
127
+ """
128
+ self.send(Command.SWITCH_METHOD_CMD.value.format(method_dir=self.src,
129
+ method_name=method_name))
130
+
131
+ time.sleep(2)
132
+ self.send(Command.GET_METHOD_CMD)
133
+ time.sleep(2)
134
+ res = self.receive()
135
+ if res.is_ok():
136
+ parsed_response = res.ok_value.string_response
137
+ assert parsed_response == f"{method_name}.M", "Switching Methods failed."
138
+ self.table_state = None
139
+
140
+ def load_from_disk(self, method_name: str) -> MethodDetails:
141
+ """
142
+ Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
143
+ organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
144
+ only two solvents are being used.
145
+
146
+ :param method_name: name of method to load details of
147
+ :raises FileNotFoundError: Method does not exist
148
+ :return: method details
149
+ """
150
+ method_folder = f"{method_name}.M"
151
+ method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
152
+ dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
153
+
154
+ if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
155
+ parser = XmlParser()
156
+ method = parser.parse(method_path, PumpMethod)
157
+ dad = parser.parse(dad_path, DadMethod)
158
+
159
+ organic_modifier: Optional[SolventElement] = None
160
+ aq_modifier: Optional[SolventElement] = None
161
+
162
+ if len(method.solvent_composition.solvent_element) == 2:
163
+ for solvent in method.solvent_composition.solvent_element:
164
+ if solvent.channel == "Channel_A":
165
+ aq_modifier = solvent
166
+ elif solvent.channel == "Channel_B":
167
+ organic_modifier = solvent
168
+
169
+ self.table_state = MethodDetails(name=method_name,
170
+ params=HPLCMethodParams(organic_modifier=organic_modifier.percentage,
171
+ flow=method.flow),
172
+ stop_time=method.stop_time.stop_time_value,
173
+ post_time=method.post_time.post_time_value,
174
+ timetable=[TimeTableEntry(start_time=tte.time,
175
+ organic_modifer=tte.percent_b,
176
+ flow=method.flow
177
+ ) for tte in method.timetable.timetable_entry],
178
+ dad_wavelengthes=dad.signals.signal)
179
+ return self.table_state
180
+ else:
181
+ raise FileNotFoundError
182
+
183
+ def edit(self, updated_method: MethodDetails, save: bool):
184
+ """Updated the currently loaded method in ChemStation with provided values.
185
+
186
+ :param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
187
+ :param save: if false only modifies the method, otherwise saves to disk
188
+ """
189
+ self.table_state = updated_method
190
+ initial_organic_modifier: Param = Param(val=updated_method.params.organic_modifier,
191
+ chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
192
+ ptype=PType.NUM)
193
+ max_time: Param = Param(val=updated_method.stop_time,
194
+ chemstation_key=RegisterFlag.MAX_TIME,
195
+ ptype=PType.NUM)
196
+ post_time: Param = Param(val=updated_method.post_time,
197
+ chemstation_key=RegisterFlag.POST_TIME,
198
+ ptype=PType.NUM)
199
+ flow: Param = Param(val=updated_method.params.flow,
200
+ chemstation_key=RegisterFlag.FLOW,
201
+ ptype=PType.NUM)
202
+
203
+ # Method settings required for all runs
204
+ self.update_method_params(flow, initial_organic_modifier, max_time, post_time)
205
+ self._update_method_timetable(updated_method.timetable)
206
+
207
+ if save:
208
+ self.send(Command.SAVE_METHOD_CMD.value.format(
209
+ commit_msg=f"saved method at {str(time.time())}"
210
+ ))
211
+
212
+ def update_method_params(self, flow, initial_organic_modifier, max_time, post_time):
213
+ self.delete_table()
214
+ self._update_param(initial_organic_modifier)
215
+ self._update_param(flow)
216
+ if self.table_state.stop_time:
217
+ self._update_param(Param(val="Set", chemstation_key=RegisterFlag.STOPTIME_MODE, ptype=PType.STR))
218
+ self._update_param(max_time)
219
+ else:
220
+ self._update_param(Param(val="Off", chemstation_key=RegisterFlag.STOPTIME_MODE, ptype=PType.STR))
221
+ if self.table_state.post_time:
222
+ self._update_param(Param(val="Set", chemstation_key=RegisterFlag.POSTIME_MODE, ptype=PType.STR))
223
+ self._update_param(post_time)
224
+ else:
225
+ self._update_param(Param(val="Off", chemstation_key=RegisterFlag.POSTIME_MODE, ptype=PType.STR))
226
+ self.download()
227
+
228
+ def _update_param(self, method_param: Param):
229
+ """Change a method parameter, changes what is visibly seen in Chemstation GUI.
230
+ (changes the first row in the timetable)
231
+
232
+ :param method_param: a parameter to update for currently loaded method.
233
+ """
234
+ register = self.table.register
235
+ setting_command = TableOperation.UPDATE_OBJ_HDR_VAL if method_param.ptype == PType.NUM else TableOperation.UPDATE_OBJ_HDR_TEXT
236
+ if isinstance(method_param.chemstation_key, list):
237
+ for register_flag in method_param.chemstation_key:
238
+ self.send(setting_command.value.format(register=register,
239
+ register_flag=register_flag,
240
+ val=method_param.val))
241
+ else:
242
+ self.send(setting_command.value.format(register=register,
243
+ register_flag=method_param.chemstation_key,
244
+ val=method_param.val))
245
+ time.sleep(2)
246
+
247
+ def download(self):
248
+ self.send('Sleep 1')
249
+ self.sleepy_send("DownloadRCMethod PMP1")
250
+ self.send('Sleep 1')
251
+
252
+ def edit_row(self, row: TimeTableEntry, first_row: bool = False):
253
+ if first_row:
254
+ if row.organic_modifer:
255
+ self.add_row()
256
+ self.add_new_col_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.SOLVENT_COMPOSITION.value)
257
+ self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
258
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION, val=row.organic_modifer)
259
+ if row.flow:
260
+ self.add_row()
261
+ self.get_num_rows()
262
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
263
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
264
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
265
+ self.download()
266
+ else:
267
+ if row.organic_modifer:
268
+ self.add_row()
269
+ self.get_num_rows()
270
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.SOLVENT_COMPOSITION.value)
271
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
272
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION, val=row.organic_modifer)
273
+ self.download()
274
+ if row.flow:
275
+ self.add_row()
276
+ self.get_num_rows()
277
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
278
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
279
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
280
+ self.download()
281
+
282
+ def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
283
+ self.get_num_rows()
284
+
285
+ self.delete_table()
286
+ res = self.get_num_rows()
287
+ while not res.is_err():
288
+ self.delete_table()
289
+ res = self.get_num_rows()
290
+
291
+ self.new_table()
292
+ self.get_num_rows()
293
+
294
+ for i, row in enumerate(timetable_rows):
295
+ self.edit_row(row=row, first_row=i == 0)
296
+
297
+ def stop(self):
298
+ """
299
+ Stops the method run. A dialog window will pop up and manual intervention may be required.\
300
+ """
301
+ self.send(Command.STOP_METHOD_CMD)
302
+
303
+ def run(self, experiment_name: str, stall_while_running: bool = True):
304
+ """
305
+ This is the preferred method to trigger a run.
306
+ Starts the currently selected method, storing data
307
+ under the <data_dir>/<experiment_name>.D folder.
308
+ The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
309
+ Device must be ready.
310
+
311
+ :param experiment_name: Name of the experiment
312
+ """
313
+ if not self.table_state:
314
+ self.table_state = self.load()
315
+
316
+ folder_name = ""
317
+ hplc_is_running = self.check_hplc_is_running()
318
+ while not hplc_is_running:
319
+ timestamp = time.strftime(TIME_FORMAT)
320
+ self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
321
+ experiment_name=experiment_name,
322
+ timestamp=timestamp))
323
+ folder_name = f"{experiment_name}_{timestamp}.D"
324
+ hplc_is_running = self.check_hplc_is_running()
325
+
326
+ self.data_files.append(os.path.join(self.data_dir, folder_name))
327
+
328
+ if stall_while_running:
329
+ run_completed = self.check_hplc_done_running(method=self.table_state)
330
+ if run_completed.is_ok():
331
+ self.data_files[-1] = run_completed.ok_value
332
+ else:
333
+ raise RuntimeError("Run error has occurred.")
334
+ else:
335
+ self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
336
+
337
+ def retrieve_recent_data_files(self) -> str:
338
+ return self.data_files[-1]
339
+
340
+ def get_data(self, custom_path: Optional[str] = None,
341
+ read_uv: bool = False) -> AgilentChannelChromatogramData:
342
+ if not custom_path:
343
+ self.get_spectrum(self.data_files[-1], read_uv)
344
+ else:
345
+ self.get_spectrum(custom_path, read_uv)
346
+ return AgilentChannelChromatogramData(**self.spectra) if not read_uv else self.uv
@@ -0,0 +1,199 @@
1
+ import os
2
+ import time
3
+ from typing import Optional, Union
4
+
5
+ from .table import TableController, ChromData
6
+ from ....control.controllers.comm import CommunicationController
7
+ from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData
8
+ from ....utils.macro import Command
9
+ from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
10
+ from ....utils.table_types import RegisterFlag, Table
11
+ from ....utils.tray_types import TenVialColumn, FiftyFourVialPlate
12
+
13
+
14
+ class SequenceController(TableController):
15
+ """
16
+ Class containing sequence related logic
17
+ """
18
+
19
+ def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table, method_dir: str):
20
+ self.method_dir = method_dir
21
+ super().__init__(controller, src, data_dir, table)
22
+
23
+ def load(self) -> SequenceTable:
24
+ rows = self.get_num_rows()
25
+ self.send(Command.GET_SEQUENCE_CMD)
26
+ seq_name = self.receive()
27
+
28
+ if rows.is_ok() and seq_name.is_ok():
29
+ self.table_state = SequenceTable(
30
+ name=seq_name.ok_value.string_response.partition(".S")[0],
31
+ rows=[self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))])
32
+ return self.table_state
33
+ raise RuntimeError(rows.err_value)
34
+
35
+ def get_row(self, row: int) -> SequenceEntry:
36
+ sample_name = self.get_text(row, RegisterFlag.NAME)
37
+ vial_location = int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
38
+ method = self.get_text(row, RegisterFlag.METHOD)
39
+ num_inj = int(self.get_num(row, RegisterFlag.NUM_INJ))
40
+ inj_vol = int(self.get_text(row, RegisterFlag.INJ_VOL))
41
+ inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
42
+ sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
43
+ return SequenceEntry(sample_name=sample_name,
44
+ vial_location=vial_location,
45
+ method=None if len(method) == 0 else method,
46
+ num_inj=num_inj,
47
+ inj_vol=inj_vol,
48
+ inj_source=inj_source,
49
+ sample_type=sample_type)
50
+
51
+ def check(self) -> str:
52
+ time.sleep(2)
53
+ self.send(Command.GET_SEQUENCE_CMD)
54
+ time.sleep(2)
55
+ res = self.receive()
56
+ if res.is_ok():
57
+ return res.ok_value.string_response
58
+ return "ERROR"
59
+
60
+ def switch(self, seq_name: str):
61
+ """
62
+ Switch to the specified sequence. The sequence name does not need the '.S' extension.
63
+
64
+ :param seq_name: The name of the sequence file
65
+ """
66
+ self.send(f'_SeqFile$ = "{seq_name}.S"')
67
+ self.send(f'_SeqPath$ = "{self.src}"')
68
+ self.send(Command.SWITCH_SEQUENCE_CMD)
69
+ time.sleep(2)
70
+ self.send(Command.GET_SEQUENCE_CMD)
71
+ time.sleep(2)
72
+ parsed_response = self.receive().value.string_response
73
+
74
+ assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
75
+ self.table_state = None
76
+
77
+ def edit(self, sequence_table: SequenceTable):
78
+ """
79
+ Updates the currently loaded sequence table with the provided table. This method will delete the existing sequence table and remake it.
80
+ If you would only like to edit a single row of a sequence table, use `edit_sequence_table_row` instead.
81
+
82
+ :param sequence_table:
83
+ """
84
+ self.table_state = sequence_table
85
+ rows = self.get_num_rows()
86
+ if rows.is_ok():
87
+ existing_row_num = rows.value.num_response
88
+ wanted_row_num = len(sequence_table.rows)
89
+ while existing_row_num != wanted_row_num:
90
+ if wanted_row_num > existing_row_num:
91
+ self.add_row()
92
+ elif wanted_row_num < existing_row_num:
93
+ self.delete_row(int(existing_row_num))
94
+ self.send(Command.SAVE_SEQUENCE_CMD)
95
+ existing_row_num = self.get_num_rows().ok_value.num_response
96
+ self.send(Command.SWITCH_SEQUENCE_CMD)
97
+
98
+ for i, row in enumerate(sequence_table.rows):
99
+ self.edit_row(row=row, row_num=i + 1)
100
+ self.sleep(1)
101
+ self.send(Command.SAVE_SEQUENCE_CMD)
102
+ self.send(Command.SWITCH_SEQUENCE_CMD)
103
+
104
+ def edit_row(self, row: SequenceEntry, row_num: int):
105
+ """
106
+ Edits a row in the sequence table. If a row does NOT exist, a new one will be created.
107
+
108
+ :param row: sequence row entry with updated information
109
+ :param row_num: the row to edit, based on 1-based indexing
110
+ """
111
+ num_rows = self.get_num_rows()
112
+ if num_rows.is_ok():
113
+ while num_rows.ok_value.num_response < row_num:
114
+ self.add_row()
115
+ self.send(Command.SAVE_SEQUENCE_CMD)
116
+ num_rows = self.get_num_rows()
117
+
118
+ if row.vial_location:
119
+ loc = row.vial_location
120
+ if isinstance(loc, TenVialColumn):
121
+ loc = row.vial_location.value
122
+ elif isinstance(loc, FiftyFourVialPlate):
123
+ loc = row.vial_location.value()
124
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
125
+
126
+ if row.method:
127
+ possible_path = os.path.join(self.method_dir, row.method) + ".M\\"
128
+ method = row.method
129
+ if os.path.exists(possible_path):
130
+ method = os.path.join(self.method_dir, row.method)
131
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
132
+
133
+ if row.num_inj:
134
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
135
+
136
+ if row.inj_vol:
137
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
138
+
139
+ if row.inj_source:
140
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
141
+
142
+ if row.sample_name:
143
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
144
+ if row.data_file:
145
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
146
+ else:
147
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
148
+
149
+ if row.sample_type:
150
+ self.edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
151
+
152
+ self.send(Command.SAVE_SEQUENCE_CMD)
153
+
154
+ def run(self, stall_while_running: bool = True):
155
+ """
156
+ Starts the currently loaded sequence, storing data
157
+ under the <data_dir>/<sequence table name> folder.
158
+ Device must be ready.
159
+ """
160
+ if not self.table_state:
161
+ self.table_state = self.load()
162
+
163
+ timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
164
+ self.send(Command.RUN_SEQUENCE_CMD.value)
165
+
166
+ if self.check_hplc_is_running():
167
+ folder_name = f"{self.table_state.name} {timestamp}"
168
+ self.data_files.append(SequenceDataFiles(dir=folder_name,
169
+ sequence_name=self.table_state.name))
170
+
171
+ if stall_while_running:
172
+ run_completed = self.check_hplc_done_running(sequence=self.table_state)
173
+ if run_completed.is_ok():
174
+ self.data_files[-1].dir = run_completed.value
175
+ else:
176
+ raise RuntimeError("Run error has occurred.")
177
+ else:
178
+ self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
179
+
180
+ def retrieve_recent_data_files(self) -> str:
181
+ sequence_data_files: SequenceDataFiles = self.data_files[-1]
182
+ return sequence_data_files.dir
183
+
184
+ def get_data(self, custom_path: Optional[str] = None,
185
+ read_uv: bool = False) -> list[AgilentChannelChromatogramData]:
186
+ parent_dir = self.data_files[-1].dir if not custom_path else custom_path
187
+ subdirs = [x[0] for x in os.walk(self.data_dir)]
188
+ potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
189
+ self.data_files[-1].child_dirs = [f for f in potential_folders if
190
+ parent_dir in f and ".M" not in f and ".D" in f]
191
+
192
+ spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
193
+ all_w_spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
194
+ for row in self.data_files[-1].child_dirs:
195
+ self.get_spectrum(row, read_uv)
196
+ spectra.append(AgilentChannelChromatogramData(**self.spectra))
197
+ all_w_spectra.append(self.uv)
198
+
199
+ return spectra if not read_uv else all_w_spectra
@@ -0,0 +1,287 @@
1
+ """
2
+ Abstract module containing shared logic for Method and Sequence tables.
3
+
4
+ Authors: Lucy Hao
5
+ """
6
+
7
+ import abc
8
+ import os
9
+ from dataclasses import dataclass
10
+ from typing import Union, Optional
11
+
12
+ import numpy as np
13
+ import polling
14
+ from rainbow import DataFile
15
+ from result import Result, Ok, Err
16
+ import pandas as pd
17
+ import rainbow as rb
18
+
19
+ from ....control.controllers.comm import CommunicationController
20
+ from ....utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
21
+ from ....utils.macro import Command, HPLCRunningStatus, Response
22
+ from ....utils.method_types import MethodDetails
23
+ from ....utils.sequence_types import SequenceDataFiles, SequenceTable
24
+ from ....utils.table_types import Table, TableOperation, RegisterFlag
25
+
26
+ TableType = Union[MethodDetails, SequenceTable]
27
+
28
+
29
+ @dataclass
30
+ class ChromData:
31
+ x: np.array
32
+ y: np.array
33
+
34
+
35
+ class TableController(abc.ABC):
36
+
37
+ def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
38
+ self.controller = controller
39
+ self.table = table
40
+ self.table_state: Optional[TableType] = None
41
+
42
+ if os.path.isdir(src):
43
+ self.src: str = src
44
+ else:
45
+ raise FileNotFoundError(f"dir: {src} not found.")
46
+
47
+ if os.path.isdir(data_dir):
48
+ self.data_dir: str = data_dir
49
+ else:
50
+ raise FileNotFoundError(f"dir: {data_dir} not found.")
51
+
52
+ self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
53
+ "A": AgilentHPLCChromatogram(self.data_dir),
54
+ "B": AgilentHPLCChromatogram(self.data_dir),
55
+ "C": AgilentHPLCChromatogram(self.data_dir),
56
+ "D": AgilentHPLCChromatogram(self.data_dir),
57
+ "E": AgilentHPLCChromatogram(self.data_dir),
58
+ "F": AgilentHPLCChromatogram(self.data_dir),
59
+ "G": AgilentHPLCChromatogram(self.data_dir),
60
+ "H": AgilentHPLCChromatogram(self.data_dir),
61
+ }
62
+
63
+ self.data_files: Union[list[SequenceDataFiles], list[str]] = []
64
+
65
+ self.uv = None
66
+
67
+ # Initialize row counter for table operations
68
+ self.send('Local Rows')
69
+
70
+ def receive(self) -> Result[Response, str]:
71
+ for _ in range(10):
72
+ try:
73
+ return self.controller.receive()
74
+ except IndexError:
75
+ continue
76
+ return Err("Could not parse response")
77
+
78
+ def send(self, cmd: Union[Command, str]):
79
+ if not self.controller:
80
+ raise RuntimeError(
81
+ "Communication controller must be initialized before sending command. It is currently in offline mode.")
82
+ self.controller.send(cmd)
83
+
84
+ def sleepy_send(self, cmd: Union[Command, str]):
85
+ self.controller.sleepy_send(cmd)
86
+
87
+ def sleep(self, seconds: int):
88
+ """
89
+ Tells the HPLC to wait for a specified number of seconds.
90
+
91
+ :param seconds: number of seconds to wait
92
+ """
93
+ self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
94
+
95
+ def get_num(self, row: int, col_name: RegisterFlag) -> float:
96
+ return self.controller.get_num_val(TableOperation.GET_ROW_VAL.value.format(register=self.table.register,
97
+ table_name=self.table.name,
98
+ row=row,
99
+ col_name=col_name.value))
100
+
101
+ def get_text(self, row: int, col_name: RegisterFlag) -> str:
102
+ return self.controller.get_text_val(TableOperation.GET_ROW_TEXT.value.format(register=self.table.register,
103
+ table_name=self.table.name,
104
+ row=row,
105
+ col_name=col_name.value))
106
+
107
+ def add_new_col_num(self,
108
+ col_name: RegisterFlag,
109
+ val: Union[int, float]):
110
+ self.sleepy_send(TableOperation.NEW_COL_VAL.value.format(
111
+ register=self.table.register,
112
+ table_name=self.table.name,
113
+ col_name=col_name,
114
+ val=val))
115
+
116
+ def add_new_col_text(self,
117
+ col_name: RegisterFlag,
118
+ val: str):
119
+ self.sleepy_send(TableOperation.NEW_COL_TEXT.value.format(
120
+ register=self.table.register,
121
+ table_name=self.table.name,
122
+ col_name=col_name,
123
+ val=val))
124
+
125
+ def edit_row_num(self,
126
+ col_name: RegisterFlag,
127
+ val: Union[int, float],
128
+ row: Optional[int] = None):
129
+ self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(
130
+ register=self.table.register,
131
+ table_name=self.table.name,
132
+ row=row if row is not None else 'Rows',
133
+ col_name=col_name,
134
+ val=val))
135
+
136
+ def edit_row_text(self,
137
+ col_name: RegisterFlag,
138
+ val: str,
139
+ row: Optional[int] = None):
140
+ self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(
141
+ register=self.table.register,
142
+ table_name=self.table.name,
143
+ row=row if row is not None else 'Rows',
144
+ col_name=col_name,
145
+ val=val))
146
+
147
+ @abc.abstractmethod
148
+ def get_row(self, row: int):
149
+ pass
150
+
151
+ def delete_row(self, row: int):
152
+ self.sleepy_send(TableOperation.DELETE_ROW.value.format(register=self.table.register,
153
+ table_name=self.table.name,
154
+ row=row))
155
+
156
+ def add_row(self):
157
+ """
158
+ Adds a row to the provided table for currently loaded method or sequence.
159
+ Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
160
+ You can also provide your own table.
161
+
162
+ :param table: the table to add a new row to
163
+ """
164
+ self.sleepy_send(TableOperation.NEW_ROW.value.format(register=self.table.register,
165
+ table_name=self.table.name))
166
+
167
+ def delete_table(self):
168
+ """
169
+ Deletes the table for the current loaded method or sequence.
170
+ Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
171
+ You can also provide your own table.
172
+
173
+ :param table: the table to delete
174
+ """
175
+ self.sleepy_send(TableOperation.DELETE_TABLE.value.format(register=self.table.register,
176
+ table_name=self.table.name))
177
+
178
+ def new_table(self):
179
+ """
180
+ Creates the table for the currently loaded method or sequence. Import either the SEQUENCE_TABLE or
181
+ METHOD_TIMETABLE from hein_analytical_control.constants. You can also provide your own table.
182
+
183
+ :param table: the table to create
184
+ """
185
+ self.send(TableOperation.CREATE_TABLE.value.format(register=self.table.register,
186
+ table_name=self.table.name))
187
+
188
+ def get_num_rows(self) -> Result[Response, str]:
189
+ self.send(TableOperation.GET_NUM_ROWS.value.format(register=self.table.register,
190
+ table_name=self.table.name,
191
+ col_name=RegisterFlag.NUM_ROWS))
192
+ self.send(Command.GET_ROWS_CMD.value.format(register=self.table.register,
193
+ table_name=self.table.name,
194
+ col_name=RegisterFlag.NUM_ROWS))
195
+ res = self.controller.receive()
196
+
197
+ if res.is_ok():
198
+ self.send("Sleep 0.1")
199
+ self.send('Print Rows')
200
+ return res
201
+ else:
202
+ return Err("No rows could be read.")
203
+
204
+ def check_hplc_is_running(self) -> bool:
205
+ started_running = polling.poll(
206
+ lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
207
+ step=5,
208
+ max_tries=40)
209
+ return started_running
210
+
211
+ def check_hplc_done_running(self,
212
+ method: Optional[MethodDetails] = None,
213
+ sequence: Optional[SequenceTable] = None) -> Result[str, str]:
214
+ """
215
+ Checks if ChemStation has finished running and can read data back
216
+
217
+ :param method: if you are running a method and want to read back data, the timeout period will be adjusted to be longer than the method's runtime
218
+ :param sequence: if you are running a sequence and want to read back data, the timeout period will be adjusted to be longer than the sequence's runtime
219
+ :return: Return True if data can be read back, else False.
220
+ """
221
+ timeout = 10 * 60
222
+ if method:
223
+ timeout = ((method.stop_time + method.post_time + 3) * 60)
224
+ if sequence:
225
+ timeout *= len(sequence.rows)
226
+
227
+ most_recent_folder = self.retrieve_recent_data_files()
228
+ finished_run = polling.poll(
229
+ lambda: self.controller.check_if_running(),
230
+ timeout=timeout,
231
+ step=50)
232
+
233
+ check_folder = self.fuzzy_match_most_recent_folder(most_recent_folder)
234
+ if check_folder.is_ok() and finished_run:
235
+ return check_folder
236
+ elif check_folder.is_ok():
237
+ finished_run = polling.poll(
238
+ lambda: self.controller.check_if_running(),
239
+ timeout=timeout,
240
+ step=50)
241
+ if finished_run:
242
+ return check_folder
243
+ return check_folder
244
+ else:
245
+ return Err("Run did not complete as expected")
246
+
247
+ def fuzzy_match_most_recent_folder(self, most_recent_folder) -> Result[str, str]:
248
+ if os.path.exists(most_recent_folder):
249
+ return Ok(most_recent_folder)
250
+
251
+ subdirs = [x[0] for x in os.walk(self.data_dir)]
252
+ potential_folders = sorted(list(filter(lambda d: most_recent_folder in d, subdirs)))
253
+ parent_dirs = []
254
+ for folder in potential_folders:
255
+ path = os.path.normpath(folder)
256
+ split_folder = path.split(os.sep)
257
+ if most_recent_folder in split_folder[-1]:
258
+ parent_dirs.append(folder)
259
+ parent_dir = sorted(parent_dirs, reverse=True)[0]
260
+ return Ok(parent_dir)
261
+
262
+ @abc.abstractmethod
263
+ def retrieve_recent_data_files(self):
264
+ pass
265
+
266
+ @abc.abstractmethod
267
+ def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
268
+ pass
269
+
270
+ def get_uv_spectrum(self, path: str):
271
+ data_uv: DataFile = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
272
+ zipped_data = zip(data_uv.ylabels, data_uv.data)
273
+ self.uv = {str(w_a[0]): ChromData(x=data_uv.xlabels, y=w_a[1]) for w_a in zipped_data}
274
+
275
+ def get_spectrum(self, data_path: str, read_uv: bool = False):
276
+ """
277
+ Load chromatogram for any channel in spectra dictionary.
278
+ """
279
+ if read_uv:
280
+ self.get_uv_spectrum(data_path)
281
+
282
+ for channel, spec in self.spectra.items():
283
+ try:
284
+ spec.load_spectrum(data_path=data_path, channel=channel)
285
+ except FileNotFoundError:
286
+ self.spectra[channel] = None
287
+ print(f"No data at channel: {channel}")
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from pychemstation.utils.tray_types import Tray
5
+
6
+
7
+ @dataclass
8
+ class Draw:
9
+ amount: float
10
+ source: Tray
11
+ speed: Any
12
+ offset: Any
13
+
14
+
15
+ @dataclass
16
+ class Wait:
17
+ time: int
18
+
19
+
20
+ @dataclass
21
+ class Inject:
22
+ pass
23
+
24
+
25
+ InjectorFunction = [Draw, Wait, Inject]
26
+
27
+
28
+ @dataclass
29
+ class InjectorTable:
30
+ functions: list[InjectorFunction]
@@ -34,6 +34,7 @@ class Command(Enum):
34
34
  INSTRUMENT_OFF = 'macro "SHUTDOWN.MAC" ,go'
35
35
  INSTRUMENT_ON = 'LIDoOperation "TURN_ON"'
36
36
 
37
+ # Method and Sequence Related
37
38
  GET_METHOD_CMD = "response$ = _MethFile$"
38
39
  GET_ROWS_CMD = 'response_num = TabHdrVal({register}, "{table_name}", "{col_name}")'
39
40
  SWITCH_METHOD_CMD = 'LoadMethod "{method_dir}", "{method_name}.M"'
@@ -0,0 +1,7 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Pump:
6
+ solvent: str
7
+ in_use: bool
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Optional, Union
6
6
 
7
- from pychemstation.utils.tray_types import TenVialColumn
7
+ from pychemstation.utils.tray_types import TenVialColumn, Tray
8
8
 
9
9
 
10
10
  @dataclass
@@ -39,7 +39,8 @@ class InjectionSource(Enum):
39
39
  @dataclass
40
40
  class SequenceEntry:
41
41
  sample_name: str
42
- vial_location: Union[TenVialColumn, int]
42
+ vial_location: Tray
43
+ data_file: Optional[str] = None
43
44
  method: Optional[str] = None
44
45
  num_inj: Optional[int] = 1
45
46
  inj_vol: Optional[int] = 2
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
+ from typing import Union
5
6
 
6
7
 
7
8
  class Num(Enum):
@@ -50,3 +51,6 @@ class TenVialColumn(Enum):
50
51
  EIGHT = 8
51
52
  NINE = 9
52
53
  TEN = 10
54
+
55
+
56
+ Tray = Union[FiftyFourVialPlate, TenVialColumn]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.5.12
3
+ Version: 0.5.13.dev2
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Home-page: https://gitlab.com/heingroup/device-api/pychemstation
6
6
  Author: Lucy Hao
@@ -14,6 +14,7 @@ Requires-Dist: polling
14
14
  Requires-Dist: seabreeze
15
15
  Requires-Dist: xsdata
16
16
  Requires-Dist: result
17
+ Requires-Dist: rainbow
17
18
 
18
19
  # Agilent HPLC Macro Control
19
20
 
@@ -99,7 +100,7 @@ our [GitLab](https://gitlab.com/heingroup/device-api/pychemstation)!
99
100
 
100
101
  ## Authors and Acknowledgements
101
102
 
102
- Lucy Hao
103
+ Lucy Hao, Maria Politi
103
104
 
104
105
  - Adapted from [**AnalyticalLabware**](https://github.com/croningp/analyticallabware), created by members in the Cronin
105
106
  Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
@@ -6,11 +6,19 @@ pychemstation/analysis/utils.py,sha256=ISupAOb_yqA4_DZRK9v18UL-XjUQccAicIJKb1VMn
6
6
  pychemstation/control/__init__.py,sha256=4xTy8X-mkn_PPZKr7w9rnj1wZhtmTesbQptPhpYmKXs,64
7
7
  pychemstation/control/comm.py,sha256=u44g1hTluQ0yUG93Un-QAshScoDpgYRrZfFTgweP5tY,7386
8
8
  pychemstation/control/hplc.py,sha256=5xC5q-hrAn5hKdz-ZwT_Dlas9LWLV27jZvhCKj7Lzg4,8761
9
- pychemstation/control/controllers/__init__.py,sha256=di3ytLIK-35XC_THw4IjNaOtCUTe7GuEOFb-obmREw4,166
9
+ pychemstation/control/controllers/__init__.py,sha256=EM6LBNSTJqYVatmnvPq0P-S3q0POA88c-y64zL79I_I,252
10
10
  pychemstation/control/controllers/comm.py,sha256=IU4I_Q42VNCNUlVi93MxCmw2EBY9hiBDkU9FxubKg3c,7441
11
11
  pychemstation/control/controllers/method.py,sha256=XUclB7lQ_SIkquR58MBmmi9drHIPEq9AR8VprTLenvI,15503
12
12
  pychemstation/control/controllers/sequence.py,sha256=kYNxxck2I-q9mZDEZwG8bJ_99FfLmunS13EAHOS65wU,8288
13
13
  pychemstation/control/controllers/table_controller.py,sha256=70ovnIjLKkJborS1ztk445Mv42TtUM9jUniaQmZuyWQ,11031
14
+ pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ pychemstation/control/controllers/devices/column.py,sha256=SCpCnVFZFUM9LM51MbWkVcBRayN3WFxy7lz9gs2PYeY,348
16
+ pychemstation/control/controllers/devices/device.py,sha256=SF1JK93FjmACnYrlKvldX3gEeA21qnXZegeNhc9QJGQ,738
17
+ pychemstation/control/controllers/devices/pump.py,sha256=DJQh4lNXEraeC1CWrsKmsITOjuYlRI3tih_XRB3F1hg,1404
18
+ pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ pychemstation/control/controllers/tables/method.py,sha256=TlP7FwEmN-QGwewoxXNuzLBAzDipRms9TMTgdAaKJmM,16033
20
+ pychemstation/control/controllers/tables/sequence.py,sha256=Yq31YhcIhmu1A3iVEFV_d_N68QHABGVOkqn1QrvyKu0,8766
21
+ pychemstation/control/controllers/tables/table.py,sha256=LAxDNKY2sEypikYR6v6o8SiuP70Ay-FYcvuCfVSFhsQ,11621
14
22
  pychemstation/control/table/__init__.py,sha256=RgMN4uIWHdNUHpGRBWdzmzAbk7XEKl6Y-qtqWCxzSZU,124
15
23
  pychemstation/control/table/method.py,sha256=THVoGomSXff_CTU3eAYme0BYwkPzab5UgZKsiZ29QSk,12196
16
24
  pychemstation/control/table/sequence.py,sha256=Eri52AnbE3BGthfrRSvYKYciquUzvHKo0lYUTySYYE8,10542
@@ -20,20 +28,23 @@ pychemstation/generated/dad_method.py,sha256=0W8Z5WDtF5jpIcudMqb7XrkTnR2EGg_QOCs
20
28
  pychemstation/generated/pump_method.py,sha256=sUhE2Oo00nzVcoONtq3EMWsN4wLSryXbG8f3EeViWKg,12174
21
29
  pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
30
  pychemstation/utils/chromatogram.py,sha256=-q3_hL9GTyi4C95os7IwAiOrkTM4EXIiigm-nW9pFmM,3221
23
- pychemstation/utils/macro.py,sha256=3KOuhFfs95JGdrIXGlYtlmMEQvWtjhN0eRSio4kjslM,2813
31
+ pychemstation/utils/injector_types.py,sha256=xOkvlX_cH_QmuKbyXj8Lc2JEA_6g82az1zB4oTwcKN0,386
32
+ pychemstation/utils/macro.py,sha256=OCQRRC-f46RlhenZfe7ldSgxBhzcZ5JlW3Ej-9OfeSI,2847
24
33
  pychemstation/utils/method_types.py,sha256=e7c2nWub6pT_S8ZF6sd7uXYm9ZBno0mARnzc1hnSHPY,1516
25
34
  pychemstation/utils/parsing.py,sha256=bnFIsZZwFy9NKzVUf517yN-ogzQbm0hp_aho3KUD6Is,9317
26
- pychemstation/utils/sequence_types.py,sha256=OhbmBUS7XQOKHC8YLjgbEVdpjriEc1bORkwB5ZV43oA,1063
35
+ pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
36
+ pychemstation/utils/sequence_types.py,sha256=4cNpmRdPLN5oGN7ozHgT21E65aBO8vV3ZcRXMOQ3EA8,1084
27
37
  pychemstation/utils/table_types.py,sha256=mlbxPAiPvO_EBba5OSzuJcpCL0srrC7uUfm_lKsOsmA,2557
28
- pychemstation/utils/tray_types.py,sha256=Xe5UvDqGo_FL4roWGlrqoqH4_Kj1Hi60B9UaiNvBRAw,690
38
+ pychemstation/utils/tray_types.py,sha256=MaHN36rhcEI5mAY95VU8hfP9HhAlngQvMYq-2oyC0hc,764
29
39
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
40
  tests/constants.py,sha256=iU6knsNpQGnBfGcC8VAj5SrCDcHD3jBZLhQdF6UUwY4,2452
31
41
  tests/test_comb.py,sha256=EDE1Ve0A_EK4qro9imZsrD0xXrQN8hAowiOWPFtw3dM,5515
32
42
  tests/test_comm.py,sha256=EYOpVXzEMQLGhhKYDPO-KaLcJdPSMPTD9Y4jSI0yVQY,2516
43
+ tests/test_inj.py,sha256=yaPGZoHiOC3ZSgsmrtiqp8QtSo2bMxB9FJhaFlOpad0,1412
33
44
  tests/test_method.py,sha256=r1Q1irqiVzs31QuTYLX3u_A0FpX8rIAQ1L4WOk9tLbk,2473
34
45
  tests/test_sequence.py,sha256=Nz2iqp1cJgw6kcQvnwSkfBmhxpOH62PoEu6o_5rO-PY,4929
35
- pychemstation-0.5.12.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
36
- pychemstation-0.5.12.dist-info/METADATA,sha256=ewxWIstBLkHdJaC4sF2JwejgLAZRrQq_xzuXGwj-1ks,4330
37
- pychemstation-0.5.12.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
38
- pychemstation-0.5.12.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
39
- pychemstation-0.5.12.dist-info/RECORD,,
46
+ pychemstation-0.5.13.dev2.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
47
+ pychemstation-0.5.13.dev2.dist-info/METADATA,sha256=KRy25kSutKC7MXyLmp6YYcIkiIRh9s0GzglAVa1rudg,4372
48
+ pychemstation-0.5.13.dev2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
49
+ pychemstation-0.5.13.dev2.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
50
+ pychemstation-0.5.13.dev2.dist-info/RECORD,,
tests/test_inj.py ADDED
@@ -0,0 +1,38 @@
1
+ import os
2
+ import unittest
3
+
4
+ from pychemstation.control import HPLCController
5
+ from tests.constants import *
6
+
7
+
8
+ class TestInj(unittest.TestCase):
9
+ def setUp(self):
10
+ path_constants = room(254)
11
+ for path in path_constants:
12
+ if not os.path.exists(path):
13
+ self.fail(
14
+ f"{path} does not exist on your system. If you would like to run tests, please change this path.")
15
+
16
+ self.hplc_controller = HPLCController(comm_dir=path_constants[0],
17
+ method_dir=path_constants[1],
18
+ data_dir=path_constants[2],
19
+ sequence_dir=path_constants[3])
20
+
21
+ def test_load_inj(self):
22
+ self.hplc_controller.switch_method(DEFAULT_METHOD)
23
+ try:
24
+ gp_mtd = self.hplc_controller.method_controller.load_from_disk(DEFAULT_METHOD)
25
+ self.assertTrue(gp_mtd.first_row.organic_modifier == 5)
26
+ except Exception as e:
27
+ self.fail(f"Should have not failed, {e}")
28
+
29
+ def test_edit_inj(self):
30
+ self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
31
+ new_method = gen_rand_method()
32
+ try:
33
+ self.hplc_controller.edit_method(new_method)
34
+ except Exception as e:
35
+ self.fail(f"Should have not failed: {e}")
36
+
37
+ if __name__ == '__main__':
38
+ unittest.main()