jpayne@68: # cython: embedsignature=True jpayne@68: # cython: profile=True jpayne@68: ############################################################################### jpayne@68: ############################################################################### jpayne@68: # Cython wrapper for SAM/BAM/CRAM files based on htslib jpayne@68: ############################################################################### jpayne@68: # The principal classes defined in this module are: jpayne@68: # jpayne@68: # class FastaFile random read read/write access to faidx indexd files jpayne@68: # class FastxFile streamed read/write access to fasta/fastq files jpayne@68: # jpayne@68: # Additionally this module defines several additional classes that are part jpayne@68: # of the internal API. These are: jpayne@68: # jpayne@68: # class FastqProxy jpayne@68: # class FastxRecord jpayne@68: # jpayne@68: # For backwards compatibility, the following classes are also defined: jpayne@68: # jpayne@68: # class Fastafile equivalent to FastaFile jpayne@68: # class FastqFile equivalent to FastxFile jpayne@68: # jpayne@68: ############################################################################### jpayne@68: # jpayne@68: # The MIT License jpayne@68: # jpayne@68: # Copyright (c) 2015 Andreas Heger jpayne@68: # jpayne@68: # Permission is hereby granted, free of charge, to any person obtaining a jpayne@68: # copy of this software and associated documentation files (the "Software"), jpayne@68: # to deal in the Software without restriction, including without limitation jpayne@68: # the rights to use, copy, modify, merge, publish, distribute, sublicense, jpayne@68: # and/or sell copies of the Software, and to permit persons to whom the jpayne@68: # Software is furnished to do so, subject to the following conditions: jpayne@68: # jpayne@68: # The above copyright notice and this permission notice shall be included in jpayne@68: # all copies or substantial portions of the Software. jpayne@68: # jpayne@68: # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR jpayne@68: # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, jpayne@68: # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL jpayne@68: # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER jpayne@68: # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING jpayne@68: # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER jpayne@68: # DEALINGS IN THE SOFTWARE. jpayne@68: # jpayne@68: ############################################################################### jpayne@68: import sys jpayne@68: import os jpayne@68: import re jpayne@68: jpayne@68: jpayne@68: from libc.errno cimport errno jpayne@68: from libc.string cimport strerror jpayne@68: jpayne@68: from cpython cimport array jpayne@68: jpayne@68: from cpython cimport PyErr_SetString, \ jpayne@68: PyBytes_Check, \ jpayne@68: PyUnicode_Check, \ jpayne@68: PyBytes_FromStringAndSize jpayne@68: jpayne@68: from pysam.libchtslib cimport \ jpayne@68: faidx_nseq, fai_load, fai_load3, fai_destroy, fai_fetch, \ jpayne@68: faidx_seq_len, faidx_iseq, faidx_seq_len, \ jpayne@68: faidx_fetch_seq, hisremote, \ jpayne@68: bgzf_open, bgzf_close jpayne@68: jpayne@68: from pysam.libcutils cimport force_bytes, force_str, charptr_to_str jpayne@68: from pysam.libcutils cimport encode_filename, from_string_and_size jpayne@68: from pysam.libcutils cimport qualitystring_to_array, parse_region jpayne@68: jpayne@68: cdef class FastqProxy jpayne@68: cdef makeFastqProxy(kseq_t * src): jpayne@68: '''enter src into AlignedRead.''' jpayne@68: cdef FastqProxy dest = FastqProxy.__new__(FastqProxy) jpayne@68: dest._delegate = src jpayne@68: return dest jpayne@68: jpayne@68: ## TODO: jpayne@68: ## add automatic indexing. jpayne@68: ## add function to get sequence names. jpayne@68: cdef class FastaFile: jpayne@68: """Random access to fasta formatted files that jpayne@68: have been indexed by :term:`faidx`. jpayne@68: jpayne@68: The file is automatically opened. The index file of file jpayne@68: ```` is expected to be called ``.fai``. jpayne@68: jpayne@68: Parameters jpayne@68: ---------- jpayne@68: jpayne@68: filename : string jpayne@68: Filename of fasta file to be opened. jpayne@68: jpayne@68: filepath_index : string jpayne@68: Optional, filename of the index. By default this is jpayne@68: the filename + ".fai". jpayne@68: jpayne@68: filepath_index_compressed : string jpayne@68: Optional, filename of the index if fasta file is. By default this is jpayne@68: the filename + ".gzi". jpayne@68: jpayne@68: Raises jpayne@68: ------ jpayne@68: jpayne@68: ValueError jpayne@68: if index file is missing jpayne@68: jpayne@68: IOError jpayne@68: if file could not be opened jpayne@68: jpayne@68: """ jpayne@68: jpayne@68: def __cinit__(self, *args, **kwargs): jpayne@68: self.fastafile = NULL jpayne@68: self._filename = None jpayne@68: self._references = None jpayne@68: self._lengths = None jpayne@68: self.reference2length = None jpayne@68: self._open(*args, **kwargs) jpayne@68: jpayne@68: def is_open(self): jpayne@68: '''return true if samfile has been opened.''' jpayne@68: return self.fastafile != NULL jpayne@68: jpayne@68: def __len__(self): jpayne@68: if self.fastafile == NULL: jpayne@68: raise ValueError("calling len() on closed file") jpayne@68: jpayne@68: return faidx_nseq(self.fastafile) jpayne@68: jpayne@68: def _open(self, filename, filepath_index=None, filepath_index_compressed=None): jpayne@68: '''open an indexed fasta file. jpayne@68: jpayne@68: This method expects an indexed fasta file. jpayne@68: ''' jpayne@68: jpayne@68: # close a previously opened file jpayne@68: if self.fastafile != NULL: jpayne@68: self.close() jpayne@68: jpayne@68: self._filename = encode_filename(filename) jpayne@68: cdef char *cfilename = self._filename jpayne@68: cdef char *cindexname = NULL jpayne@68: cdef char *cindexname_compressed = NULL jpayne@68: self.is_remote = hisremote(cfilename) jpayne@68: jpayne@68: # open file for reading jpayne@68: if (self._filename != b"-" jpayne@68: and not self.is_remote jpayne@68: and not os.path.exists(filename)): jpayne@68: raise IOError("file `%s` not found" % filename) jpayne@68: jpayne@68: # 3 modes to open: jpayne@68: # compressed fa: fai_load3 with filename, index_fai and index_gzi jpayne@68: # uncompressed fa: fai_load3 with filename and index_fai jpayne@68: # uncompressed fa: fai_load with default index name jpayne@68: if filepath_index: jpayne@68: # when opening, set flags to 0 - do not automatically jpayne@68: # build index if it does not exist. jpayne@68: jpayne@68: if not os.path.exists(filepath_index): jpayne@68: raise IOError("filename {} does not exist".format(filepath_index)) jpayne@68: cindexname = bindex_filename = encode_filename(filepath_index) jpayne@68: jpayne@68: if filepath_index_compressed: jpayne@68: if not os.path.exists(filepath_index_compressed): jpayne@68: raise IOError("filename {} does not exist".format(filepath_index_compressed)) jpayne@68: cindexname_compressed = bindex_filename_compressed = encode_filename(filepath_index_compressed) jpayne@68: with nogil: jpayne@68: self.fastafile = fai_load3(cfilename, cindexname, cindexname_compressed, 0) jpayne@68: else: jpayne@68: with nogil: jpayne@68: self.fastafile = fai_load3(cfilename, cindexname, NULL, 0) jpayne@68: else: jpayne@68: with nogil: jpayne@68: self.fastafile = fai_load(cfilename) jpayne@68: jpayne@68: if self.fastafile == NULL: jpayne@68: raise IOError("error when opening file `%s`" % filename) jpayne@68: jpayne@68: cdef int nreferences = faidx_nseq(self.fastafile) jpayne@68: cdef int x jpayne@68: cdef const char * s jpayne@68: self._references = [] jpayne@68: self._lengths = [] jpayne@68: for x from 0 <= x < nreferences: jpayne@68: s = faidx_iseq(self.fastafile, x) jpayne@68: ss = force_str(s) jpayne@68: self._references.append(ss) jpayne@68: self._lengths.append(faidx_seq_len(self.fastafile, s)) jpayne@68: self.reference2length = dict(zip(self._references, self._lengths)) jpayne@68: jpayne@68: def close(self): jpayne@68: """close the file.""" jpayne@68: if self.fastafile != NULL: jpayne@68: fai_destroy(self.fastafile) jpayne@68: self.fastafile = NULL jpayne@68: jpayne@68: def __dealloc__(self): jpayne@68: if self.fastafile != NULL: jpayne@68: fai_destroy(self.fastafile) jpayne@68: self.fastafile = NULL jpayne@68: jpayne@68: # context manager interface jpayne@68: def __enter__(self): jpayne@68: return self jpayne@68: jpayne@68: def __exit__(self, exc_type, exc_value, traceback): jpayne@68: self.close() jpayne@68: return False jpayne@68: jpayne@68: property closed: jpayne@68: """bool indicating the current state of the file object. jpayne@68: This is a read-only attribute; the close() method changes the value. jpayne@68: """ jpayne@68: def __get__(self): jpayne@68: return not self.is_open() jpayne@68: jpayne@68: property filename: jpayne@68: """filename associated with this object. This is a read-only attribute.""" jpayne@68: def __get__(self): jpayne@68: return self._filename jpayne@68: jpayne@68: property references: jpayne@68: '''tuple with the names of :term:`reference` sequences.''' jpayne@68: def __get__(self): jpayne@68: return self._references jpayne@68: jpayne@68: property nreferences: jpayne@68: """int with the number of :term:`reference` sequences in the file. jpayne@68: This is a read-only attribute.""" jpayne@68: def __get__(self): jpayne@68: return len(self._references) if self.references else None jpayne@68: jpayne@68: property lengths: jpayne@68: """tuple with the lengths of :term:`reference` sequences.""" jpayne@68: def __get__(self): jpayne@68: return self._lengths jpayne@68: jpayne@68: def fetch(self, jpayne@68: reference=None, jpayne@68: start=None, jpayne@68: end=None, jpayne@68: region=None): jpayne@68: """fetch sequences in a :term:`region`. jpayne@68: jpayne@68: A region can jpayne@68: either be specified by :term:`reference`, `start` and jpayne@68: `end`. `start` and `end` denote 0-based, half-open jpayne@68: intervals. jpayne@68: jpayne@68: Alternatively, a samtools :term:`region` string can be jpayne@68: supplied. jpayne@68: jpayne@68: If any of the coordinates are missing they will be replaced by the jpayne@68: minimum (`start`) or maximum (`end`) coordinate. jpayne@68: jpayne@68: Note that region strings are 1-based, while `start` and `end` denote jpayne@68: an interval in python coordinates. jpayne@68: The region is specified by :term:`reference`, `start` and `end`. jpayne@68: jpayne@68: Returns jpayne@68: ------- jpayne@68: jpayne@68: string : a string with the sequence specified by the region. jpayne@68: jpayne@68: Raises jpayne@68: ------ jpayne@68: jpayne@68: IndexError jpayne@68: if the coordinates are out of range jpayne@68: jpayne@68: ValueError jpayne@68: if the region is invalid jpayne@68: jpayne@68: """ jpayne@68: jpayne@68: if not self.is_open(): jpayne@68: raise ValueError("I/O operation on closed file" ) jpayne@68: jpayne@68: cdef int length jpayne@68: cdef char *seq jpayne@68: cdef char *ref jpayne@68: cdef int rstart, rend jpayne@68: jpayne@68: contig, rstart, rend = parse_region(reference, start, end, region) jpayne@68: jpayne@68: if contig is None: jpayne@68: raise ValueError("no sequence/region supplied.") jpayne@68: jpayne@68: if rstart == rend: jpayne@68: return "" jpayne@68: jpayne@68: contig_b = force_bytes(contig) jpayne@68: ref = contig_b jpayne@68: with nogil: jpayne@68: length = faidx_seq_len(self.fastafile, ref) jpayne@68: if length == -1: jpayne@68: raise KeyError("sequence '%s' not present" % contig) jpayne@68: if rstart >= length: jpayne@68: return "" jpayne@68: jpayne@68: # fai_fetch adds a '\0' at the end jpayne@68: with nogil: jpayne@68: seq = faidx_fetch_seq(self.fastafile, jpayne@68: ref, jpayne@68: rstart, jpayne@68: rend-1, jpayne@68: &length) jpayne@68: jpayne@68: if not seq: jpayne@68: if errno: jpayne@68: raise IOError(errno, strerror(errno)) jpayne@68: else: jpayne@68: raise ValueError("failure when retrieving sequence on '%s'" % contig) jpayne@68: jpayne@68: try: jpayne@68: return charptr_to_str(seq) jpayne@68: finally: jpayne@68: free(seq) jpayne@68: jpayne@68: cdef char *_fetch(self, char *reference, int start, int end, int *length) except? NULL: jpayne@68: '''fetch sequence for reference, start and end''' jpayne@68: jpayne@68: cdef char *seq jpayne@68: with nogil: jpayne@68: seq = faidx_fetch_seq(self.fastafile, jpayne@68: reference, jpayne@68: start, jpayne@68: end-1, jpayne@68: length) jpayne@68: jpayne@68: if not seq: jpayne@68: if errno: jpayne@68: raise IOError(errno, strerror(errno)) jpayne@68: else: jpayne@68: raise ValueError("failure when retrieving sequence on '%s'" % reference) jpayne@68: jpayne@68: return seq jpayne@68: jpayne@68: def get_reference_length(self, reference): jpayne@68: '''return the length of reference.''' jpayne@68: return self.reference2length[reference] jpayne@68: jpayne@68: def __getitem__(self, reference): jpayne@68: return self.fetch(reference) jpayne@68: jpayne@68: def __contains__(self, reference): jpayne@68: '''return true if reference in fasta file.''' jpayne@68: return reference in self.reference2length jpayne@68: jpayne@68: jpayne@68: cdef class FastqProxy: jpayne@68: """A single entry in a fastq file.""" jpayne@68: def __init__(self): jpayne@68: raise ValueError("do not instantiate FastqProxy directly") jpayne@68: jpayne@68: property name: jpayne@68: """The name of each entry in the fastq file.""" jpayne@68: def __get__(self): jpayne@68: return charptr_to_str(self._delegate.name.s) jpayne@68: jpayne@68: property sequence: jpayne@68: """The sequence of each entry in the fastq file.""" jpayne@68: def __get__(self): jpayne@68: return charptr_to_str(self._delegate.seq.s) jpayne@68: jpayne@68: property comment: jpayne@68: def __get__(self): jpayne@68: if self._delegate.comment.l: jpayne@68: return charptr_to_str(self._delegate.comment.s) jpayne@68: else: jpayne@68: return None jpayne@68: jpayne@68: property quality: jpayne@68: """The quality score of each entry in the fastq file, represented as a string.""" jpayne@68: def __get__(self): jpayne@68: if self._delegate.qual.l: jpayne@68: return charptr_to_str(self._delegate.qual.s) jpayne@68: else: jpayne@68: return None jpayne@68: jpayne@68: cdef cython.str to_string(self): jpayne@68: if self.comment is None: jpayne@68: comment = "" jpayne@68: else: jpayne@68: comment = " %s" % self.comment jpayne@68: jpayne@68: if self.quality is None: jpayne@68: return ">%s%s\n%s" % (self.name, comment, self.sequence) jpayne@68: else: jpayne@68: return "@%s%s\n%s\n+\n%s" % (self.name, comment, jpayne@68: self.sequence, self.quality) jpayne@68: jpayne@68: cdef cython.str tostring(self): jpayne@68: """deprecated : use :meth:`to_string`""" jpayne@68: return self.to_string() jpayne@68: jpayne@68: def __str__(self): jpayne@68: return self.to_string() jpayne@68: jpayne@68: cpdef array.array get_quality_array(self, int offset=33): jpayne@68: '''return quality values as integer array after subtracting offset.''' jpayne@68: if self.quality is None: jpayne@68: return None jpayne@68: return qualitystring_to_array(force_bytes(self.quality), jpayne@68: offset=offset) jpayne@68: jpayne@68: cdef class FastxRecord: jpayne@68: """A fasta/fastq record. jpayne@68: jpayne@68: A record must contain a name and a sequence. If either of them are jpayne@68: None, a ValueError is raised on writing. jpayne@68: jpayne@68: """ jpayne@68: def __init__(self, jpayne@68: name=None, jpayne@68: comment=None, jpayne@68: sequence=None, jpayne@68: quality=None, jpayne@68: FastqProxy proxy=None): jpayne@68: if proxy is not None: jpayne@68: self.comment = proxy.comment jpayne@68: self.quality = proxy.quality jpayne@68: self.sequence = proxy.sequence jpayne@68: self.name = proxy.name jpayne@68: else: jpayne@68: self.comment = comment jpayne@68: self.quality = quality jpayne@68: self.sequence = sequence jpayne@68: self.name = name jpayne@68: jpayne@68: def __copy__(self): jpayne@68: return FastxRecord(self.name, self.comment, self.sequence, self.quality) jpayne@68: jpayne@68: def __deepcopy__(self, memo): jpayne@68: return FastxRecord(self.name, self.comment, self.sequence, self.quality) jpayne@68: jpayne@68: cdef cython.str to_string(self): jpayne@68: if self.name is None: jpayne@68: raise ValueError("can not write record without name") jpayne@68: jpayne@68: if self.sequence is None: jpayne@68: raise ValueError("can not write record without a sequence") jpayne@68: jpayne@68: if self.comment is None: jpayne@68: comment = "" jpayne@68: else: jpayne@68: comment = " %s" % self.comment jpayne@68: jpayne@68: if self.quality is None: jpayne@68: return ">%s%s\n%s" % (self.name, comment, self.sequence) jpayne@68: else: jpayne@68: return "@%s%s\n%s\n+\n%s" % (self.name, comment, jpayne@68: self.sequence, self.quality) jpayne@68: jpayne@68: cdef cython.str tostring(self): jpayne@68: """deprecated : use :meth:`to_string`""" jpayne@68: return self.to_string() jpayne@68: jpayne@68: def set_name(self, name): jpayne@68: if name is None: jpayne@68: raise ValueError("FastxRecord must have a name and not None") jpayne@68: self.name = name jpayne@68: jpayne@68: def set_comment(self, comment): jpayne@68: self.comment = comment jpayne@68: jpayne@68: def set_sequence(self, sequence, quality=None): jpayne@68: """set sequence of this record. jpayne@68: jpayne@68: """ jpayne@68: self.sequence = sequence jpayne@68: if quality is not None: jpayne@68: if len(sequence) != len(quality): jpayne@68: raise ValueError("sequence and quality length do not match: {} vs {}".format( jpayne@68: len(sequence), len(quality))) jpayne@68: jpayne@68: self.quality = quality jpayne@68: else: jpayne@68: self.quality = None jpayne@68: jpayne@68: def __str__(self): jpayne@68: return self.to_string() jpayne@68: jpayne@68: cpdef array.array get_quality_array(self, int offset=33): jpayne@68: '''return quality values as array after subtracting offset.''' jpayne@68: if self.quality is None: jpayne@68: return None jpayne@68: return qualitystring_to_array(force_bytes(self.quality), jpayne@68: offset=offset) jpayne@68: jpayne@68: jpayne@68: cdef class FastxFile: jpayne@68: r"""Stream access to :term:`fasta` or :term:`fastq` formatted files. jpayne@68: jpayne@68: The file is automatically opened. jpayne@68: jpayne@68: Entries in the file can be both fastq or fasta formatted or even a jpayne@68: mixture of the two. jpayne@68: jpayne@68: This file object permits iterating over all entries in the jpayne@68: file. Random access is not implemented. The iteration returns jpayne@68: objects of type :class:`FastqProxy` jpayne@68: jpayne@68: Parameters jpayne@68: ---------- jpayne@68: jpayne@68: filename : string jpayne@68: Filename of fasta/fastq file to be opened. jpayne@68: jpayne@68: persist : bool jpayne@68: jpayne@68: If True (default) make a copy of the entry in the file during jpayne@68: iteration. If set to False, no copy will be made. This will jpayne@68: permit much faster iteration, but an entry will not persist jpayne@68: when the iteration continues and an entry is read-only. jpayne@68: jpayne@68: Notes jpayne@68: ----- jpayne@68: Prior to version 0.8.2, this class was called FastqFile. jpayne@68: jpayne@68: Raises jpayne@68: ------ jpayne@68: jpayne@68: IOError jpayne@68: if file could not be opened jpayne@68: jpayne@68: jpayne@68: Examples jpayne@68: -------- jpayne@68: >>> with pysam.FastxFile(filename) as fh: jpayne@68: ... for entry in fh: jpayne@68: ... print(entry.name) jpayne@68: ... print(entry.sequence) jpayne@68: ... print(entry.comment) jpayne@68: ... print(entry.quality) jpayne@68: >>> with pysam.FastxFile(filename) as fin, open(out_filename, mode='w') as fout: jpayne@68: ... for entry in fin: jpayne@68: ... fout.write(str(entry) + '\n') jpayne@68: jpayne@68: """ jpayne@68: def __cinit__(self, *args, **kwargs): jpayne@68: # self.fastqfile = NULL jpayne@68: self._filename = None jpayne@68: self.entry = NULL jpayne@68: self._open(*args, **kwargs) jpayne@68: jpayne@68: def is_open(self): jpayne@68: '''return true if samfile has been opened.''' jpayne@68: return self.entry != NULL jpayne@68: jpayne@68: def _open(self, filename, persist=True): jpayne@68: '''open a fastq/fasta file in *filename* jpayne@68: jpayne@68: Paramentes jpayne@68: ---------- jpayne@68: jpayne@68: persist : bool jpayne@68: jpayne@68: if True return a copy of the underlying data (default jpayne@68: True). The copy will persist even if the iteration jpayne@68: on the file continues. jpayne@68: jpayne@68: ''' jpayne@68: if self.fastqfile != NULL: jpayne@68: self.close() jpayne@68: jpayne@68: self._filename = encode_filename(filename) jpayne@68: cdef char *cfilename = self._filename jpayne@68: self.is_remote = hisremote(cfilename) jpayne@68: jpayne@68: # open file for reading jpayne@68: if (self._filename != b"-" jpayne@68: and not self.is_remote jpayne@68: and not os.path.exists(filename)): jpayne@68: raise IOError("file `%s` not found" % filename) jpayne@68: jpayne@68: self.persist = persist jpayne@68: jpayne@68: with nogil: jpayne@68: self.fastqfile = bgzf_open(cfilename, "r") jpayne@68: self.entry = kseq_init(self.fastqfile) jpayne@68: self._filename = filename jpayne@68: jpayne@68: def close(self): jpayne@68: '''close the file.''' jpayne@68: if self.fastqfile != NULL: jpayne@68: bgzf_close(self.fastqfile) jpayne@68: self.fastqfile = NULL jpayne@68: if self.entry != NULL: jpayne@68: kseq_destroy(self.entry) jpayne@68: self.entry = NULL jpayne@68: jpayne@68: def __dealloc__(self): jpayne@68: if self.fastqfile != NULL: jpayne@68: bgzf_close(self.fastqfile) jpayne@68: if self.entry: jpayne@68: kseq_destroy(self.entry) jpayne@68: jpayne@68: # context manager interface jpayne@68: def __enter__(self): jpayne@68: return self jpayne@68: jpayne@68: def __exit__(self, exc_type, exc_value, traceback): jpayne@68: self.close() jpayne@68: return False jpayne@68: jpayne@68: property closed: jpayne@68: """bool indicating the current state of the file object. jpayne@68: This is a read-only attribute; the close() method changes the value. jpayne@68: """ jpayne@68: def __get__(self): jpayne@68: return not self.is_open() jpayne@68: jpayne@68: property filename: jpayne@68: """string with the filename associated with this object.""" jpayne@68: def __get__(self): jpayne@68: return self._filename jpayne@68: jpayne@68: def __iter__(self): jpayne@68: if not self.is_open(): jpayne@68: raise ValueError("I/O operation on closed file") jpayne@68: return self jpayne@68: jpayne@68: cdef kseq_t * getCurrent(self): jpayne@68: return self.entry jpayne@68: jpayne@68: cdef int cnext(self): jpayne@68: '''C version of iterator jpayne@68: ''' jpayne@68: with nogil: jpayne@68: return kseq_read(self.entry) jpayne@68: jpayne@68: def __next__(self): jpayne@68: """ jpayne@68: python version of next(). jpayne@68: """ jpayne@68: cdef int l jpayne@68: with nogil: jpayne@68: l = kseq_read(self.entry) jpayne@68: if (l >= 0): jpayne@68: if self.persist: jpayne@68: return FastxRecord(proxy=makeFastqProxy(self.entry)) jpayne@68: return makeFastqProxy(self.entry) jpayne@68: elif (l == -1): jpayne@68: raise StopIteration jpayne@68: elif (l == -2): jpayne@68: raise ValueError('truncated quality string in {0}' jpayne@68: .format(self._filename)) jpayne@68: else: jpayne@68: raise ValueError('unknown problem parsing {0}' jpayne@68: .format(self._filename)) jpayne@68: jpayne@68: # Compatibility Layer for pysam 0.8.1 jpayne@68: cdef class FastqFile(FastxFile): jpayne@68: """FastqFile is deprecated: use FastxFile instead""" jpayne@68: pass jpayne@68: jpayne@68: # Compatibility Layer for pysam < 0.8 jpayne@68: cdef class Fastafile(FastaFile): jpayne@68: """Fastafile is deprecated: use FastaFile instead""" jpayne@68: pass jpayne@68: jpayne@68: __all__ = ["FastaFile", jpayne@68: "FastqFile", jpayne@68: "FastxFile", jpayne@68: "Fastafile", jpayne@68: "FastxRecord", jpayne@68: "FastqProxy"]