Skip to content

Commit

Permalink
feat: add Probe object
Browse files Browse the repository at this point in the history
  • Loading branch information
emmcauley committed Sep 25, 2024
1 parent c83ffb1 commit e9b59de
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 21 deletions.
22 changes: 22 additions & 0 deletions prymer/api/probe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from dataclasses import dataclass

from fgpyo.util.metric import Metric

from prymer.api.primer import Primer


@dataclass(frozen=True, init=True, kw_only=True, slots=True)
class Probe(Primer, Metric["Probe"]):
"""Stores the properties of the designed Probe. Inherits `tm`, `penalty`,
`span`, `bases`, and `tail` from `Primer`.
Attributes:
self_any_th: self-complementarity throughout the probe as calculated by Primer3
self_end_th: 3' end complementarity of the probe as calculated by Primer3
hairpin_th: hairpin formation thermodynamics of the probe as calculated by Primer3
"""

self_any_th: float
self_end_th: float
hairpin_th: float
47 changes: 27 additions & 20 deletions prymer/primer3/primer3.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@
from fgpyo.util.metric import Metric

from prymer.api.primer import Primer
from prymer.api.probe import Probe
from prymer.api.primer_like import PrimerLike
from prymer.api.primer_pair import PrimerPair
from prymer.api.probe import Probe
from prymer.api.span import Span
from prymer.api.span import Strand
from prymer.api.variant_lookup import SimpleVariant
Expand Down Expand Up @@ -366,11 +366,12 @@ def design_oligos(self, design_input: Primer3Input) -> Primer3Result: # noqa: C
f"Error, trying to use a subprocess that has already been "
f"terminated, return code {self._subprocess.returncode}"
)
design_region: Span
match design_input.task:
case PickHybProbeOnly():
design_region: Span = design_input.target
design_region = design_input.target
case _:
design_region: Span = self._create_design_region(
design_region = self._create_design_region(
target_region=design_input.target,
max_amplicon_length=design_input.primer_and_amplicon_params.max_amplicon_length,
min_primer_length=design_input.primer_and_amplicon_params.min_primer_length,
Expand Down Expand Up @@ -438,12 +439,13 @@ def primer3_error(message: str) -> None:
primer3_error("Primer3 failed")

match design_input.task:
case PickHybProbeOnly(): # Probe design
case PickHybProbeOnly(): # Probe design
all_probe_results: list[Probe] = Primer3._build_probes(
design_input=design_input,
design_results=primer3_results,
design_region=design_region,
unmasked_design_seq=soft_masked)
unmasked_design_seq=soft_masked,
)

return Primer3._assemble_single_designs(
design_input=design_input,
Expand Down Expand Up @@ -483,10 +485,10 @@ def primer3_error(message: str) -> None:

@staticmethod
def _build_probes(
design_input: Primer3Input,
design_results: dict[str, str],
design_region: Span,
unmasked_design_seq: str,
design_input: Primer3Input,
design_results: dict[str, str],
design_region: Span,
unmasked_design_seq: str,
) -> list[Probe]:
count: int = _check_design_results(design_input, design_results)
task_key = design_input.task.task_type
Expand Down Expand Up @@ -585,9 +587,13 @@ def _build_primers(
return primers

@staticmethod
def _assemble_single_designs(design_input: Primer3Input, design_results: dict[str, str], unfiltered_designs: Union[list[Primer], list[Probe]]
def _assemble_single_designs(
design_input: Primer3Input,
design_results: dict[str, str],
unfiltered_designs: Union[list[Primer], list[Probe]],
) -> Primer3Result:
"""Screens oligo designs (primers or probes) emitted by Primer3 for acceptable dinucleotide runs and extracts failure reasons for failed designs."""
"""Screens oligo designs (primers or probes) emitted by Primer3 for acceptable dinucleotide
runs and extracts failure reasons for failed designs."""

valid_oligo_designs = [
design
Expand All @@ -607,7 +613,6 @@ def _assemble_single_designs(design_input: Primer3Input, design_results: dict[st
)
return design_candidates


@staticmethod
def _build_primer_pairs(
design_input: Primer3Input,
Expand Down Expand Up @@ -789,8 +794,9 @@ def _create_design_region(

return design_region


def _check_design_results(design_input: Primer3Input, design_results: dict[str, str]) -> int:
"""Checks for any additional Primer3 errors and reports out the count of designs emitted by Primer3."""
"""Checks for any additional Primer3 errors and reports out the count of emitted designs."""
count_tag = design_input.task.count_tag
maybe_count: Optional[str] = design_results.get(count_tag)
if maybe_count is None: # no count tag was found
Expand All @@ -803,13 +809,14 @@ def _check_design_results(design_input: Primer3Input, design_results: dict[str,

return count

def _has_acceptable_dinuc_run(design_input: Primer3Input, oligo_design: Union[Primer, Probe]) -> bool:

def _has_acceptable_dinuc_run(
design_input: Primer3Input, oligo_design: Union[Primer, Probe]
) -> bool:
max_dinuc_bases: int
if type(oligo_design) is Primer:
max_dinuc_bases: int = design_input.primer_and_amplicon_params.primer_max_dinuc_bases
max_dinuc_bases = design_input.primer_and_amplicon_params.primer_max_dinuc_bases
elif type(oligo_design) is Probe:
max_dinuc_bases: int = design_input.probe_params.probe_max_dinuc_bases
max_dinuc_bases = design_input.probe_params.probe_max_dinuc_bases

return (
oligo_design.longest_dinucleotide_run_length()
<= max_dinuc_bases
)
return oligo_design.longest_dinucleotide_run_length() <= max_dinuc_bases
4 changes: 3 additions & 1 deletion tests/primer3/test_primer3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
from prymer.api.span import Span
from prymer.api.span import Strand
from prymer.api.variant_lookup import cached
from prymer.primer3.primer3 import Primer3, _has_acceptable_dinuc_run
from prymer.primer3.primer3 import Primer3
from prymer.primer3.primer3 import Primer3Failure
from prymer.primer3.primer3 import Primer3Result
from prymer.primer3.primer3 import _has_acceptable_dinuc_run
from prymer.primer3.primer3_input import Primer3Input
from prymer.primer3.primer3_parameters import PrimerAndAmpliconParameters
from prymer.primer3.primer3_parameters import ProbeParameters
Expand Down Expand Up @@ -180,6 +181,7 @@ def test_internal_probe_valid_designs(
valid_probes = designer.design_oligos(design_input=design_input)
print(valid_probes)


def test_left_primer_valid_designs(
genome_ref: Path,
single_primer_params: PrimerAndAmpliconParameters,
Expand Down

0 comments on commit e9b59de

Please sign in to comment.