diff --git a/prymer/offtarget/offtarget_detector.py b/prymer/offtarget/offtarget_detector.py index 9d80618..d19d6e2 100644 --- a/prymer/offtarget/offtarget_detector.py +++ b/prymer/offtarget/offtarget_detector.py @@ -75,6 +75,7 @@ """ # noqa: E501 import itertools +from contextlib import AbstractContextManager from dataclasses import dataclass from dataclasses import field from dataclasses import replace @@ -124,7 +125,7 @@ class OffTargetResult: right_primer_spans: list[Span] = field(default_factory=list) -class OffTargetDetector: +class OffTargetDetector(AbstractContextManager): """ Detect off-target mappings of primers and primer pairs. @@ -442,4 +443,5 @@ def __exit__( traceback: Optional[TracebackType], ) -> None: """Gracefully terminates any running subprocesses.""" + super().__exit__(exc_type, exc_value, traceback) self.close() diff --git a/prymer/util/executable_runner.py b/prymer/util/executable_runner.py index 969da7f..e69500c 100644 --- a/prymer/util/executable_runner.py +++ b/prymer/util/executable_runner.py @@ -11,13 +11,14 @@ import os import shutil import subprocess +from contextlib import AbstractContextManager from pathlib import Path from types import TracebackType from typing import Optional from typing import Self -class ExecutableRunner: +class ExecutableRunner(AbstractContextManager): """ Base class for interaction with subprocess for all command-line tools. The base class supports use of the context management protocol and performs basic validation of executable paths. @@ -67,6 +68,7 @@ def __exit__( traceback: Optional[TracebackType], ) -> None: """Gracefully terminates any running subprocesses.""" + super().__exit__(exc_type, exc_value, traceback) self.close() @classmethod diff --git a/tests/api/test_picking.py b/tests/api/test_picking.py index 012b804..3018155 100644 --- a/tests/api/test_picking.py +++ b/tests/api/test_picking.py @@ -573,31 +573,29 @@ def _pick_top_primer_pairs( max_primer_pair_hits: int, min_difference: int = 1, ) -> list[PrimerPair]: - offtarget_detector = OffTargetDetector( - ref=picking_ref, - max_primer_hits=max_primer_hits, - max_primer_pair_hits=max_primer_pair_hits, - three_prime_region_length=5, - max_mismatches_in_three_prime_region=0, - max_mismatches=0, - max_amplicon_size=params.amplicon_sizes.max, - ) - dimer_checker = NtThermoAlign() - - picked = pick_top_primer_pairs( - primer_pairs=primer_pairs, - num_primers=len(primer_pairs), - min_difference=min_difference, - params=params, - offtarget_detector=offtarget_detector, - is_dimer_tm_ok=lambda s1, s2: ( - dimer_checker.duplex_tm(s1=s1, s2=s2) <= params.max_dimer_tm - ), - ) - offtarget_detector.close() - dimer_checker.close() - - return picked + with ( + NtThermoAlign() as dimer_checker, + OffTargetDetector( + ref=picking_ref, + max_primer_hits=max_primer_hits, + max_primer_pair_hits=max_primer_pair_hits, + three_prime_region_length=5, + max_mismatches_in_three_prime_region=0, + max_mismatches=0, + max_amplicon_size=params.amplicon_sizes.max, + ) as offtarget_detector, + ): + picked: list[PrimerPair] = pick_top_primer_pairs( + primer_pairs=primer_pairs, + num_primers=len(primer_pairs), + min_difference=min_difference, + params=params, + offtarget_detector=offtarget_detector, + is_dimer_tm_ok=lambda s1, s2: ( + dimer_checker.duplex_tm(s1=s1, s2=s2) <= params.max_dimer_tm + ), + ) + return picked _PARAMS: FilteringParams = _zero_score_filtering_params(_score_input())