diff --git a/prymer/primer3/primer3.py b/prymer/primer3/primer3.py index aa9995c..59b712a 100644 --- a/prymer/primer3/primer3.py +++ b/prymer/primer3/primer3.py @@ -385,9 +385,8 @@ def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901 seq_args = {} global_args = {} for tag, value in assembled_primer3_tags.items(): - tag_str = f"{tag}" - if tag_str.startswith("SEQUENCE"): - seq_args[tag_str] = value + if tag.is_sequence_arg: + seq_args[tag] = value else: global_args[tag] = value design_primers_retval = primer3.design_primers(seq_args=seq_args, global_args=global_args) @@ -397,22 +396,16 @@ def design(self, design_input: Primer3Input) -> Primer3Result: # noqa: C901 # Because Primer3 will emit both the input given and the output generated, we # discard the input that is echo'ed back by looking for tags (keys) # that do not match any Primer3InputTag - if not any(key == item.value for item in Primer3InputTag): + if key not in Primer3InputTag: primer3_results[key] = value - def primer3_error(message: str) -> None: - """Formats the Primer3 error and raises a ValueError.""" - error_message = f"{message}: " - # add in any reported PRIMER_ERROR - if "PRIMER_ERROR" in primer3_results: - error_message += primer3_results["PRIMER_ERROR"] - # raise the exception now - raise ValueError(error_message) - # Check for any errors. Typically, these are in error_lines, but also the results can # contain the PRIMER_ERROR key. - if "PRIMER_ERROR" in primer3_results : - primer3_error("Primer3 failed") + if "PRIMER_ERROR" in primer3_results: + if "PRIMER_ERROR" in primer3_results: + raise ValueError("Primer3 failed: " + primer3_results["PRIMER_ERROR"]) + else: + raise ValueError("Primer3 failed") match design_input.task: case DesignPrimerPairsTask(): # Primer pair design diff --git a/prymer/primer3/primer3_input_tag.py b/prymer/primer3/primer3_input_tag.py index 109614c..ac3c6b6 100644 --- a/prymer/primer3/primer3_input_tag.py +++ b/prymer/primer3/primer3_input_tag.py @@ -29,6 +29,18 @@ class Primer3InputTag(UppercaseStrEnum): Errors in these "global" input tags are fatal. """ + # Developer note: sequence-specific tags must be specified prior to global tags + + @property + def is_sequence_arg(self) -> bool: + """True if this is a sequence input tag (query-specific)""" + return self.name.startswith("SEQUENCE") + + @property + def is_global_arg(self) -> bool: + """True if this is a global input tags (will persist across primer3 queries)""" + return not self.is_sequence_arg + # Sequence input tags; query-specific SEQUENCE_EXCLUDED_REGION = auto() SEQUENCE_INCLUDED_REGION = auto() diff --git a/tests/primer3/test_primer3.py b/tests/primer3/test_primer3.py index d2c9162..1c7e2ea 100644 --- a/tests/primer3/test_primer3.py +++ b/tests/primer3/test_primer3.py @@ -361,9 +361,7 @@ def test_fasta_close_valid( task=DesignLeftPrimersTask(), ) - with pytest.raises( - ValueError, match="I/O operation on closed file" - ): + with pytest.raises(ValueError, match="I/O operation on closed file"): designer.design(design_input=design_input) diff --git a/tests/primer3/test_primer3_input_tag.py b/tests/primer3/test_primer3_input_tag.py new file mode 100644 index 0000000..b163ecc --- /dev/null +++ b/tests/primer3/test_primer3_input_tag.py @@ -0,0 +1,9 @@ +from prymer.primer3 import Primer3InputTag + + +def test_primer3_input_tag() -> None: + # 17 + for tag in Primer3InputTag: + assert tag.is_sequence_arg == tag.startswith("S") + assert tag.is_global_arg == tag.startswith("P") + assert tag.is_global_arg != tag.is_sequence_arg