""" Class that represents a file with the prediction results * allows prediction results to be appended to the file in chunks * allows results to be skipped if they already exist in the file """ import csv from typing import List, OrderedDict from pathlib import Path import logging from .models.results import PredictionResult from .errors import PredictionResultExistsError, FileExistsError LOG = logging.getLogger(__name__) class PredictionResultsFileIter: def __init__(self, results_file_class): self._results = results_file_class.get_results() self._results_size = len(self._results) self._current_index = 0 def __iter__(self): return self def __next__(self): if self._current_index < self._results_size: member = self._results[self._current_index] self._current_index += 1 return member raise StopIteration class PredictionResultsFile: """ Writes prediction results to a file in chunks """ COLNAMES = ['chain_id', 'sequence_md5', 'nres', 'ndom', 'chopping', 'confidence', 'time_sec'] def __init__(self, csv_path: Path, *, chunk_size: int=20, write_header: bool=True, allow_append: bool=False, allow_skip: bool=False): self.csv_path = Path(str(csv_path)).absolute() self.chunk_size = chunk_size self.write_header = write_header self.allow_append = allow_append self.allow_skip = allow_skip self._results_by_id: OrderedDict[PredictionResult] = OrderedDict() self._unflushed_results_by_id: OrderedDict[PredictionResult] = OrderedDict() self._written_header = False self._init() def _init(self): if self.csv_path.exists(): msg = f"file '{self.csv_path}' already exists (allow_append={self.allow_append}))" LOG.warning(msg) if not self.allow_append: raise FileExistsError(msg) self._read_results() def _read_results(self): """ Read the results from the file """ with self.csv_path.open('r') as fp: reader = self.get_csv_reader(fp) for row in reader: if row['chain_id'] == 'chain_id': continue pdb_path = Path(row['chain_id'] + '.pdb') result = PredictionResult(pdb_path=pdb_path, **row) self._results_by_id[result.chain_id] = result def get_flushed_results(self): self.flush() return self._results_by_id.values() def get_csv_reader(self, fp): return csv.DictReader(fp, delimiter='\t', fieldnames=self.COLNAMES) def get_csv_writer(self, fp): return csv.DictWriter(fp, delimiter='\t', fieldnames=self.COLNAMES) def write_csv_result(self, csv_writer, res: PredictionResult): """ Render PredictionResult result to file pointer """ row = { 'chain_id': res.chain_id, 'sequence_md5': res.sequence_md5, 'nres': res.nres, 'ndom': res.ndom, 'chopping': res.chopping if res.chopping is not None else 'NULL', 'confidence': f'{res.confidence:.3g}' if res.confidence is not None else 'NULL', 'time_sec': f'{res.time_sec}' if res.time_sec is not None else 'NULL', } csv_writer.writerow(row) def flush(self): """ Write unflushed results to the output file """ if len(self._unflushed_results_by_id) == 0: return with self.csv_path.open('a') as fp: csv_writer = self.get_csv_writer(fp) if self.write_header and not self._written_header and len(self._results_by_id) == 0: csv_writer.writeheader() self._written_header = True for result in self._unflushed_results_by_id.values(): self.write_csv_result(csv_writer, result) def has_result(self, result: PredictionResult): """ Check if the prediction result already exists in the file """ return self.has_result_for_chain_id(result.chain_id) def has_result_for_chain_id(self, chain_id: str): """ Check if the chain_id already exists in the file """ if chain_id in self._results_by_id: return True if chain_id in self._unflushed_results_by_id: return True return False def add_result(self, result: PredictionResult): """ Add a result to the buffer (and possibly flush the buffer) """ if self.has_result(result): msg = f"result '{result.chain_id}' already exists in file '{self.csv_path}'" raise PredictionResultExistsError(msg) self._unflushed_results_by_id[result.chain_id] = result if len(self._unflushed_results_by_id) >= self.chunk_size: self.flush() self._results_by_id.update(self._unflushed_results_by_id) self._unflushed_results_by_id = OrderedDict() def add_results(self, results: List[PredictionResult]): for res in results: self.add_result(res) def __iter__(self): return PredictionResultsFileIter(self)