pychemstation 0.10.4__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.
@@ -3,6 +3,7 @@
3
3
  import os
4
4
  import time
5
5
  from dataclasses import dataclass
6
+ from typing import Dict
6
7
 
7
8
  import numpy as np
8
9
 
@@ -114,3 +115,22 @@ class AgilentChannelChromatogramData:
114
115
  F: AgilentHPLCChromatogram
115
116
  G: AgilentHPLCChromatogram
116
117
  H: AgilentHPLCChromatogram
118
+
119
+ @classmethod
120
+ def from_dict(cls, chroms: Dict[str, AgilentHPLCChromatogram]):
121
+ keys = chroms.keys()
122
+ class_keys = vars(AgilentChannelChromatogramData)["__annotations__"].keys()
123
+ if set(class_keys) == set(keys):
124
+ return AgilentChannelChromatogramData(
125
+ A=chroms["A"],
126
+ B=chroms["B"],
127
+ C=chroms["C"],
128
+ D=chroms["D"],
129
+ E=chroms["E"],
130
+ F=chroms["F"],
131
+ G=chroms["G"],
132
+ H=chroms["H"],
133
+ )
134
+ else:
135
+ err = f"{keys} don't match {class_keys}"
136
+ raise KeyError(err)
@@ -1,10 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
4
  import os
3
5
  import re
4
6
  from abc import abstractmethod
5
7
  from dataclasses import dataclass
6
8
  from enum import Enum
7
- from typing import AnyStr, Dict, List, Optional, Pattern
9
+ from typing import AnyStr, Dict, List, Optional, Pattern, Union
8
10
 
9
11
  import pandas as pd
