Skip to content

Commit adff10b

Browse files
solve comments
1 parent a6088f5 commit adff10b

4 files changed

Lines changed: 114 additions & 116 deletions

File tree

src/allotropy/parsers/agilent_gen5/agilent_gen5_parser.py

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,7 @@
1010
from allotropy.parsers.agilent_gen5.agilent_gen5_reader import AgilentGen5Reader
1111
from allotropy.parsers.agilent_gen5.agilent_gen5_structure import (
1212
create_metadata,
13-
Gen5DataContext,
14-
get_concentrations,
15-
get_identifiers,
16-
get_kinetic_measurements,
17-
get_results_section,
18-
get_temperature,
19-
HeaderData,
20-
KineticData,
21-
KineticResultProcessor,
22-
ReadData,
23-
ResultProcessor,
24-
SpectralResultProcessor,
25-
StandardResultProcessor,
13+
get_processor,
2614
)
2715
from allotropy.parsers.agilent_gen5.constants import (
2816
NO_MEASUREMENTS_ERROR,
@@ -40,19 +28,12 @@ class AgilentGen5Parser(VendorParser[Data, Model]):
4028

4129
def __init__(self, timestamp_parser: TimestampParser | None = None):
4230
super().__init__(timestamp_parser)
43-
self._processors = [
44-
SpectralResultProcessor(),
45-
KineticResultProcessor(),
46-
StandardResultProcessor(),
47-
]
4831

4932
def create_data(self, named_file_contents: NamedFileContents) -> Data:
5033
reader = AgilentGen5Reader(named_file_contents)
51-
context = self._extract_data_context(
52-
reader, named_file_contents.original_file_path
53-
)
34+
context = reader.extract_data_context(named_file_contents.original_file_path)
5435

55-
processor = self._get_processor(context)
36+
processor = get_processor(context)
5637
measurement_groups, calculated_data = processor.process(context)
5738

5839
if not measurement_groups:
@@ -63,41 +44,3 @@ def create_data(self, named_file_contents: NamedFileContents) -> Data:
6344
measurement_groups=measurement_groups,
6445
calculated_data=calculated_data,
6546
)
66-
67-
def _extract_data_context(
68-
self, reader: AgilentGen5Reader, file_path: str
69-
) -> Gen5DataContext:
70-
results_section = get_results_section(reader)
71-
if not results_section:
72-
reader.header_data.get_unread()
73-
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
74-
75-
kinetic_result = get_kinetic_measurements(reader.time_section)
76-
kinetic_measurements, kinetic_elapsed_time, kinetic_errors = kinetic_result or (
77-
{},
78-
[],
79-
{},
80-
)
81-
82-
return Gen5DataContext(
83-
header_data=HeaderData.create(reader.header_data, file_path),
84-
read_data=ReadData.create(reader.procedure_details),
85-
kinetic_data=KineticData.create(reader.procedure_details),
86-
results_section=results_section,
87-
sample_identifiers=get_identifiers(reader.layout_section),
88-
concentration_values=get_concentrations(reader.layout_section),
89-
actual_temperature=get_temperature(reader.actual_temperature_section),
90-
kinetic_measurements=kinetic_measurements,
91-
kinetic_elapsed_time=kinetic_elapsed_time,
92-
kinetic_errors=kinetic_errors,
93-
wavelength_section=reader.wavelength_section,
94-
)
95-
96-
def _get_processor(self, context: Gen5DataContext) -> ResultProcessor:
97-
"""Get the appropriate processor for the given context."""
98-
for processor in self._processors:
99-
if processor.can_process(context):
100-
return processor
101-
102-
msg = "No suitable processor found for the data."
103-
raise AllotropeConversionError(msg)

src/allotropy/parsers/agilent_gen5/agilent_gen5_reader.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@
22

