From 3bc817c7fd1a22d573738616a67f35e67eb5575e Mon Sep 17 00:00:00 2001 From: "C. Titus Brown" Date: Mon, 19 Dec 2016 17:14:56 -0800 Subject: [PATCH] Update tests & constrain behavior in various ways. (#54) * require that Record objects have name, sequence * new file for Record-specific tests * remove 'quality' if set to None --- screed/fasta.py | 4 ++-- screed/fastq.py | 4 ++-- screed/screedRecord.py | 24 +++++++++++++++---- screed/tests/test_fasta.py | 1 + screed/tests/test_record.py | 48 +++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 screed/tests/test_record.py diff --git a/screed/fasta.py b/screed/fasta.py index 400c7d7..bf8d655 100644 --- a/screed/fasta.py +++ b/screed/fasta.py @@ -18,7 +18,7 @@ def fasta_iter(handle, parse_description=False, line=None): line = handle.readline() while line: - data = Record() + data = {} line = to_str(line.strip()) if not line.startswith('>'): @@ -45,4 +45,4 @@ def fasta_iter(handle, parse_description=False, line=None): line = to_str(handle.readline()) data['sequence'] = ''.join(sequenceList) - yield data + yield Record(**data) diff --git a/screed/fastq.py b/screed/fastq.py index ed3798e..4982a31 100644 --- a/screed/fastq.py +++ b/screed/fastq.py @@ -19,7 +19,7 @@ def fastq_iter(handle, line=None, parse_description=False): line = handle.readline() line = to_str(line.strip()) while line: - data = Record() + data = {} if line and not line.startswith('@'): raise IOError("Bad FASTQ format: no '@' at beginning of line") @@ -60,4 +60,4 @@ def fastq_iter(handle, line=None, parse_description=False): raise IOError('sequence and quality strings must be ' 'of equal length') - yield data + yield Record(**data) diff --git a/screed/screedRecord.py b/screed/screedRecord.py index 26dbd84..6436c6f 100644 --- a/screed/screedRecord.py +++ b/screed/screedRecord.py @@ -18,8 +18,22 @@ class Record(MutableMapping): Simple dict-like record interface with bag behavior. """ - def __init__(self, *args, **kwargs): - self.d = dict(*args, **kwargs) + def __init__(self, name=None, sequence=None, **kwargs): + d = dict() + if name is not None: + d['name'] = name + if sequence is not None: + d['sequence'] = sequence + + d.update(kwargs) + + if 'name' not in d: + raise TypeError("'name' must be specified") + if 'sequence' not in d: + raise TypeError("'sequence' must be specified") + if 'quality' in d and d['quality'] is None: + del d['quality'] + self.d = d def __setitem__(self, name, value): self.d[name] = value @@ -38,11 +52,11 @@ def keys(self): def __getitem__(self, idx): if isinstance(idx, slice): - trimmed = Record(self.d) + trimmed = dict(self.d) trimmed['sequence'] = trimmed['sequence'][idx] if 'quality' in trimmed: trimmed['quality'] = trimmed['quality'][idx] - return Record(trimmed) + return Record(**trimmed) return self.d[idx] def __delitem__(self, key): @@ -178,7 +192,7 @@ def _buildRecord(fieldTuple, dbObj, rowName, queryBy): else: hackedResult.append((key, value)) - return Record(hackedResult) + return Record(**dict(hackedResult)) def write_fastx(record, fileobj): diff --git a/screed/tests/test_fasta.py b/screed/tests/test_fasta.py index 5e0663a..0b11823 100644 --- a/screed/tests/test_fasta.py +++ b/screed/tests/test_fasta.py @@ -24,6 +24,7 @@ def test_new_record(): records = list(iter(screed.fasta.fasta_iter(s))) assert records[0]['name'] == '1' assert records[1]['name'] == '2' + assert not hasattr(records[0], 'accuracy') # check for legacy attribute class Test_fasta(object): diff --git a/screed/tests/test_record.py b/screed/tests/test_record.py new file mode 100644 index 0000000..f569a71 --- /dev/null +++ b/screed/tests/test_record.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import, unicode_literals, print_function +from screed import Record +import pytest + + +@pytest.mark.xfail(raises=TypeError) +def test_create_noname(): + r = Record(sequence='ATGGAC') + + +@pytest.mark.xfail(raises=TypeError) +def test_create_noseq(): + r = Record(name='somename') + + +def test_create_quality_none(): + r = Record(name='foo', sequence='ATGACG', quality=None) + assert not hasattr(r, 'quality') + + +def test_len(): + r = Record(name='foo', sequence='ATGACG') + assert len(r) == 6 + + +# copied over from khmer tests/test_read_parsers.py +def test_read_type_basic(): + # Constructing without mandatory arguments should raise an exception + with pytest.raises(TypeError): + Record() + + name = "895:1:1:1246:14654 1:N:0:NNNNN" + sequence = "ACGT" + r = Record(name, sequence) + + assert r.name == name + assert r.sequence == sequence + assert not hasattr(r, 'quality'), x + assert not hasattr(r, 'annotations'), x + + +# copied over from khmer tests/test_read_parsers.py +def test_read_type_attributes(): + r = Record(sequence='ACGT', quality='good', name='1234', annotations='ann') + assert r.sequence == 'ACGT' + assert r.quality == 'good' + assert r.name == '1234' + assert r.annotations == 'ann'