10
12
  from aghplctools.ingestion.text import (
@@ -15,6 +17,7 @@ from aghplctools.ingestion.text import (
15
17
  _signal_table_re,
16
18
  chunk_string,
17
19
  )
20
+ from pandas.errors import EmptyDataError
18
21
  from result import Err, Ok, Result
19
22
 
20
23
  from ..analysis.chromatogram import AgilentHPLCChromatogram
@@ -43,7 +46,7 @@ class Signals:
43
46
  class AgilentReport:
44
47
  vial_location: Optional[Tray]
45
48
  signals: List[Signals]
46
- solvents: Optional[Dict[AnyStr, AnyStr]]
49
+ solvents: Optional[Dict[str, str]]
47
50
 
48
51
 
49
52
  class ReportType(Enum):
@@ -69,6 +72,37 @@ class CSVProcessor(ReportProcessor):
69
72
  """
70
73
  super().__init__(path)
71
74
 
75
+ def find_csv_prefix(self) -> str:
76
+ files = [
77
+ f
78
+ for f in os.listdir(self.path)
79
+ if os.path.isfile(os.path.join(self.path, f))
80
+ ]
81
+ for file in files:
82
+ if "00" in file:
83
+ name, _, file_extension = file.partition(".")
84
+ if "00" in name and file_extension.lower() == "csv":
85
+ prefix, _, _ = name.partition("00")
86
+ return prefix
87
+ raise FileNotFoundError("Couldn't find the prefix for CSV")
88
+
89
+ def report_contains(self, labels: List[str], want: List[str]):
90
+ for label in labels:
91
+ if label in want:
92
+ want.remove(label)
93
+
94
+ all_labels_seen = False
95
+ if len(want) != 0:
96
+ for want_label in want:
97
+ label_seen = False
98
+ for label in labels:
99
+ if want_label in label or want_label == label:
100
+ label_seen = True
101
+ all_labels_seen = label_seen
102
+ else:
103
+ return True
104
+ return all_labels_seen
105
+
72
106
  def process_report(self) -> Result[AgilentReport, AnyStr]:
73
107
  """
74
108
  Method to parse details from CSV report.
@@ -76,15 +110,30 @@ class CSVProcessor(ReportProcessor):
76
110
  :return: subset of complete report details, specifically the sample location, solvents in pumps,
77
111
  and list of peaks at each wavelength channel.
78
112
  """
79
- labels = os.path.join(self.path, "REPORT00.CSV")
80
- if os.path.exists(labels):
81
- df_labels: Dict[int, Dict[int:AnyStr]] = pd.read_csv(
113
+ prefix = self.find_csv_prefix()
114
+ labels = os.path.join(self.path, f"{prefix}00.CSV")
115
+ if not os.path.exists(labels):
116
+ raise ValueError(
117
+ "CSV reports do not exist, make sure to turn on the post run CSV report option!"
118
+ )
119
+ elif os.path.exists(labels):
120
+ LOCATION = "Location"
121
+ NUM_SIGNALS = "Number of Signals"
122
+ SOLVENT = "Solvent"
123
+ df_labels: Dict[int, Dict[int, str]] = pd.read_csv(
82
124
  labels, encoding="utf-16", header=None
83
125
  ).to_dict()
84
- vial_location = []
85
- signals = {}
86
- solvents = {}
87
- for pos, val in df_labels[0].items():
126
+ vial_location: str = ""
127
+ signals: Dict[int, list[AgilentPeak]] = {}
128
+ solvents: Dict[str, str] = {}
129
+ report_labels: Dict[int, str] = df_labels[0]
130
+
131
+ if not self.report_contains(
132
+ list(report_labels.values()), [LOCATION, NUM_SIGNALS, SOLVENT]
133
+ ):
134
+ return Err(f"Missing one of: {LOCATION}, {NUM_SIGNALS}, {SOLVENT}")
135
+
136
+ for pos, val in report_labels.items():
88
137
  if val == "Location":
89
138
  vial_location = df_labels[1][pos]
90
139
  elif "Solvent" in val:
@@ -93,22 +142,29 @@ class CSVProcessor(ReportProcessor):
93
142
  elif val == "Number of Signals":
94
143
  num_signals = int(df_labels[1][pos])
95
144
  for s in range(1, num_signals + 1):
96
- df = pd.read_csv(
97
- os.path.join(self.path, f"REPORT0{s}.CSV"),
98
- encoding="utf-16",
99
- header=None,
100
- )
101
- peaks = df.apply(lambda row: AgilentPeak(*row), axis=1)
102
- wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][
103
- -3:
104
- ]
105
- signals[wavelength] = list(peaks)
145
+ try:
146
+ df = pd.read_csv(
147
+ os.path.join(self.path, f"{prefix}0{s}.CSV"),
148
+ encoding="utf-16",
149
+ header=None,
150
+ )
151
+ peaks = df.apply(lambda row: AgilentPeak(*row), axis=1)
152
+ except EmptyDataError:
153
+ peaks = []
154
+ try:
155
+ wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[
156
+ 0
157
+ ][-3:]
158
+ signals[int(wavelength)] = list(peaks)
159
+ except (IndexError, ValueError):
160
+ # TODO: Ask about the MS signals
161
+ pass
106
162
  break
107
163
 
108
164
  return Ok(
109
165
  AgilentReport(
110
166
  signals=[
111
- Signals(wavelength=int(w), peaks=s, data=None)
167
+ Signals(wavelength=w, peaks=s, data=None)
112
168
  for w, s in signals.items()
113
169
  ],
114
170
  vial_location=FiftyFourVialPlate.from_int(int(vial_location)),
@@ -154,7 +210,7 @@ class TXTProcessor(ReportProcessor):
154
210
  path: str,
155
211
  min_ret_time: int = 0,
156
212
  max_ret_time: int = 999,
157
- target_wavelength_range: List[int] = range(200, 300),
213
+ target_wavelength_range=None,
158
214
  ):
159
215
  """
160
216
  Class to process reports in CSV form.
@@ -164,12 +220,14 @@ class TXTProcessor(ReportProcessor):
164
220
  :param max_ret_time: peaks will only be returned up to this time (min)
165
221
  :param target_wavelength_range: range of wavelengths to return
166
222
  """
223
+ if target_wavelength_range is None:
224
+ target_wavelength_range = list(range(200, 300))
167
225
  self.target_wavelength_range = target_wavelength_range
168
226
  self.min_ret_time = min_ret_time
169
227
  self.max_ret_time = max_ret_time
170
228
  super().__init__(path)
171
229
 
172
- def process_report(self) -> Result[AgilentReport, AnyStr]:
230
+ def process_report(self) -> Result[AgilentReport, Union[AnyStr, Exception]]:
173
231
  """
174
232
  Method to parse details from CSV report.
175
233
  If you want more functionality, use `aghplctools`.
@@ -179,44 +237,48 @@ class TXTProcessor(ReportProcessor):
179
237
  :return: subset of complete report details, specifically the sample location, solvents in pumps,
180
238
  and list of peaks at each wavelength channel.
181
239
  """
182
-
183
- with open(
184
- os.path.join(self.path, "REPORT.TXT"), "r", encoding="utf-16"
185
- ) as openfile:
186
- text = openfile.read()
187
-
188
240
  try:
189
- signals = self.parse_area_report(text)
190
- except ValueError as e:
191
- return Err("No peaks found: " + str(e))
192
-
193
- signals = {
194
- key: signals[key] for key in self.target_wavelength_range if key in signals
195
- }
196
-
197
- parsed_signals = []
198
- for wavelength, wavelength_dict in signals.items():
199
- current_wavelength_signals = Signals(
200
- wavelength=int(wavelength), peaks=[], data=None
201
- )
202
- for ret_time, ret_time_dict in wavelength_dict.items():
203
- if self.min_ret_time <= ret_time <= self.max_ret_time:
204
- current_wavelength_signals.peaks.append(
205
- AgilentPeak(
206
- retention_time=ret_time,
207
- area=ret_time_dict["Area"],
208
- width=ret_time_dict["Width"],
209
- height=ret_time_dict["Height"],
210
- peak_number=None,
211
- peak_type=ret_time_dict["Type"],
212
- area_percent=None,
241
+ with open(
242
+ os.path.join(self.path, "REPORT.TXT"), "r", encoding="utf-16"
243
+ ) as openfile:
244
+ text = openfile.read()
245
+
246
+ try:
247
+ signals = self.parse_area_report(text)
248
+ except ValueError as e:
249
+ return Err("No peaks found: " + str(e))
250
+
251
+ signals = {
252
+ key: signals[key]
253
+ for key in self.target_wavelength_range
254
+ if key in signals
255
+ }
256
+
257
+ parsed_signals = []
258
+ for wavelength, wavelength_dict in signals.items():
259
+ current_wavelength_signals = Signals(
260
+ wavelength=int(wavelength), peaks=[], data=None
261
+ )
262
+ for ret_time, ret_time_dict in wavelength_dict.items():
263
+ if self.min_ret_time <= ret_time <= self.max_ret_time:
264
+ current_wavelength_signals.peaks.append(
265
+ AgilentPeak(
266
+ retention_time=ret_time,
267
+ area=ret_time_dict["Area"],
268
+ width=ret_time_dict["Width"],
269
+ height=ret_time_dict["Height"],
270
+ peak_number=None,
271
+ peak_type=ret_time_dict["Type"],
272
+ area_percent=None,
273
+ )
213
274
  )
214
- )
215
- parsed_signals.append(current_wavelength_signals)
275
+ parsed_signals.append(current_wavelength_signals)
216
276
 
217
- return Ok(
218
- AgilentReport(vial_location=None, solvents=None, signals=parsed_signals)
219
- )
277
+ return Ok(
278
+ AgilentReport(vial_location=None, solvents=None, signals=parsed_signals)
279
+ )
280
+ except Exception as e:
281
+ return Err(e)
220
282
 
221
283
  def parse_area_report(self, report_text: str) -> Dict:
222
284
  """
@@ -234,7 +296,7 @@ class TXTProcessor(ReportProcessor):
234
296
  if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
235
297
  raise ValueError("No peaks found in Report.txt")
236
298
  blocks = _header_block_re.split(report_text)
237
- signals = {} # output dictionary
299
+ signals: Dict[int, dict] = {} # output dictionary
238
300
  for ind, block in enumerate(blocks):
239
301
  # area report block
240
302
  if _area_report_re.match(block): # match area report block
@@ -247,7 +309,7 @@ class TXTProcessor(ReportProcessor):
247
309
  # some error state (e.g. 'not found')
248
310
  if si.group("error") != "":
249
311
  continue
250
- wavelength = float(si.group("wavelength"))
312
+ wavelength = int(si.group("wavelength"))
251
313
  if wavelength in signals:
252
314
  # placeholder error raise just in case (this probably won't happen)
253
315
  raise KeyError(
@@ -270,15 +332,14 @@ class TXTProcessor(ReportProcessor):
270
332
  for key in self._column_re_dictionary:
271
333
  if key in peak.re.groupindex:
272
334
  try: # try float conversion, otherwise continue
273
- value = float(peak.group(key))
335
+ current[key] = float(peak.group(key))
274
336
  except ValueError:
275
- value = peak.group(key)
276
- current[key] = value
337
+ current[key] = peak.group(key)
277
338
  else: # ensures defined
278
339
  current[key] = None
279
340
  return signals
280
341
 
281
- def build_peak_regex(self, signal_table: str) -> Pattern[AnyStr]:
342
+ def build_peak_regex(self, signal_table: str) -> Pattern[str] | None:
282
343
  """
283
344
  Builds a peak regex from a signal table. Courtesy of Veronica Lai.
284
345
 
@@ -311,6 +372,7 @@ class TXTProcessor(ReportProcessor):
311
372
  f'The header/unit combination "{header}" "{unit}" is not defined in the peak regex '
312
373
  f"dictionary. Let Lars know."
313
374
  )
375
+
314
376
  return re.compile(
315
377
  "[ ]+".join(
316
378
  peak_re_string
@@ -3,7 +3,9 @@
3
3
  """
4
4
 
5
5
  from .hplc import HPLCController
6
+ from . import controllers
6
7
 
7
8
  __all__ = [
8
9
  "HPLCController",
10
+ "controllers",
9
11
  ]
@@ -3,7 +3,6 @@
3
3
  """
4
4
 
5
5
  from .comm import CommunicationController
6
- from .tables.method import MethodController
7
- from .tables.sequence import SequenceController
6
+ from . import tables
8
7
 
9
- __all__ = ["CommunicationController", "MethodController", "SequenceController"]
8
+ __all__ = ["CommunicationController", "tables"]
@@ -39,6 +39,7 @@ class CommunicationController:
39
39
  comm_dir: str,
40
40
  cmd_file: str = "cmd",
41
41
  reply_file: str = "reply",
42
+ offline: bool = False,
42
43
  debug: bool = False,
43
44
  ):
44
45
  """
@@ -47,26 +48,27 @@ class CommunicationController:
47
48
  :param reply_file: Name of reply file
48
49
  :param debug: whether to save log of sent commands
49
50
  """
50
- self.debug = debug
51
- if os.path.isdir(comm_dir):
52
- self.cmd_file = os.path.join(comm_dir, cmd_file)
53
- self.reply_file = os.path.join(comm_dir, reply_file)
54
- self.cmd_no = 0
55
- else:
56
- raise FileNotFoundError(f"comm_dir: {comm_dir} not found.")
57
- self._most_recent_hplc_status: Optional[Status] = None
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.")
58
59
 
59
- # Create files for Chemstation to communicate with Python
60
- open(self.cmd_file, "a").close()
61
- 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()
62
63
 
63
- self.reset_cmd_counter()
64
+ self.reset_cmd_counter()
64
65
 
65
- # Initialize row counter for table operations
66
- 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")
67
69
 
68
70
  def get_num_val(self, cmd: str) -> Union[int, float]:
69
- tries = 5
71
+ tries = 10
70
72
  for _ in range(tries):
71
73
  self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
72
74
  res = self.receive()
@@ -75,7 +77,7 @@ class CommunicationController:
75
77
  raise RuntimeError("Failed to get number.")
76
78
 
77
79
  def get_text_val(self, cmd: str) -> str:
78
- tries = 5
80
+ tries = 10
79
81
  for _ in range(tries):
80
82
  self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
81
83
  res = self.receive()
@@ -92,9 +94,15 @@ class CommunicationController:
92
94
  time.sleep(1)
93
95
 
94
96
  try:
95
- parsed_response = self.receive().value.string_response
96
- self._most_recent_hplc_status = str_to_status(parsed_response)
97
- return self._most_recent_hplc_status
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")
98
106
  except IOError:
99
107
  return HPLCErrorStatus.NORESPONSE
100
108
  except IndexError:
@@ -145,7 +153,8 @@ class CommunicationController:
145
153
  :raises IOError: Could not read reply file.
146
154
  :return: Potential ChemStation response
147
155
  """
148
- err: Optional[Union[OSError, IndexError]] = None
156
+ err: Optional[Union[OSError, IndexError, ValueError]] = None
157
+ err_msg = ""
149
158
  for _ in range(num_attempts):
150
159
  time.sleep(1)
151
160
 
@@ -158,7 +167,11 @@ class CommunicationController:
158
167
 
159
168
  try:
160
169
  first_line = response.splitlines()[0]
161
- 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}"
162
175
  except IndexError as e:
163
176
  err = e
164
177
  continue
@@ -169,7 +182,9 @@ class CommunicationController:
169
182
  else:
170
183
  continue
171
184
  else:
172
- 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
+ )
173
188
 