33
from allotropy.exceptions import AllotropeConversionError
44
from allotropy.named_file_contents import NamedFileContents
5+
from allotropy.parsers.agilent_gen5.agilent_gen5_structure import (
6+
Gen5DataContext,
7+
get_concentrations,
8+
get_identifiers,
9+
get_kinetic_measurements,
10+
get_temperature,
11+
HeaderData,
12+
KineticData,
13+
ReadData,
14+
)
515
from allotropy.parsers.agilent_gen5.constants import (
616
MULTIPLATE_FILE_ERROR,
17+
NO_MEASUREMENTS_ERROR,
718
NO_PLATE_DATA_ERROR,
819
)
920
from allotropy.parsers.lines_reader import SectionLinesReader
@@ -87,3 +98,79 @@ def time_section(self) -> list[str] | None:
8798
@property
8899
def actual_temperature_section(self) -> list[str] | None:
89100
return self.sections.get("Actual Temperature")
101+
102+
def _validate_result_sections(self, result_sections: list[list[str]]) -> None:
103+
"""Validates whether all the result sections dimensions are consistent."""
104+
first_section = result_sections[0]
105+
106+
for section in result_sections[1:]:
107+
if not first_section[0] == section[0] and len(first_section) == len(
108+
section
109+
):
110+
msg = "All result tables should have the same dimensions."
111+
raise AllotropeConversionError(msg)
112+
113+
def get_results_section(self) -> list[str] | None:
114+
"""Returns a valid Results Matrix from the reader sections if found.
115+
116+
Checks for Results in the reader sections, if not found, creates the results matrix with all
117+
sections that are correctly formatted as a results table (excluding the Layout section). If
118+
no tables with results are found, returns None
119+
"""
120+
if "Results" in self.sections:
121+
return self.sections["Results"]
122+
123+
def is_results(section: list[str]) -> bool:
124+
return (
125+
len(section) > 2
126+
and section[1].startswith("\t1")
127+
and section[2].startswith("A\t")
128+
)
129+
130+
result_sections = []
131+
for name, section in self.sections.items():
132+
if name == "Layout":
133+
continue
134+
if is_results(section):
135+
result_sections.append(section[1:])
136+
137+
if result_sections:
138+
self._validate_result_sections(result_sections)
139+
return [
140+
"Results",
141+
result_sections[0][0],
142+
*[
143+
section[i + 1]
144+
for i in range(len(result_sections[0]) - 1)
145+
for section in result_sections
146+
],
147+
]
148+
149+
return None
150+
151+
def extract_data_context(self, file_path: str) -> Gen5DataContext:
152+
results_section = self.get_results_section()
153+
if not results_section:
154+
self.header_data.get_unread()
155+
raise AllotropeConversionError(NO_MEASUREMENTS_ERROR)
156+
157+
kinetic_result = get_kinetic_measurements(self.time_section)
158+
kinetic_measurements, kinetic_elapsed_time, kinetic_errors = kinetic_result or (
159+
{},
160+
[],
161+
{},
162+
)
163+
164+
return Gen5DataContext(
165+
header_data=HeaderData.create(self.header_data, file_path),
166+
read_data=ReadData.create(self.procedure_details),
167+
kinetic_data=KineticData.create(self.procedure_details),
168+
results_section=results_section,
169+
sample_identifiers=get_identifiers(self.layout_section),
170+
concentration_values=get_concentrations(self.layout_section),
171+
actual_temperature=get_temperature(self.actual_temperature_section),
172+
kinetic_measurements=kinetic_measurements,
173+
kinetic_elapsed_time=kinetic_elapsed_time,
174+
kinetic_errors=kinetic_errors,
175+
wavelength_section=self.wavelength_section,
176+
)

