Skip to content

Commit 103f491

Browse files
committed
Check VariantCalls for duplicate positions.
1 parent eba8bdb commit 103f491

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

pyvdrm/tests/test_vcf.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ def test_init_multiple_wildtypes(self):
203203
r'Multiple wildtypes found: Q, R\.'):
204204
MutationSet(mutations=mutations)
205205

206+
def test_init_bad_text(self):
207+
expected_message = \
208+
r'MutationSet text expects wild type \(optional\), position, and ' \
209+
r'zero or more variants\.'
210+
for bad_text in ('!20A', '20Ac', 'r20Q'):
211+
with self.subTest(bad_text):
212+
with self.assertRaisesRegex(ValueError, expected_message):
213+
MutationSet(bad_text)
214+
206215
def test_repr(self):
207216
expected_repr = "MutationSet('Q1AC')"
208217
ms = MutationSet('Q1AC')
@@ -263,6 +272,19 @@ def test_immutable(self):
263272
with self.assertRaises(AttributeError):
264273
ms.wildtype = 'D'
265274

275+
def test_length(self):
276+
self.assertEqual(0, len(MutationSet('A10')))
277+
self.assertEqual(1, len(MutationSet('A10I')))
278+
self.assertEqual(2, len(MutationSet('A10IL')))
279+
280+
def test_in(self):
281+
ms = MutationSet('A10IL')
282+
283+
self.assertIn(Mutation('A10I'), ms)
284+
self.assertIn(Mutation('A10L'), ms)
285+
self.assertIn(Mutation('10L'), ms)
286+
self.assertNotIn(Mutation('A10S'), ms)
287+
266288

267289
class TestVariantCalls(unittest.TestCase):
268290
def test_init_text(self):
@@ -300,6 +322,11 @@ def test_init_bad_length(self):
300322
ValueError, r'Reference length was 4 and sample length was 5\.'):
301323
VariantCalls(reference=reference, sample=sample)
302324

325+
def test_init_with_duplicate_positions(self):
326+
with self.assertRaisesRegex(
327+
ValueError, r'Multiple mutation sets at position 10\.'):
328+
VariantCalls('H10R A1IN H10C')
329+
303330
def test_init_with_reference(self):
304331
expected_reference = 'ASH'
305332
expected_repr = "VariantCalls('A1IL H3R')"
@@ -362,6 +389,11 @@ def test_in(self):
362389
self.assertIn(mutation_set1, calls)
363390
self.assertNotIn(mutation_set2, calls)
364391

392+
def test_length(self):
393+
self.assertEqual(0, len(VariantCalls('')))
394+
self.assertEqual(1, len(VariantCalls('A10IL')))
395+
self.assertEqual(2, len(VariantCalls('A10IL H3R')))
396+
365397
def test_immutable(self):
366398
calls = VariantCalls('A1IL H3R')
367399

pyvdrm/vcf.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ def __new__(cls, text=None, reference=None, sample=None):
3737
for i, (alt, ref) in enumerate(zip(sample,
3838
reference),
3939
1)}
40+
positions = set()
41+
for mutation_set in mutation_sets:
42+
if mutation_set.pos in positions:
43+
message = 'Multiple mutation sets at position {}.'.format(
44+
mutation_set.pos)
45+
raise ValueError(message)
46+
positions.add(mutation_set.pos)
4047
# noinspection PyArgumentList
4148
return super().__new__(cls,
4249
mutation_sets=mutation_sets,
@@ -62,6 +69,9 @@ def __hash__(self):
6269
def __iter__(self):
6370
return iter(self.mutation_sets)
6471

72+
def __len__(self):
73+
return len(self.mutation_sets)
74+
6575
def __contains__(self, item):
6676
return item in self.mutation_sets
6777

@@ -143,9 +153,11 @@ def __new__(cls,
143153
mutations=None,
144154
reference=None):
145155
if text:
146-
match = re.match(r"([A-Z]?)(\d+)([idA-Z]*)", text)
156+
match = re.match(r"([A-Z]?)(\d+)([idA-Z]*)$", text)
147157
if match is None:
148-
raise ValueError
158+
message = 'MutationSet text expects wild type (optional), ' \
159+
'position, and zero or more variants.'
160+
raise ValueError(message)
149161

150162
wildtype, pos, variants = match.groups()
151163
if reference:
@@ -157,7 +169,7 @@ def __new__(cls,
157169
variant=variant)
158170
for variant in variants)
159171
else:
160-
mutations = frozenset(mutations)
172+
mutations = frozenset(mutations or tuple())
161173
positions = {mutation.pos for mutation in mutations}
162174
wildtypes = {mutation.wildtype for mutation in mutations}
163175
if pos is not None:

0 commit comments

Comments
 (0)