Skip to content

Commit

Permalink
feat: use OffTargetDetector and ExecutableRunner as ContextManagers (#64
Browse files Browse the repository at this point in the history
)

Since `OffTargetDetector` and `ExecutableRunner` are context managers,
it makes sense to type them as such by having them inherit from the
abstract manager base class. This PR also fixes an issue where if tests
fail, IO resources are not cleaned up since context managers were not
being used.
  • Loading branch information
clintval authored Oct 4, 2024
1 parent c434b1d commit 5515d05
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 27 deletions.
4 changes: 3 additions & 1 deletion prymer/offtarget/offtarget_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
""" # noqa: E501

import itertools
from contextlib import AbstractContextManager
from dataclasses import dataclass
from dataclasses import field
from dataclasses import replace
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -442,4 +443,5 @@ def __exit__(
traceback: Optional[TracebackType],
) -> None:
"""Gracefully terminates any running subprocesses."""
super().__exit__(exc_type, exc_value, traceback)
self.close()
4 changes: 3 additions & 1 deletion prymer/util/executable_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
48 changes: 23 additions & 25 deletions tests/api/test_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down

0 comments on commit 5515d05

Please sign in to comment.