src/allotropy/parsers/agilent_gen5/agilent_gen5_structure.py

Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
AllotropeConversionError,
3333
AllotropyParserError,
3434
)
35-
from allotropy.parsers.agilent_gen5.agilent_gen5_reader import AgilentGen5Reader
3635
from allotropy.parsers.agilent_gen5.constants import (
3736
ALPHALISA_FLUORESCENCE_FOUND,
3837
DATA_SOURCE_FEATURE_VALUES,
@@ -732,55 +731,6 @@ def _safe_get(data_list: list[str], idx: int) -> str | None:
732731
return data_list[idx] if data_list and idx < len(data_list) else None
733732

734733

735-
def _validate_result_sections(result_sections: list[list[str]]) -> None:
736-
"""Validates whether all the result sections dimensions are consistent."""
737-
first_section = result_sections[0]
738-
739-
for section in result_sections[1:]:
740-
if not first_section[0] == section[0] and len(first_section) == len(section):
741-
msg = "All result tables should have the same dimensions."
742-
raise AllotropeConversionError(msg)
743-
744-
745-
def get_results_section(reader: AgilentGen5Reader) -> list[str] | None:
746-
"""Returns a valid Results Matrix from the reader sections if found.
747-
748-
Checks for Results in the reader sections, if not found, creates the results matrix with all
749-
sections that are correctly formatted as a results table (excluding the Layout section). If
750-
no tables with results are found, returns None
751-
"""
752-
if "Results" in reader.sections:
753-
return reader.sections["Results"]
754-
755-
def is_results(section: list[str]) -> bool:
756-
return (
757-
len(section) > 2
758-
and section[1].startswith("\t1")
759-
and section[2].startswith("A\t")
760-
)
761-
762-
result_sections = []
763-
for name, section in reader.sections.items():
764-
if name == "Layout":
765-
continue
766-
if is_results(section):
767-
result_sections.append(section[1:])
768-
769-
if result_sections:
770-
_validate_result_sections(result_sections)
771-
return [
772-
"Results",
773-
result_sections[0][0],
774-
*[
775-
section[i + 1]
776-
for i in range(len(result_sections[0]) - 1)
777-
for section in result_sections
778-
],
779-
]
780-
781-
return None
782-
783-
784734
def get_concentrations(layout_lines: list[str] | None) -> dict[str, float | None]:
785735
"""Extract concentration/dilution values from the Layout section.
786736
@@ -1618,8 +1568,9 @@ def _convert_time_to_seconds(time_str: str | None) -> float:
16181568
class ResultProcessor(ABC):
16191569
"""Abstract base class for processing different types of Gen5 results."""
16201570

1571+
@classmethod
16211572
@abstractmethod
1622-
def can_process(self, context: Gen5DataContext) -> bool:
1573+
def can_process(cls, context: Gen5DataContext) -> bool:
16231574
"""Check if this processor can handle the given context."""
16241575
pass
16251576

@@ -1634,7 +1585,8 @@ def process(
16341585
class SpectralResultProcessor(ResultProcessor):
16351586
"""Processor for spectral measurements with wavelength data."""
16361587

1637-
def can_process(self, context: Gen5DataContext) -> bool:
1588+
@classmethod
1589+
def can_process(cls, context: Gen5DataContext) -> bool:
16381590
return context.is_spectral and context.wavelength_section is not None
16391591

16401592
def process(
@@ -1654,7 +1606,8 @@ def process(
16541606
class KineticResultProcessor(ResultProcessor):
16551607
"""Processor for kinetic measurements with time series data."""
16561608

1657-
def can_process(self, context: Gen5DataContext) -> bool:
1609+
@classmethod
1610+
def can_process(cls, context: Gen5DataContext) -> bool:
16581611
if not context.has_kinetic_data:
16591612
return False
16601613

@@ -1684,7 +1637,8 @@ def process(
16841637
class StandardResultProcessor(ResultProcessor):
16851638
"""Processor for standard endpoint measurements."""
16861639

1687-
def can_process(self, _: Gen5DataContext) -> bool:
1640+
@classmethod
1641+
def can_process(cls, _: Gen5DataContext) -> bool:
16881642
return True
16891643

16901644
def process(
@@ -1698,3 +1652,18 @@ def process(
16981652
context.actual_temperature,
16991653
context.concentration_values,
17001654
)
1655+
1656+
1657+
def get_processor(
1658+
context: Gen5DataContext,
1659+
) -> SpectralResultProcessor | KineticResultProcessor | StandardResultProcessor:
1660+
"""Factory function to get the appropriate processor for the given context."""
1661+
if SpectralResultProcessor.can_process(context):
1662+
return SpectralResultProcessor()
1663+
if KineticResultProcessor.can_process(context):
1664+
return KineticResultProcessor()
1665+
if StandardResultProcessor.can_process(context):
1666+
return StandardResultProcessor()
1667+
1668+
msg = "No suitable processor found for the data."
1669+
raise AllotropeConversionError(msg)

src/allotropy/parsers/agilent_gen5_image/agilent_gen5_image_parser.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from allotropy.parsers.agilent_gen5.agilent_gen5_reader import AgilentGen5Reader
1010
from allotropy.parsers.agilent_gen5.agilent_gen5_structure import (
1111
get_identifiers,
12-
get_results_section,
1312
HeaderData,
1413
)
1514
from allotropy.parsers.agilent_gen5_image.agilent_gen5_image_structure import (
@@ -38,7 +37,7 @@ def create_data(self, named_file_contents: NamedFileContents) -> Data:
3837
return Data(
3938
metadata=create_metadata(header_data),
4039
measurement_groups=create_results(
41-
get_results_section(reader),
40+
reader.get_results_section(),
4241
header_data,
4342
read_data,
4443
get_identifiers(reader.sections.get("Layout")),

0 commit comments

Comments
 (0)