pychemstation 0.6.2__py3-none-any.whl → 0.6.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.
@@ -1,18 +1,15 @@
1
- """
2
- pip install pandas
3
- pip install aghplctools==4.8.6
4
- """
5
-
6
1
  import os
7
2
  import re
8
3
  from dataclasses import dataclass
9
- from typing import List, AnyStr
4
+ from typing import List, AnyStr, Dict
10
5
 
11
6
  import pandas as pd
12
7
  from aghplctools.ingestion.text import _no_peaks_re, _area_report_re, _header_block_re, _signal_info_re, \
13
8
  _signal_table_re, chunk_string
14
9
  from result import Result, Err, Ok
15
10
 
11
+ from pychemstation.utils.tray_types import Tray, FiftyFourVialPlate
12
+
16
13
 
17
14
  @dataclass
18
15
  class AgilentPeak:
@@ -25,6 +22,13 @@ class AgilentPeak:
25
22
  height_percent: float
26
23
 
27
24
 
25
+ @dataclass
26
+ class AgilentReport:
27
+ vial_location: Tray
28
+ signals: Dict[AnyStr, List[AgilentPeak]]
29
+ solvents: Dict[AnyStr, AnyStr]
30
+
31
+
28
32
  _column_re_dictionary = { # regex matches for column and unit combinations
29
33
  'Peak': { # peak index
30
34
  '#': '[ ]+(?P<Peak>[\d]+)', # number
@@ -51,7 +55,7 @@ _column_re_dictionary = { # regex matches for column and unit combinations
51
55
  }
52
56
 
53
57
 
54
- def build_peak_regex(signal_table: str):
58
+ def build_peak_regex(signal_table: str) -> re.Pattern[AnyStr]:
55
59
  """
56
60
  Builds a peak regex from a signal table
57
61
 
@@ -211,9 +215,40 @@ def process_folder(folder_path, target_wavelengths=None, min_retention_time=0, m
211
215
  return results_df
212
216
 
213
217
 
214
- def process_csv_report(folder_path: str, num: int) -> Result[List[AgilentPeak], AnyStr]:
215
- potential_report = os.path.join(folder_path, f'REPORT0{num}.CSV')
216
- if os.path.exists(potential_report):
217
- df = pd.read_csv(potential_report, encoding="utf-16", header=None)
218
- return Ok(df.apply(lambda row: AgilentPeak(*row), axis=1))
218
+ def process_csv_report(folder_path: str) -> Result[AgilentReport, AnyStr]:
219
+ labels = os.path.join(folder_path, f'REPORT00.CSV')
220
+ if os.path.exists(labels):
221
+ df_labels: Dict[int, Dict[int: AnyStr]] = pd.read_csv(labels, encoding="utf-16", header=None).to_dict()
222
+ vial_location = []
223
+ signals = {}
224
+ solvents = {}
225
+ for pos, val in df_labels[0].items():
226
+ if val == "Location":
227
+ vial_location = df_labels[1][pos]
228
+ elif "Solvent" in val:
229
+ if val not in solvents.keys():
230
+ solvents[val] = df_labels[2][pos]
231
+ elif val == "Number of Signals":
232
+ num_signals = int(df_labels[1][pos])
233
+ for s in range(1, num_signals + 1):
234
+ peaks = process_peaks(os.path.join(folder_path, f'REPORT0{s}.CSV'))
235
+ if peaks.is_ok():
236
+ wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][-3:]
237
+ signals[wavelength] = peaks.ok_value
238
+ break
239
+
240
+ return Ok(AgilentReport(
241
+ signals=signals,
242
+ vial_location=FiftyFourVialPlate.from_int(vial_location),
243
+ solvents=solvents
244
+ ))
245
+
219
246
  return Err("No report found")
247
+
248
+
249
+ def process_peaks(folder_path: str) -> Result[List[AgilentPeak], AnyStr]:
250
+ try:
251
+ df = pd.read_csv(folder_path, encoding="utf-16", header=None)
252
+ return Ok(df.apply(lambda row: AgilentPeak(*row), axis=1))
253
+ except Exception:
254
+ return Err("Trouble reading report")
@@ -132,7 +132,7 @@ class MethodController(TableController):
132
132
  :raise IndexError: Response did not have expected format. Try again.
133
133
  :raise AssertionError: The desired method is not selected. Try again.
134
134
  """
135
- self.send(Command.SWITCH_METHOD_CMD.value.format(method_dir=self.src,
135
+ self.send(Command.SWITCH_METHOD_CMD_SPECIFIC.value.format(method_dir=self.src,
136
136
  method_name=method_name))
137
137
 
138
138
  time.sleep(2)
@@ -41,8 +41,9 @@ class SequenceController(TableController):
41
41
  inj_vol = int(self.get_text(row, RegisterFlag.INJ_VOL))
42
42
  inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
43
43
  sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
44
+ vial_enum = TenVialColumn(vial_location) if vial_location <= 10 else FiftyFourVialPlate.from_int(num=vial_location)
44
45
  return SequenceEntry(sample_name=sample_name,
45
- vial_location=vial_location,
46
+ vial_location=vial_enum,
46
47
  method=None if len(method) == 0 else method,
47
48
  num_inj=num_inj,
48
49
  inj_vol=inj_vol,
@@ -8,7 +8,7 @@ import abc
8
8
  import os
9
9
  import warnings
10
10
  from dataclasses import dataclass
11
- from typing import Union, Optional
11
+ from typing import Union, Optional, AnyStr
12
12
 
13
13
  import numpy as np
14
14
  import polling
@@ -48,13 +48,13 @@ class TableController(abc.ABC):
48
48
 
49
49
  if src and os.path.isdir(src):
50
50
  self.src: str = src
51
- else:
52
- warnings.warn(f"dir: {src} not found.")
51
+ elif isinstance(src, str):
52
+ raise FileNotFoundError(f"dir: {src} not found.")
53
53
 
54
54
  if data_dir and os.path.isdir(data_dir):
55
55
  self.data_dir: str = data_dir
56
- else:
57
- warnings.warn(f"dir: {data_dir} not found.")
56
+ elif isinstance(data_dir, str):
57
+ raise FileNotFoundError(f"dir: {data_dir} not found.")
58
58
 
59
59
  if hasattr(self, "data_dir"):
60
60
  self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
@@ -43,6 +43,7 @@ class HPLCController:
43
43
  data_dir: str,
44
44
  method_dir: str,
45
45
  sequence_dir: str,
46
+ secondary_data_dir: Optional[str] = None,
46
47
  offline: bool = False):
47
48
  """Initialize HPLC controller. The `hplc_talk.mac` macro file must be loaded in the Chemstation software.
48
49
  `comm_dir` must match the file path in the macro file.
@@ -61,7 +62,7 @@ class HPLCController:
61
62
  offline=offline))
62
63
  self.sequence_controller = SequenceController(controller=self.comm,
63
64
  src=sequence_dir,
64
- data_dir=data_dir,
65
+ data_dir=data_dir if not secondary_data_dir else secondary_data_dir,
65
66
  table=self.SEQUENCE_TABLE,
66
67
  method_dir=method_dir,
67
68
  offline=offline)
@@ -38,6 +38,7 @@ class Command(Enum):
38
38
  GET_METHOD_CMD = "response$ = _MethFile$"
39
39
  GET_ROWS_CMD = 'response_num = TabHdrVal({register}, "{table_name}", "{col_name}")'
40
40
  SWITCH_METHOD_CMD = 'LoadMethod _MethPath$, _MethFile$'
41
+ SWITCH_METHOD_CMD_SPECIFIC = 'LoadMethod "{method_dir}", "{method_name}.M"'
41
42
  START_METHOD_CMD = "StartMethod"
42
43
  RUN_METHOD_CMD = 'RunMethod "{data_dir}",, "{experiment_name}_{timestamp}"'
43
44
  STOP_METHOD_CMD = "StopMethod"
@@ -23,7 +23,7 @@ class Param:
23
23
 
24
24
  @dataclass
25
25
  class HPLCMethodParams:
26
- organic_modifier: int
26
+ organic_modifier: float
27
27
  flow: float
28
28
  pressure: Optional[float] = None # TODO: find this
29
29
 
@@ -51,6 +51,6 @@ class MethodDetails:
51
51
  params: HPLCMethodParams
52
52
  timetable: list[TimeTableEntry]
53
53
  injector_program: Optional[InjectorTable] = None
54
- stop_time: Optional[int] = None
55
- post_time: Optional[int] = None
54
+ stop_time: Optional[float] = None
55
+ post_time: Optional[float] = None
56
56
  dad_wavelengthes: Optional[list[Signal]] = None
@@ -43,7 +43,7 @@ class SequenceEntry:
43
43
  data_file: Optional[str] = None
44
44
  method: Optional[str] = None
45
45
  num_inj: Optional[int] = 1
46
- inj_vol: Optional[int] = 2
46
+ inj_vol: Optional[float] = 2
47
47
  inj_source: Optional[InjectionSource] = InjectionSource.HIP_ALS
48
48
  sample_type: Optional[SampleType] = SampleType.SAMPLE
49
49
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
3
4
  from dataclasses import dataclass
4
5
  from enum import Enum
5
6
  from typing import Union
@@ -16,18 +17,67 @@ class Num(Enum):
16
17
  EIGHT = 8
17
18
  NINE = 9
18
19
 
20
+ @classmethod
21
+ def from_num(cls, num: int) -> Num:
22
+ match num:
23
+ case 1:
24
+ return Num.ONE
25
+ case 2:
26
+ return Num.TWO
27
+ case 3:
28
+ return Num.THREE
29
+ case 4:
30
+ return Num.FOUR
31
+ case 5:
32
+ return Num.FIVE
33
+ case 6:
34
+ return Num.SIX
35
+ case 7:
36
+ return Num.SEVEN
37
+ case 8:
38
+ return Num.EIGHT
39
+ case 9:
40
+ return Num.NINE
41
+ case _:
42
+ raise ValueError("Num is one of 1 to 9")
43
+
19
44
 
20
45
  class Plate(Enum):
21
- ONE = "1"
22
- TWO = "2"
46
+ ONE = -96
47
+ TWO = 4000
48
+
49
+ @classmethod
50
+ def from_num(cls, plate: int) -> Plate:
51
+ if 1 <= plate <= 2:
52
+ return Plate.ONE if plate == 1 else Plate.TWO
53
+ raise ValueError("Plate is one or 1 or 2")
23
54
 
24
55
 
25
56
  class Letter(Enum):
26
- A = 8191
27
- B = 8255
28
- C = 8319
29
- D = 8383
30
- F = 8447
57
+ A = 4191
58
+ B = 4255
59
+ C = 4319
60
+ D = 4383
61
+ E = 4447
62
+ F = 4511
63
+
64
+ @classmethod
65
+ def from_str(cls, let: str) -> Letter:
66
+ match let:
67
+ case "A":
68
+ return Letter.A
69
+ case "B":
70
+ return Letter.B
71
+ case "C":
72
+ return Letter.C
73
+ case "D":
74
+ return Letter.D
75
+ case "E":
76
+ return Letter.E
77
+ case "F":
78
+ return Letter.F
79
+ case _:
80
+ raise ValueError("Letter is one of A to F")
31
81
 
32
82
 
33
83
  @dataclass
@@ -37,7 +87,64 @@ class FiftyFourVialPlate:
37
87
  num: Num
38
88
 
39
89
  def value(self) -> int:
40
- return self.letter.value + self.num.value
90
+ return self.plate.value + self.letter.value + self.num.value
91
+
92
+ @classmethod
93
+ def from_str(cls, loc: str):
94
+ if len(loc) != 5:
95
+ raise ValueError("Plate locations must be PX-LY, where X is either 1 or 2 and Y is 1 to 9")
96
+ try:
97
+ plate = int(loc[1])
98
+ letter = loc[3]
99
+ num = int(loc[4])
100
+ return FiftyFourVialPlate(plate=Plate.from_num(plate),
101
+ letter=Letter.from_str(letter),
102
+ num=Num.from_num(num))
103
+ except Exception as e:
104
+ raise ValueError("Plate locations must be PX-LY, where X is either 1 or 2 and Y is 1 to 9")
105
+
106
+ @classmethod
107
+ def from_int(cls, num: int) -> FiftyFourVialPlate:
108
+ row_starts = [
109
+ # plate 1
110
+ FiftyFourVialPlate.from_str('P1-F1'),
111
+ FiftyFourVialPlate.from_str('P1-E1'),
112
+ FiftyFourVialPlate.from_str('P1-D1'),
113
+ FiftyFourVialPlate.from_str('P1-C1'),
114
+ FiftyFourVialPlate.from_str('P1-B1'),
115
+ FiftyFourVialPlate.from_str('P1-A1'),
116
+ # plate 2
117
+ FiftyFourVialPlate.from_str('P2-F1'),
118
+ FiftyFourVialPlate.from_str('P2-E1'),
119
+ FiftyFourVialPlate.from_str('P2-D1'),
120
+ FiftyFourVialPlate.from_str('P2-C1'),
121
+ FiftyFourVialPlate.from_str('P2-B1'),
122
+ FiftyFourVialPlate.from_str('P2-A1'),
123
+ ]
124
+
125
+ # find which row
126
+ possible_row = None
127
+ for i in range(0, 6):
128
+ p1_val = row_starts[i].value()
129
+ p2_val = row_starts[6 + i].value()
130
+ if num >= p2_val:
131
+ possible_row = row_starts[6 + i]
132
+ elif p1_val <= num < row_starts[-1].value():
133
+ possible_row = row_starts[i]
134
+ if possible_row:
135
+ break
136
+
137
+ # determine which num
138
+ if possible_row:
139
+ starting_loc = possible_row
140
+ base_val = starting_loc.plate.value + starting_loc.letter.value
141
+ for i in range(1, 10):
142
+ if num - i == base_val:
143
+ return FiftyFourVialPlate(
144
+ plate=starting_loc.plate,
145
+ letter=starting_loc.letter,
146
+ num=Num.from_num(i))
147
+ raise ValueError("Number didn't match any location. " + str(num))
41
148
 
42
149
 
43
150
  class TenVialColumn(Enum):
@@ -53,4 +160,4 @@ class TenVialColumn(Enum):
53
160
  TEN = 10
54
161
 
55
162
 
56
- Tray = Union[FiftyFourVialPlate, TenVialColumn]
163
+ Tray = Union[FiftyFourVialPlate, TenVialColumn]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.6.2
3
+ Version: 0.6.5
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
@@ -1,12 +1,12 @@
1
1
  pychemstation/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
2
2
  pychemstation/analysis/__init__.py,sha256=EWoU47iyn9xGS-b44zK9eq50bSjOV4AC5dvt420YMI4,44
3
3
  pychemstation/analysis/base_spectrum.py,sha256=XPf9eJ72uz0CnxCY5uFOyu1MbVX-OTTXeN1tLzIMok4,16536
4
- pychemstation/analysis/process_report.py,sha256=d290KgPY-cMlQg4yHaspLFxgso_yu9qNxXG6SF2qpuo,9054
4
+ pychemstation/analysis/process_report.py,sha256=3NWBK1g9fwjmdHoIlMxlXBlbyU5oXyK-_roEE7ZcPXM,10412
5
5
  pychemstation/analysis/spec_utils.py,sha256=UOo9hJR3evJfmaohEEsyb7aq6X996ofuUfu-GKjiDi8,10201
6
6
  pychemstation/analysis/utils.py,sha256=ISupAOb_yqA4_DZRK9v18UL-XjUQccAicIJKb1VMnGg,2055
7
7
  pychemstation/control/__init__.py,sha256=4xTy8X-mkn_PPZKr7w9rnj1wZhtmTesbQptPhpYmKXs,64
8
8
  pychemstation/control/comm.py,sha256=u44g1hTluQ0yUG93Un-QAshScoDpgYRrZfFTgweP5tY,7386
9
- pychemstation/control/hplc.py,sha256=8vKdm5sTGY_VnBRqcAS3EMRJAQ2wbLRbFswaSi2CHHc,9848
9
+ pychemstation/control/hplc.py,sha256=zUPALdS8TbmtbOwJo4o8x6Ceyxw9nEYOwyfTOWZj510,9957
10
10
  pychemstation/control/controllers/__init__.py,sha256=EM6LBNSTJqYVatmnvPq0P-S3q0POA88c-y64zL79I_I,252
11
11
  pychemstation/control/controllers/comm.py,sha256=IU4I_Q42VNCNUlVi93MxCmw2EBY9hiBDkU9FxubKg3c,7441
12
12
  pychemstation/control/controllers/method.py,sha256=XUclB7lQ_SIkquR58MBmmi9drHIPEq9AR8VprTLenvI,15503
@@ -19,10 +19,10 @@ pychemstation/control/controllers/devices/device.py,sha256=QUFpUwmGxMzmG1CiRhNau
19
19
  pychemstation/control/controllers/devices/injector.py,sha256=Kxf4NjxPUt2xjOmJ9Pea1b9YzWyGcpW8VdOlx9Jo-Nw,5540
20
20
  pychemstation/control/controllers/devices/pump.py,sha256=DJQh4lNXEraeC1CWrsKmsITOjuYlRI3tih_XRB3F1hg,1404
21
21
  pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- pychemstation/control/controllers/tables/method.py,sha256=3GdAQ6Cwf20QL3v7eMkwNieWbJdtr9GN12USbSVl_iE,16426
22
+ pychemstation/control/controllers/tables/method.py,sha256=Bzo0BHatMOFf_TmMBVuQbtLvZRTARaW5q1e2-WKJVHY,16435
23
23
  pychemstation/control/controllers/tables/ms.py,sha256=JFD-tOhu8uRyKdl-E3-neRssii8MNqVRIlsrnFhNY_M,682
24
- pychemstation/control/controllers/tables/sequence.py,sha256=vqwJeV38YWdFnaDXvZVOGYl-UCV9lmMbh8Fj5kQ3mqY,8815
25
- pychemstation/control/controllers/tables/table.py,sha256=GjZ15aJhsCQ9Ps9imOh3Q5NgK97kzangrJcOGa8bjKA,12610
24
+ pychemstation/control/controllers/tables/sequence.py,sha256=RvJEtOskHDAkQOmfZBxvCg2cPoy83zN4LudYmRfp5wg,8935
25
+ pychemstation/control/controllers/tables/table.py,sha256=EqlCrx0wN48_JyzlW7YekgaBePoaD9liC0vomaJsswI,12685
26
26
  pychemstation/control/table/__init__.py,sha256=RgMN4uIWHdNUHpGRBWdzmzAbk7XEKl6Y-qtqWCxzSZU,124
27
27
  pychemstation/control/table/method.py,sha256=THVoGomSXff_CTU3eAYme0BYwkPzab5UgZKsiZ29QSk,12196
28
28
  pychemstation/control/table/sequence.py,sha256=Eri52AnbE3BGthfrRSvYKYciquUzvHKo0lYUTySYYE8,10542
@@ -33,23 +33,26 @@ pychemstation/generated/pump_method.py,sha256=sUhE2Oo00nzVcoONtq3EMWsN4wLSryXbG8
33
33
  pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  pychemstation/utils/chromatogram.py,sha256=-q3_hL9GTyi4C95os7IwAiOrkTM4EXIiigm-nW9pFmM,3221
35
35
  pychemstation/utils/injector_types.py,sha256=SD452SwEHNx4Xs2p-mhg6X0pd99XewQ1Zbu-r9kPOJs,817
36
- pychemstation/utils/macro.py,sha256=o1dypIKhEMbDea34VFh4gKu-kWIjXZAFyqK4FYjCKjo,2836
37
- pychemstation/utils/method_types.py,sha256=3qxKeREl_97GnQ74qcA3kxibnvWI4ueb-6XZlxcV2Hg,1632
36
+ pychemstation/utils/macro.py,sha256=PZ916baFfqCkPaSqpdjm2N5mhfg4V4H8KQl7N5l3rUw,2916
37
+ pychemstation/utils/method_types.py,sha256=5FK7RThLhaQcLrzRi_qLnlPqZuGPtwwipP6eMoq0kpE,1638
38
38
  pychemstation/utils/parsing.py,sha256=bnFIsZZwFy9NKzVUf517yN-ogzQbm0hp_aho3KUD6Is,9317
39
39
  pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
40
- pychemstation/utils/sequence_types.py,sha256=4cNpmRdPLN5oGN7ozHgT21E65aBO8vV3ZcRXMOQ3EA8,1084
40
+ pychemstation/utils/sequence_types.py,sha256=x2EClcq6ROdzeLZg63XcXXTknwl2aZ48Vuyru0xZjgA,1086
41
41
  pychemstation/utils/table_types.py,sha256=0kg7gZXFk7O5l0K1BEBaF4OFFdja3-hFUG9UbN5PBcs,3173
42
- pychemstation/utils/tray_types.py,sha256=MaHN36rhcEI5mAY95VU8hfP9HhAlngQvMYq-2oyC0hc,764
42
+ pychemstation/utils/tray_types.py,sha256=3W0IE20SEqnRRa6pZ4Jv94Cn4-3-LGoP-hxUz5LCztc,4403
43
43
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- tests/constants.py,sha256=55DOLpylLt12mb_gSgzEo-EYynwTmI2uH7PcrTM6Iq0,2455
44
+ tests/constants.py,sha256=7CpYly3S549ALNNezO_KVqEPzl6fIdmTuTfJVFwzZq8,3278
45
45
  tests/test_comb.py,sha256=TS-CbtiPbntL4u6E1gSZ6xquNp6cQxIFdJqUr2ak7PA,5515
46
46
  tests/test_comm.py,sha256=iwl-Ey-xoytXmlNrjG84pDm82Ry_QUX6wY4gmVh4NDc,2516
47
- tests/test_inj.py,sha256=Blpk-z9PQuqo4xQ7AUi0CS2czMiYm-pqZe75OFTXru4,1092
47
+ tests/test_inj.py,sha256=UakPA1Sd1GAbFvFepEredWcWPoW7PMLKotfqVZ1i4hE,1434
48
48
  tests/test_method.py,sha256=KB7yAtVb4gZftnYzh-VfPb9LGVZOHUIW6OljEYRtbhA,4570
49
- tests/test_proc_rep.py,sha256=WhUiFqDD1Aey_Nc6Hvbd2zo48K4MWjwDhfO9LiwEQ7M,703
49
+ tests/test_nightly.py,sha256=WpLZfs-n9oPrwz-64gWJB5oxJGYz_ab4sLZ4E-xFBpY,2654
50
+ tests/test_proc_rep.py,sha256=sxRiTBybVm6lyAqmgrri-T2EfZgs27oSgbYXSsN9CwU,1380
51
+ tests/test_runs_stable.py,sha256=-u9lFUwn26ZsNJtV3yMR8irjdrVezMCjWwImbq6m0H0,3487
50
52
  tests/test_sequence.py,sha256=vs5-dqkItRds_tPM2-N6MNJ37FB0nLRFaDzBV8d42i8,4880
51
- pychemstation-0.6.2.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
52
- pychemstation-0.6.2.dist-info/METADATA,sha256=aZ5H2wFid5vlvSpa7eGizZT4QHiKvm6rN0YL3WdvydY,4370
53
- pychemstation-0.6.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
54
- pychemstation-0.6.2.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
55
- pychemstation-0.6.2.dist-info/RECORD,,
53
+ tests/test_stable.py,sha256=9JNSW5bl13KEJKTNRWR9nCwrGSCYbVClx1yKXAL50xA,11565
54
+ pychemstation-0.6.5.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
55
+ pychemstation-0.6.5.dist-info/METADATA,sha256=UUe-F8F2oeDvQdXMSwgLOZcmZa_aLJdhtqpVAW9hVAU,4370
56
+ pychemstation-0.6.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
57
+ pychemstation-0.6.5.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
58
+ pychemstation-0.6.5.dist-info/RECORD,,
tests/constants.py CHANGED
@@ -1,5 +1,8 @@
1
+ import os
1
2
  import random
2
3
 
4
+ from pychemstation.control import HPLCController
5
+ from pychemstation.utils.macro import Command
3
6
  from pychemstation.utils.method_types import *
4
7
  from pychemstation.utils.sequence_types import *
5
8
 
@@ -63,3 +66,19 @@ seq_entry = SequenceEntry(
63
66
  sample_name="Test",
64
67
  sample_type=SampleType(int(random.random() * 3)),
65
68
  )
69
+
70
+
71
+ def set_up_utils(num: int) -> HPLCController:
72
+ path_constants = room(num)
73
+ for path in path_constants:
74
+ if not os.path.exists(path):
75
+ raise FileNotFoundError(
76
+ f"{path} does not exist on your system. If you would like to run tests, please change this path.")
77
+
78
+ controller = HPLCController(comm_dir=path_constants[0],
79
+ method_dir=path_constants[1],
80
+ data_dir=path_constants[2],
81
+ sequence_dir=path_constants[3])
82
+ controller.send(Command.SAVE_METHOD_CMD.value.format(commit_msg="method saved by pychemstation"))
83
+ controller.send(Command.SAVE_SEQUENCE_CMD)
84
+ return controller
tests/test_inj.py CHANGED
@@ -2,22 +2,27 @@ import os
2
2
  import unittest
3
3
 
4
4
  from pychemstation.control import HPLCController
5
+ from pychemstation.utils.tray_types import FiftyFourVialPlate, Letter, Plate, Num
5
6
  from tests.constants import *
6
7
 
8
+ offline = True
9
+
7
10
 
8
11
  class TestInj(unittest.TestCase):
9
12
  def setUp(self):
10
13
  path_constants = room(254)
11
14
  for path in path_constants:
12
- if not os.path.exists(path):
15
+ if not offline and not os.path.exists(path):
13
16
  self.fail(
14
17
  f"{path} does not exist on your system. If you would like to run tests, please change this path.")
15
18
 
16
- self.hplc_controller = HPLCController(comm_dir=path_constants[0],
19
+ self.hplc_controller = HPLCController(offline=offline,
20
+ comm_dir=path_constants[0],
17
21
  method_dir=path_constants[1],
18
22
  data_dir=path_constants[2],
19
23
  sequence_dir=path_constants[3])
20
- # self.hplc_controller.switch_method(DEFAULT_METHOD)
24
+ if not offline:
25
+ self.hplc_controller.switch_method(DEFAULT_METHOD)
21
26
 
22
27
  def test_load_inj(self):
23
28
  try:
@@ -26,7 +31,8 @@ class TestInj(unittest.TestCase):
26
31
  except Exception as e:
27
32
  self.fail(f"Should have not failed, {e}")
28
33
 
29
-
34
+ def test_plate_number(self):
35
+ self.assertEqual(4096, FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.A, num=Num.FOUR).value())
30
36
 
31
37
 
32
38
  if __name__ == '__main__':
tests/test_nightly.py ADDED
@@ -0,0 +1,80 @@
1
+ import unittest
2
+
3
+ from pluggy import Result
4
+
5
+ from pychemstation.analysis.process_report import process_csv_report
6
+ from pychemstation.utils.tray_types import FiftyFourVialPlate, Letter, Plate, Num
7
+ from tests.constants import *
8
+
9
+ offline = True
10
+
11
+
12
+ class TestNightly(unittest.TestCase):
13
+ def setUp(self):
14
+ path_constants = room(254)
15
+ for path in path_constants:
16
+ if not offline and not os.path.exists(path):
17
+ self.fail(
18
+ f"{path} does not exist on your system. If you would like to run tests, please change this path.")
19
+
20
+ self.hplc_controller = HPLCController(offline=offline,
21
+ comm_dir=path_constants[0],
22
+ method_dir=path_constants[1],
23
+ data_dir=path_constants[2],
24
+ sequence_dir=path_constants[3])
25
+ if not offline:
26
+ self.hplc_controller.switch_method(DEFAULT_METHOD)
27
+
28
+ def test_load_inj(self):
29
+ try:
30
+ inj_table = self.hplc_controller.load_injector_program()
31
+ self.assertTrue(len(inj_table.functions) == 2)
32
+ except Exception as e:
33
+ self.fail(f"Should have not failed, {e}")
34
+
35
+ def test_plate_number(self):
36
+ self.assertEqual(4096, FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.A, num=Num.FOUR).value())
37
+
38
+ def test_build_peak_regex(self):
39
+ try:
40
+ # TODO
41
+ print('yes')
42
+ except Exception as e:
43
+ self.fail(f"Should have not failed, {e}")
44
+
45
+ def test_parse_area_report(self):
46
+ try:
47
+ # TODO
48
+ print('yes')
49
+ except Exception as e:
50
+ self.fail(f"Should have not failed, {e}")
51
+
52
+ def test_process_export_report(self):
53
+ try:
54
+ import pandas as pd
55
+
56
+ file_path = "/Users/lucyhao/Codes/pychemstation/tests/0_2025-03-15 19-14-35.D/Report00.CSV"
57
+ df = pd.read_csv(file_path, encoding="utf-16")
58
+
59
+ # Print the first column
60
+ print(df)
61
+ except Exception as e:
62
+ self.fail(f"Should have not failed, {e}")
63
+
64
+ def test_process_folder(self):
65
+ try:
66
+ # TODO
67
+ print('yes')
68
+ except Exception as e:
69
+ self.fail(f"Should have not failed, {e}")
70
+
71
+ def test_report_csv(self):
72
+ try:
73
+ report: Result = process_csv_report(folder_path="0_2025-03-15 19-14-35.D")
74
+ print(report)
75
+ except Exception as e:
76
+ self.fail(f"Should have not failed: {e}")
77
+
78
+
79
+ if __name__ == '__main__':
80
+ unittest.main()
tests/test_proc_rep.py CHANGED
@@ -7,18 +7,43 @@ from pychemstation.analysis.process_report import process_csv_report
7
7
 
8
8
  class TestReport(unittest.TestCase):
9
9
 
10
- def test_process_reporttxt(self):
10
+ def test_build_peak_regex(self):
11
11
  try:
12
12
  # TODO
13
13
  print('yes')
14
14
  except Exception as e:
15
15
  self.fail(f"Should have not failed, {e}")
16
16
 
17
- def test_report_csv(self):
17
+ def test_parse_area_report(self):
18
18
  try:
19
- possible_peaks: Result = process_csv_report(folder_path="0_2025-03-15 19-14-35.D", num=1)
20
- self.assertTrue(len(possible_peaks.ok_value) == 16)
19
+ # TODO
21
20
  print('yes')
21
+ except Exception as e:
22
+ self.fail(f"Should have not failed, {e}")
23
+
24
+ def test_process_export_report(self):
25
+ try:
26
+ import pandas as pd
27
+
28
+ file_path = "/Users/lucyhao/Codes/pychemstation/tests/0_2025-03-15 19-14-35.D/Report00.CSV"
29
+ df = pd.read_csv(file_path, encoding="utf-16")
30
+
31
+ # Print the first column
32
+ print(df)
33
+ except Exception as e:
34
+ self.fail(f"Should have not failed, {e}")
35
+
36
+ def test_process_folder(self):
37
+ try:
38
+ # TODO
39
+ print('yes')
40
+ except Exception as e:
41
+ self.fail(f"Should have not failed, {e}")
42
+
43
+ def test_report_csv(self):
44
+ try:
45
+ report: Result = process_csv_report(folder_path="0_2025-03-15 19-14-35.D")
46
+ print(report)
22
47
  except Exception as e:
23
48
  self.fail(f"Should have not failed: {e}")
24
49
 
@@ -0,0 +1,88 @@
1
+
2
+ import unittest
3
+
4
+ from pychemstation.utils.tray_types import FiftyFourVialPlate, Plate, Letter, Num
5
+ from tests.constants import *
6
+
7
+ run_too = True
8
+
9
+
10
+ class TestRunsStable(unittest.TestCase):
11
+ def setUp(self):
12
+ self.hplc_controller = set_up_utils(254)
13
+
14
+ def test_run_method(self):
15
+ try:
16
+ self.hplc_controller.run_method(experiment_name="test_experiment")
17
+ chrom = self.hplc_controller.get_last_run_method_data()
18
+ except Exception as e:
19
+ self.fail(f"Should have not failed: {e}")
20
+
21
+ def test_run_10_times(self):
22
+ self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
23
+ rand_method = MethodDetails(
24
+ name=DEFAULT_METHOD,
25
+ params=HPLCMethodParams(
26
+ organic_modifier=5,
27
+ flow=0.65),
28
+ timetable=[TimeTableEntry(
29
+ start_time=0.50,
30
+ organic_modifer=99,
31
+ flow=0.65)],
32
+ stop_time=1,
33
+ post_time=0)
34
+ self.hplc_controller.edit_method(rand_method, save=True)
35
+ try:
36
+ for _ in range(10):
37
+ self.hplc_controller.run_method(experiment_name="limiting_testing")
38
+ except Exception as e:
39
+ self.fail(f"Should have not failed: {e}")
40
+
41
+ def test_update_method_update_seq_table_run(self):
42
+ try:
43
+ loc = FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.F, num=Num.TWO)
44
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
45
+ seq_table = SequenceTable(
46
+ name=DEFAULT_SEQUENCE,
47
+ rows=[SequenceEntry(
48
+ vial_location=loc,
49
+ sample_name="run seq with new method",
50
+ method=DEFAULT_METHOD,
51
+ inj_source=InjectionSource.HIP_ALS,
52
+ inj_vol=0.5,
53
+ num_inj=1,
54
+ sample_type=SampleType.SAMPLE
55
+ )])
56
+ self.hplc_controller.edit_sequence(seq_table) # bug didnt delete all the rows, left an extra
57
+
58
+ self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
59
+ rand_method = MethodDetails(
60
+ name=DEFAULT_METHOD,
61
+ params=HPLCMethodParams(
62
+ organic_modifier=5,
63
+ flow=0.65),
64
+ timetable=[TimeTableEntry(
65
+ start_time=random.randint(1, 3) + 0.50,
66
+ organic_modifer=100,
67
+ flow=0.65)],
68
+ stop_time=random.randint(4, 6),
69
+ post_time=1)
70
+ self.hplc_controller.edit_method(rand_method, save=True)
71
+ if run_too:
72
+ self.hplc_controller.preprun()
73
+ self.hplc_controller.run_sequence()
74
+ chrom = self.hplc_controller.get_last_run_sequence_data()
75
+ # report = process_csv_report(self.hplc_controller.sequence_controller.data_files[-1].child_dirs[-1])
76
+ # self.assertEqual(loc, report.ok_value.vial_location)
77
+ except Exception:
78
+ self.fail("Failed")
79
+
80
+ def test_run_sequence(self):
81
+ try:
82
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
83
+ self.hplc_controller.preprun()
84
+ self.hplc_controller.run_sequence()
85
+ chrom = self.hplc_controller.get_last_run_sequence_data()
86
+ self.assertTrue(len(chrom) == 1)
87
+ except Exception:
88
+ self.fail("Failed")
tests/test_stable.py ADDED
@@ -0,0 +1,268 @@
1
+ import unittest
2
+
3
+ from pychemstation.utils.macro import *
4
+ from pychemstation.utils.tray_types import *
5
+ from tests.constants import *
6
+
7
+
8
+ class TestStable(unittest.TestCase):
9
+
10
+ def setUp(self):
11
+ self.hplc_controller = set_up_utils(254)
12
+
13
+ def test_status_check_standby(self):
14
+ self.hplc_controller.standby()
15
+ self.assertTrue(self.hplc_controller.status() in [HPLCAvailStatus.STANDBY, HPLCRunningStatus.NOTREADY])
16
+
17
+ def test_status_check_preprun(self):
18
+ self.hplc_controller.preprun()
19
+ self.assertTrue(self.hplc_controller.status() in [HPLCAvailStatus.PRERUN, HPLCAvailStatus.STANDBY,
20
+ HPLCRunningStatus.NOTREADY])
21
+
22
+ def test_send_command(self):
23
+ try:
24
+ self.hplc_controller.send(Command.GET_METHOD_CMD)
25
+ except Exception as e:
26
+ self.fail(f"Should not throw error: {e}")
27
+
28
+ def test_send_str(self):
29
+ try:
30
+ self.hplc_controller.send("Local TestNum")
31
+ self.hplc_controller.send("TestNum = 0")
32
+ self.hplc_controller.send("Print TestNum")
33
+ self.hplc_controller.send("response_num = TestNum")
34
+ self.hplc_controller.send("Print response_num")
35
+ except Exception as e:
36
+ self.fail(f"Should not throw error: {e}")
37
+
38
+ def test_get_num(self):
39
+ try:
40
+ self.hplc_controller.send("response_num = 10")
41
+ res = self.hplc_controller.receive().num_response
42
+ self.assertEqual(res, 10)
43
+ except Exception as e:
44
+ self.fail(f"Should not throw error: {e}")
45
+
46
+ def test_get_response(self):
47
+ try:
48
+ self.hplc_controller.switch_method(method_name=DEFAULT_METHOD)
49
+ self.hplc_controller.send(Command.GET_METHOD_CMD)
50
+ res = self.hplc_controller.receive()
51
+ self.assertTrue(DEFAULT_METHOD in res.string_response)
52
+ except Exception as e:
53
+ self.fail(f"Should not throw error: {e}")
54
+
55
+ def test_load_method_from_disk(self):
56
+ self.hplc_controller.switch_method(DEFAULT_METHOD)
57
+ try:
58
+ gp_mtd = self.hplc_controller.method_controller.load_from_disk(DEFAULT_METHOD)
59
+ self.assertTrue(gp_mtd.params.organic_modifier == 5)
60
+ except Exception as e:
61
+ self.fail(f"Should have not failed, {e}")
62
+
63
+ def test_edit_method(self):
64
+ self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
65
+ new_method = MethodDetails(name=DEFAULT_METHOD + ".M",
66
+ timetable=[TimeTableEntry(start_time=1.0,
67
+ organic_modifer=20.0,
68
+ flow=0.65),
69
+ TimeTableEntry(start_time=2.0,
70
+ organic_modifer=30.0,
71
+ flow=0.65),
72
+ TimeTableEntry(start_time=2.5,
73
+ organic_modifer=60.0,
74
+ flow=0.65),
75
+ TimeTableEntry(start_time=3.0,
76
+ organic_modifer=80.0,
77
+ flow=0.65),
78
+ TimeTableEntry(start_time=3.5,
79
+ organic_modifer=100.0,
80
+ flow=0.65)],
81
+ stop_time=4.0,
82
+ post_time=1.0,
83
+ params=HPLCMethodParams(organic_modifier=5.0, flow=0.65))
84
+ try:
85
+ self.hplc_controller.edit_method(new_method)
86
+ self.assertEqual(new_method, self.hplc_controller.load_method())
87
+ except Exception as e:
88
+ self.fail(f"Should have not failed: {e}")
89
+
90
+ def test_load_method(self):
91
+ self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
92
+ new_method = gen_rand_method()
93
+ try:
94
+ self.hplc_controller.edit_method(new_method)
95
+ loaded_method = self.hplc_controller.load_method()
96
+ self.assertEqual(new_method.params.organic_modifier,
97
+ loaded_method.params.organic_modifier)
98
+ self.assertEqual(new_method.timetable[0].organic_modifer,
99
+ loaded_method.timetable[0].organic_modifer)
100
+ self.assertEqual(round(new_method.params.flow, 2),
101
+ round(loaded_method.params.flow, 2))
102
+ except Exception as e:
103
+ self.fail(f"Should have not failed: {e}")
104
+
105
+ def test_switch(self):
106
+ try:
107
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
108
+ except Exception as e:
109
+ self.fail(f"Should have not expected: {e}")
110
+
111
+ def test_read(self):
112
+ try:
113
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
114
+ table = self.hplc_controller.load_sequence()
115
+ self.assertTrue(table)
116
+ except Exception as e:
117
+ self.fail(f"Should have not expected: {e}")
118
+
119
+ def test_edit_entire_table(self):
120
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
121
+ seq_folder = self.hplc_controller.sequence_controller.src
122
+ meth_path = os.path.join(seq_folder, DEFAULT_METHOD)
123
+ try:
124
+ seq_table = SequenceTable(
125
+ name=DEFAULT_SEQUENCE,
126
+ rows=[
127
+ SequenceEntry(
128
+ vial_location=TenVialColumn.ONE,
129
+ method=meth_path,
130
+ num_inj=3,
131
+ inj_vol=4,
132
+ sample_name="Sampel1",
133
+ sample_type=SampleType.SAMPLE,
134
+ inj_source=InjectionSource.HIP_ALS
135
+ ),
136
+ SequenceEntry(
137
+ vial_location=FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.A, num=Num.ONE),
138
+ method=meth_path,
139
+ num_inj=3,
140
+ inj_vol=4,
141
+ sample_name="Sampel2",
142
+ sample_type=SampleType.SAMPLE,
143
+ inj_source=InjectionSource.HIP_ALS
144
+ ),
145
+ SequenceEntry(
146
+ vial_location=FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.A, num=Num.TWO),
147
+ method=meth_path,
148
+ num_inj=3,
149
+ inj_vol=4,
150
+ sample_name="Sampel2",
151
+ sample_type=SampleType.SAMPLE,
152
+ inj_source=InjectionSource.HIP_ALS
153
+ ),
154
+ SequenceEntry(
155
+ vial_location=FiftyFourVialPlate(plate=Plate.ONE, letter=Letter.A, num=Num.THREE),
156
+ method=meth_path,
157
+ num_inj=3,
158
+ inj_vol=4,
159
+ sample_name="Sampel2",
160
+ sample_type=SampleType.SAMPLE,
161
+ inj_source=InjectionSource.HIP_ALS
162
+ )
163
+ ]
164
+ )
165
+ self.hplc_controller.edit_sequence(seq_table)
166
+ self.assertEqual(seq_table,
167
+ self.hplc_controller.load_sequence())
168
+ except Exception:
169
+ self.fail("Should have not occured")
170
+
171
+ def test_edit_entire_table_less_rows(self):
172
+ self.hplc_controller.switch_sequence(sequence_name=DEFAULT_SEQUENCE)
173
+ try:
174
+ seq_table = SequenceTable(
175
+ name=DEFAULT_SEQUENCE,
176
+ rows=[
177
+ SequenceEntry(
178
+ vial_location=TenVialColumn.TEN,
179
+ method=DEFAULT_METHOD,
180
+ num_inj=3,
181
+ inj_vol=4,
182
+ sample_name="Sampel2",
183
+ sample_type=SampleType.SAMPLE,
184
+ inj_source=InjectionSource.HIP_ALS
185
+ ),
186
+ SequenceEntry(
187
+ vial_location=TenVialColumn.THREE,
188
+ method=DEFAULT_METHOD,
189
+ num_inj=3,
190
+ inj_vol=4,
191
+ sample_name="Sampel2",
192
+ sample_type=SampleType.SAMPLE,
193
+ inj_source=InjectionSource.HIP_ALS
194
+ )
195
+ ]
196
+ )
197
+ self.hplc_controller.edit_sequence(seq_table)
198
+ except Exception:
199
+ self.fail("Should have not occured")
200
+
201
+ def test_load(self):
202
+ try:
203
+ seq = self.hplc_controller.load_sequence()
204
+ self.assertTrue(len(seq.rows) > 0)
205
+ except Exception as e:
206
+ self.fail(f"Should have not expected: {e}")
207
+
208
+ def test_tray_nums(self):
209
+ vial_locations = [
210
+ FiftyFourVialPlate.from_str('P1-A7'),
211
+ FiftyFourVialPlate.from_str('P1-B4'),
212
+ FiftyFourVialPlate.from_str('P1-C2'),
213
+ FiftyFourVialPlate.from_str('P1-D8'),
214
+ FiftyFourVialPlate.from_str('P1-E3'),
215
+ FiftyFourVialPlate.from_str('P1-F5'),
216
+ # plate 2
217
+ FiftyFourVialPlate.from_str('P2-A7'),
218
+ FiftyFourVialPlate.from_str('P2-B2'),
219
+ FiftyFourVialPlate.from_str('P2-C1'),
220
+ FiftyFourVialPlate.from_str('P2-D8'),
221
+ FiftyFourVialPlate.from_str('P2-E3'),
222
+ FiftyFourVialPlate.from_str('P2-F6'),
223
+ ]
224
+ seq_table = SequenceTable(
225
+ name=DEFAULT_SEQUENCE,
226
+ rows=[
227
+ SequenceEntry(
228
+ vial_location=v,
229
+ method=DEFAULT_METHOD,
230
+ num_inj=3,
231
+ inj_vol=4,
232
+ sample_name="Sampel2",
233
+ sample_type=SampleType.SAMPLE,
234
+ inj_source=InjectionSource.HIP_ALS
235
+ ) for v in vial_locations
236
+ ]
237
+ )
238
+ self.hplc_controller.edit_sequence(seq_table)
239
+ loaded_table = self.hplc_controller.load_sequence()
240
+ for i in range(len(vial_locations)):
241
+ self.assertTrue(vial_locations[i].value()
242
+ == seq_table.rows[i].vial_location.value()
243
+ == loaded_table.rows[i].vial_location.value())
244
+
245
+ def test_tray_nums_only(self):
246
+ vial_locations = [
247
+ # plate 2
248
+ FiftyFourVialPlate.from_str('P2-A7'),
249
+ FiftyFourVialPlate.from_str('P2-B2'),
250
+ FiftyFourVialPlate.from_str('P2-C1'),
251
+ FiftyFourVialPlate.from_str('P2-D8'),
252
+ FiftyFourVialPlate.from_str('P2-E3'),
253
+ FiftyFourVialPlate.from_str('P2-F6'),
254
+ # plate 1
255
+ FiftyFourVialPlate.from_str('P1-A7'),
256
+ FiftyFourVialPlate.from_str('P1-B4'),
257
+ FiftyFourVialPlate.from_str('P1-C2'),
258
+ FiftyFourVialPlate.from_str('P1-D8'),
259
+ FiftyFourVialPlate.from_str('P1-E3'),
260
+ FiftyFourVialPlate.from_str('P1-F5'),
261
+ ]
262
+
263
+ for i in range(len(vial_locations)):
264
+ self.assertEqual(vial_locations[i], FiftyFourVialPlate.from_int(vial_locations[i].value()))
265
+
266
+
267
+ if __name__ == '__main__':
268
+ unittest.main()