Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Release Notes
================================================================================

Version 0.7.9dev
-------------------------------------------------------------------------------

MaskPrimers:

+ Fixed a bug in MaskPrimers align involving the reverse primer position offset
calculation.

Version 0.7.8: January 28, 2026
-------------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion presto/Sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ def localAlignment(seq_record, primers, primers_regex=None, max_error=default_as
align.end = align_coord[0][-1]
else:
# Count position from tail and end gaps
rev_pos = rec_len - align_coord[0][-1]
rev_pos = rec_len - max_len if rec_len > max_len else 0
align.start = rev_pos + align_coord[0][0]
align.end = rev_pos + align_coord[0][-1]

Expand Down
2 changes: 1 addition & 1 deletion presto/Version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
__author__ = 'Jason Anthony Vander Heiden'
__copyright__ = 'Copyright 2026 Kleinstein Lab, Yale University. All rights reserved.'
__license__ = 'GNU Affero General Public License 3 (AGPL-3)'
__version__ = '0.7.8'
__version__ = '0.7.9dev'
__date__ = '2026.01.28'
39 changes: 39 additions & 0 deletions tests/test_MaskPrimers.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,45 @@ def test_localAlignment(self):
self.assertListEqual([(x, round(y, 4)) for x, y in self.align_indel],
[(x.primer, round(x.error, 4)) for x in align])

def test_issue110_revprimer_cut(self):
"""Test reverse primer alignment with maxlen parameter (issue #110)"""
# Test case from issue #110: reverse primer alignment
# This is equivalent to:
# MaskPrimers.py align --mode cut --revpr --skiprc --maxerror 0.2 --maxlen 800

# Input sequence
seq = Seq('ATGTCAGTTAGCTCGTTCAGGTAATAGTTGCCCACACAACGTCAAAATAAGAGAACGGTCGTAACATTATCCGTGATTTTCTCACTACTATCAGTACTCACGACTCGACTCTGCCGCAGCCACGTATCGCCTGAAAGCCAGTCAGCGTTAAGGAGTGCTCTGAGCAGGACAACTCGCGTAGTGAGAGTTACATGTTCGTTGGGCTCTTCCGACACGGACCTGAGTTGGCCAACGTCCCACCTGAGGTCTGTGCCCCGGTGATGAGAAGTGTGCATCTCGTTCTTGCAGCTCGTCAGTACTTTCAGAATCATGGCGTGCATGGTAGAATGACCCTTATAACGGACTTCGACATGGCAATAACCCCCCGTTTCTACTTCTAGAGGAGAAAAGTATTGACATGAGCGCTCCCGGCACAAGGGCCAAAGAAGTCTCCAATTTCTTATTTCCGAATGACATGCGTCTCCTTGCGGGTAAATCACCGACCGCAATTCATAGAAGCCTGGGGGAACAGATAGGTCTAATTAGCTTAAGAGAGTAAATCCTGGGATCATCGATACTAGTTGTTTTATATTTGTTGTAAAAAGTAGTAATTACTTCAGTAGTAACCATAAACTTACGCTGGGGCTTCTTCGGCGGATTTTTACAGTTACCAACCAGGAGATTTGAAGTAAATCAGTTGAGGATTTAGCCGCGCTATCCGGTAATCTCCAAATTAAAACATACCGTTCCATGAAGGCTAGAATTAC')
record = SeqRecord(seq, id='sample4')

# PGK1 reverse complement primer - looking for it at the end of the sequence
primers = OrderedDict([('PGK1', 'CGATACTAGTTGTTTTATATTTGTTGTAAAAAGTAGATAATTACT')])

# Test with maxlen parameter, rev_primer=True, skip_rc=True
score_dict = getDNAScoreDict(mask_score=(0, 1), gap_score=(0, 0))
result = localAlignment(record, primers, max_error=0.2, max_len=800,
rev_primer=True, skip_rc=True, score_dict=score_dict)

print('\nTEST issue #110 reverse primer>')
print(' ID> %s' % result.seq.id)
print(' PRIMER> %s' % result.primer)
print(' REV_PRIM> %s' % result.rev_primer)
print(' PRSTART> %d' % result.start)
print(' ERROR> %.4f' % result.error)

# Verify the alignment found the reverse primer
# Position should be 551 (1-based, as shown in the log output)
self.assertEqual(result.primer, 'PGK1')
self.assertTrue(result.rev_primer)
self.assertEqual(result.start, 551)
self.assertAlmostEqual(result.error, 0.0444, places=3)
self.assertTrue(result.valid)

# Test the cut operation - verify the sequence is trimmed correctly
cut_seq = maskSeq(result, mode='cut', barcode=False)
expected_cut = 'ATGTCAGTTAGCTCGTTCAGGTAATAGTTGCCCACACAACGTCAAAATAAGAGAACGGTCGTAACATTATCCGTGATTTTCTCACTACTATCAGTACTCACGACTCGACTCTGCCGCAGCCACGTATCGCCTGAAAGCCAGTCAGCGTTAAGGAGTGCTCTGAGCAGGACAACTCGCGTAGTGAGAGTTACATGTTCGTTGGGCTCTTCCGACACGGACCTGAGTTGGCCAACGTCCCACCTGAGGTCTGTGCCCCGGTGATGAGAAGTGTGCATCTCGTTCTTGCAGCTCGTCAGTACTTTCAGAATCATGGCGTGCATGGTAGAATGACCCTTATAACGGACTTCGACATGGCAATAACCCCCCGTTTCTACTTCTAGAGGAGAAAAGTATTGACATGAGCGCTCCCGGCACAAGGGCCAAAGAAGTCTCCAATTTCTTATTTCCGAATGACATGCGTCTCCTTGCGGGTAAATCACCGACCGCAATTCATAGAAGCCTGGGGGAACAGATAGGTCTAATTAGCTTAAGAGAGTAAATCCTGGGATCAT'
print(' CUT_SEQ> %s' % cut_seq.seq)
self.assertEqual(str(cut_seq.seq), expected_cut)

#@unittest.skip('-> maskSeq() skipped\n')
def test_maskSeq(self):
print('TEST CUT>')
Expand Down