174
189
  def sleepy_send(self, cmd: Union[Command, str]):
175
190
  self.send("Sleep 0.1")
@@ -1,49 +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 pychemstation.analysis.chromatogram import (
10
- AgilentChannelChromatogramData,
11
- AgilentHPLCChromatogram,
12
- )
13
- from ....utils.table_types import T, Table
9
+ from ....utils.macro import Command, Response
10
+ from ....utils.table_types import RegisterFlag, Table, TableOperation
14
11
 
15
12
 
16
- class DeviceController(TableController, abc.ABC):
13
+ class DeviceController(abc.ABC):
17
14
  def __init__(
18
15
  self, controller: CommunicationController, table: Table, offline: bool
19
16
  ):
20
- super().__init__(
21
- controller=controller, src=None, data_dirs=[], table=table, offline=offline
22
- )
17
+ self.table_locator = table
18
+ self.controller = controller
19
+ self.offline = offline
23
20
 
24
21
  @abc.abstractmethod
25
22
  def get_row(self, row: int):
26
23
  pass
27
24
 
28
- def retrieve_recent_data_files(self):
29
- 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
+ )
30
34
 
31
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
32
- 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
+ )
33
44
 
34
- def get_report(
35
- self, report_type: ReportType = ReportType.TXT
36
- ) -> List[AgilentReport]:
37
- 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()
38
61
 
