summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2022-10-31 18:18:58 -0500
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2022-10-31 18:18:58 -0500
commita9ce975c28d4bcfa2f8a777cfaea2baa40f1703f (patch)
treebfeae2c5202effa4b4d7ed01ccdae4b2eb5e436c
downloadpyqidx-a9ce975c28d4bcfa2f8a777cfaea2baa40f1703f.tar.xz
pyqidx-a9ce975c28d4bcfa2f8a777cfaea2baa40f1703f.zip
Add inital wrapperHEADmain
-rw-r--r--pyqidx/__init__.py5
-rw-r--r--pyqidx/qidx.py108
-rw-r--r--pyqidx_build.py69
-rw-r--r--setup.py18
4 files changed, 200 insertions, 0 deletions
diff --git a/pyqidx/__init__.py b/pyqidx/__init__.py
new file mode 100644
index 0000000..3b515a1
--- /dev/null
+++ b/pyqidx/__init__.py
@@ -0,0 +1,5 @@
+from pyqidx.qidx import (
+ AlignmentRecord,
+ QidxException,
+ QueryIndexFile
+)
diff --git a/pyqidx/qidx.py b/pyqidx/qidx.py
new file mode 100644
index 0000000..95f5113
--- /dev/null
+++ b/pyqidx/qidx.py
@@ -0,0 +1,108 @@
+from pyqidx._qidx import ffi, lib
+
+from typing import (
+ NamedTuple,
+ Generator,
+ BinaryIO,
+ Union,
+ Optional
+)
+
+class _QidxError( object ):
+
+ code : int
+
+ def __init__( self, code : int ):
+ self.code = code
+
+ @staticmethod
+ def from_code( code ):
+ return _QidxError( code )
+
+ @property
+ def message( self ):
+ _msg = ffi.string( lib.qidx_strerr( self.code ) )
+ return str( _msg , encoding='UTF-8' )
+
+class QidxException( Exception ):
+
+ @staticmethod
+ def from_code( err_code ):
+ return QidxException( _QidxError( err_code ).message )
+
+class AlignmentRecord( NamedTuple ):
+ tid : int
+ pos : int
+ vptr : int
+
+class _Qidx( object ):
+
+ def __init__( self, fd : int ):
+ self._qidx = ffi.new('qidx_fp_t **')
+
+ err = lib.qidx_open( self._qidx, fd )
+
+ if err != lib.QIDX_OK:
+ raise QidxException.from_code( err )
+
+ def lookup_alignments( self, query_name : str ) \
+ -> Generator[AlignmentRecord, None, None]:
+ qname = ffi.new("char[]", bytes( query_name, encoding="UTF-8" ))
+
+ rec = ffi.new('qidx_record_t **')
+ err = lib.qidx_lookup_alnrec(self._qidx[0], qname, rec)
+
+ if not rec[0]:
+ return
+
+ for i in range(rec[0].n_alns):
+ aln = rec[0].alns[i]
+ yield AlignmentRecord( aln.tid, aln.pos, aln.vptr )
+
+ lib.qidx_free_alnrec( rec[0] )
+
+ def close( self ):
+ err = lib.qidx_close( self._qidx[0] )
+
+ if err != lib.QIDX_OK:
+ raise QidxException.from_code( err )
+
+class QueryIndexFile( object ):
+
+ _filename : Optional[ str ]
+ _file : BinaryIO
+ _qidx : _Qidx
+
+ def __init__( self, filename_or_file : Union[ BinaryIO, str ] ):
+
+ if isinstance( filename_or_file, str ):
+ self._filename = filename_or_file
+ self._file = None
+ else:
+ self._file = filename_or_file
+ self._filename = None
+
+ self._qidx = None
+
+ def open( self ):
+ if self._filename:
+ self._file = open( self._filename, 'rb' )
+
+ self._qidx = _Qidx( self._file.fileno() )
+
+ def close( self ):
+ self._qidx.close()
+
+ if self._filename:
+ self._file.close()
+
+ def __enter__( self ):
+ self.open()
+ return self
+
+ def __exit__( self, exc_type, exc_value, tb ):
+ self.close()
+
+ def lookup_alignments( self, query_name : str ) \
+ -> Generator[AlignmentRecord, None, None]:
+ return (yield from self._qidx.lookup_alignments( query_name ))
diff --git a/pyqidx_build.py b/pyqidx_build.py
new file mode 100644
index 0000000..1bf8e58
--- /dev/null
+++ b/pyqidx_build.py
@@ -0,0 +1,69 @@
+from cffi import FFI
+ffibuilder = FFI()
+
+ffibuilder.cdef("""
+ // @abstract Structure representing an unique alignment and virtual file pointer
+ // @field tid chromosome ID
+ // @field pos 0-based leftmost coordinate
+ // @field vptr virtual pointer
+ typedef struct {
+ uint32_t tid;
+ uint32_t pos;
+ uint64_t vptr;
+ } aln_spec_t;
+
+ // @abstract Structure representing all alignments by queryname
+ // @field qname queryname (name for the given read)
+ // @field n_alns number of alignments
+ // @field alns array with alignments structures
+ typedef struct {
+ char * qname;
+ uint16_t n_alns;
+ aln_spec_t * alns;
+ } qidx_record_t;
+
+ typedef enum {
+ QIDX_OK = 0,
+ QIDX_ITER_DONE,
+ QIDX_NO_MEM,
+ QIDX_NO_BAM_HDR,
+ QIDX_BAM_READ_FAILURE,
+ QIDX_REC_ITER_FAILURE,
+ QIDX_NOT_SORTED,
+ QIDX_BUCKET_MAX_SIZE_EXCEEDED,
+ QIDX_MAP_FAIL,
+ QIDX_IO_FAIL,
+ QIDX_FAILED_TO_OPEN_BAMFILE,
+ QIDX_FAILED_TO_OPEN_INDEX_FILE,
+ QIDX_INVALID_VERSION,
+ QIDX_INVALID_MAGIC,
+ } qidx_err_t;
+
+ bool qidx_errno(qidx_err_t err);
+
+ char const * qidx_strerr(qidx_err_t err);
+
+ struct qidx_fp;
+ typedef struct qidx_fp qidx_fp_t;
+
+ qidx_err_t qidx_open(qidx_fp_t ** fp, int fd);
+ qidx_err_t qidx_create(qidx_fp_t ** fp, int fd, uint32_t _n_buckets,
+ uint32_t max_bucket_size);
+ qidx_err_t qidx_lookup_alnrec(qidx_fp_t * fp,
+ char const * qname, qidx_record_t ** rec);
+ void qidx_free_alnrec(qidx_record_t * rec);
+ qidx_err_t qidx_close(qidx_fp_t * fp);
+""")
+
+ffibuilder.set_source(
+ "pyqidx._qidx",
+"""
+ #include <qidx.h> // the C header of the library
+""",
+ libraries=['qidx'],
+ extra_link_args=['-Wl,-rpath=/usr/local/lib'],
+) # library name, for the linker
+
+
+if __name__ == "__main__":
+ ffibuilder.compile(verbose=True)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..d12a6d8
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,18 @@
+from setuptools import setup
+
+import pyqidx_build as pyqidx_build
+
+setup(
+ name='pyqidx',
+ version='0.0.1',
+ author='Flu0r1ne',
+ author_email='flu0r1ne@flu0r1ne.net',
+ description='Python bindings for qidx',
+ url='https://git.flu0r1ne.net',
+ license='MIT',
+ setup_requires=["cffi>=1.0.0"],
+ cffi_modules=["pyqidx_build.py:ffibuilder"],
+ install_requires=["cffi>=1.0.0"],
+ packages=['pyqidx'],
+ ext_modules=[pyqidx_build.ffibuilder.distutils_extension()],
+)