39
- def get_data_uv(
40
- self,
41
- ) -> Union[
42
- List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]
43
- ]:
44
- 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.")
45
68
 
46
- def get_data(
47
- self, custom_path: Optional[str] = None
48
- ) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
49
- 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)
@@ -10,6 +10,7 @@ from ....utils.injector_types import (
10
10
  SourceType,
11
11
  Wait,
12
12
  )
13
+ from ....utils.macro import Response
13
14
  from ....utils.table_types import RegisterFlag, Table
14
15
  from ....utils.tray_types import Tray
15
16
  from .device import DeviceController
@@ -23,7 +24,12 @@ class InjectorController(DeviceController):
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":
@@ -48,14 +54,20 @@ class InjectorController(DeviceController):
48
54
  elif function == "Remote":
49
55
  return Remote(
50
56
  command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
51
- duration=self.get_num(row, RegisterFlag.REMOTE_DUR),
57
+ duration=int(self.get_num(row, RegisterFlag.REMOTE_DUR)),
52
58
  )
59
+ raise ValueError("No valid function found.")
53
60
 
54
61
  def load(self) -> InjectorTable:
55
62
  rows = self.get_num_rows()
56
63
  if rows.is_ok():
57
- return InjectorTable(
58
- functions=[
59
- self.get_row(i) for i in range(int(rows.ok_value.num_response))
60
- ]
61
- )
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"]