[med-svn] [Git][med-team/cyvcf2][upstream] New upstream version 0.20.6

Andreas Tille gitlab at salsa.debian.org
Fri Sep 18 07:25:27 BST 2020



Andreas Tille pushed to branch upstream at Debian Med / cyvcf2


Commits:
df085d3c by Andreas Tille at 2020-09-18T08:19:49+02:00
New upstream version 0.20.6
- - - - -


7 changed files:

- README.md
- cyvcf2/__init__.py
- cyvcf2/cyvcf2.pyx
- cyvcf2/tests/test_reader.py
- + cyvcf2/tests/test_writer.py
- docs/source/index.rst
- + docs/source/writing.rst


Changes:

=====================================
README.md
=====================================
@@ -4,6 +4,8 @@ cyvcf2
 Note: cyvcf2 versions < 0.20.0 require htslib < 1.10. cyvcf2 versions >= 0.20.0 require htslib >= 1.10
 
 <!-- ghp-import -p docs/build/html/ -->
+The latest documentation for cyvcf2 can be found here:
+
 [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](http://brentp.github.io/cyvcf2/)
 
 If you use cyvcf2, please cite the [paper](https://academic.oup.com/bioinformatics/article/2971439/cyvcf2)
@@ -86,6 +88,7 @@ autoconf
 make
 
 cd ..
+pip install -r requirements.txt
 CYTHONIZE=1 pip install -e .
 ```
 


=====================================
cyvcf2/__init__.py
=====================================
@@ -2,4 +2,4 @@ from .cyvcf2 import (VCF, Variant, Writer, r_ as r_unphased, par_relatedness,
                      par_het)
 Reader = VCFReader = VCF
 
-__version__ = "0.20.1"
+__version__ = "0.20.6"


=====================================
cyvcf2/cyvcf2.pyx
=====================================
@@ -1,4 +1,5 @@
 #cython: profile=False
+#cython:language_level=3
 
 #cython: embedsignature=True
 from __future__ import print_function
@@ -154,27 +155,19 @@ cdef class HTSFile:
         cdef hFILE *hf
         self.mode = to_bytes(mode)
         reading = b"r" in self.mode
-        if not reading and b"w" not in self.mode:
+        writing = b"w" in self.mode
+        if not reading and not writing:
             raise IOError("No 'r' or 'w' in mode %s" % str(self.mode))
-        self.from_path = False
-        # for htslib, wbu seems to not work
-        if mode == b"wbu":
-            mode = to_bytes(b"wb0")
-        if isinstance(fname, (basestring, Path)):
-            self.from_path = True
+
+        self.from_path = isinstance(fname, (basestring, Path))
+        if self.from_path:
             self.fname = to_bytes(str(fname))
             if self.fname == b"-":
                 self.fname = to_bytes(b"/dev/stdin") if reading else to_bytes(b"/dev/stdout")
-            if self.fname.endswith(b".gz") and self.mode == b"w":
-                self.mode = b"wz"
-            elif self.fname.endswith((b".bcf", b".bcf.gz")) and self.mode == b"w":
-                self.mode = b"wb"
-            self.fname = to_bytes(str(fname))
-            self.mode = to_bytes(mode)
+
             self.hts = hts_open(self.fname, self.mode)
         # from a file descriptor
         elif isinstance(fname, int):
-            self.mode = to_bytes(mode)
             hf = hdopen(int(fname), self.mode)
             self.hts = hts_hopen(hf, "<file>", self.mode)
             self.fname = None
@@ -182,7 +175,6 @@ cdef class HTSFile:
         elif hasattr(fname, "fileno"):
             if fname.closed:
                 raise IOError('I/O operation on closed file')
-            self.mode = to_bytes(mode)
             hf = hdopen(fname.fileno(), self.mode)
             self.hts = hts_hopen(hf, "<file>", self.mode)
             # .name can be TextIOWrapper
@@ -304,6 +296,9 @@ cdef class VCF(HTSFile):
         ret = bcf_hdr_sync(self.hdr)
         if ret != 0:
             raise Exception("couldn't add '%s' to header")
+        if line.startswith("##contig"):
+            # need to trigger a refresh of seqnames
+            self._seqnames = []
         return ret
 
     def add_info_to_header(self, adict):
@@ -382,6 +377,11 @@ cdef class VCF(HTSFile):
             raise Exception("unable to update to header")
 
     def set_index(self, index_path=""):
+        if index_path.endswith(".tbi"):
+            self.idx = tbx_index_load2(to_bytes(self.fname), to_bytes(index_path))
+            if self.idx != NULL:
+                return
+
         self.hidx = hts_idx_load2(to_bytes(self.fname), to_bytes(index_path))
         if self.hidx == NULL:
             self.idx = tbx_index_load2(to_bytes(self.fname), to_bytes(index_path))
@@ -579,11 +579,15 @@ cdef class VCF(HTSFile):
         with nogil:
             b = bcf_init()
             ret = bcf_read(self.hts, self.hdr, b)
-        if ret >= 0:
+        if ret >= 0 or b.errcode == BCF_ERR_CTG_UNDEF:
             return newVariant(b, self)
         else:
             bcf_destroy(b)
-        raise StopIteration
+        if  ret == -1:  # end-of-file
+            raise StopIteration
+        else:  
+            raise Exception("error parsing variant with `htslib::bcf_read` error-code: %d" % (b.errcode))
+
 
     property samples:
         "list of samples pulled from the VCF."
@@ -1026,9 +1030,8 @@ cdef class Genotypes(object):
         The last column indicates phased (1 is phased, 0 is unphased).
         The other columns indicate the alleles, e.g. [0, 1, 1] is 0|1.
         """
-        cdef np.int16_t* to_return = <np.int16_t *>stdlib.malloc(sizeof(np.int16_t)
-                                                   * self.n_samples
-                                                   * (self.ploidy+1))
+        cdef np.ndarray[np.int16_t, ndim=2] to_return = np.zeros((self.n_samples, self.ploidy + 1),
+                dtype=np.int16)
 
         cdef int ind
         cdef int allele
@@ -1036,18 +1039,10 @@ cdef class Genotypes(object):
 
         for ind in range(self.n_samples):
             for allele in range(self.ploidy):
-                to_return[ind * p + allele] = (self._raw[ind * self.ploidy + allele] >> 1) - 1
-            to_return[ind * p + self.ploidy] = (self._raw[ind * self.ploidy + 1] & 1) == 1
+                to_return[ind, allele] = (self._raw[ind * self.ploidy + allele] >> 1) - 1
+            to_return[ind, self.ploidy] = (self._raw[ind * self.ploidy + 1] & 1) == 1
 
-        cdef np.npy_intp shape[2]
-        shape[0] = self.n_samples
-        shape[1] = self.ploidy + 1
-        return np.PyArray_SimpleNewFromData(
-            2,
-            shape,
-            np.NPY_INT16,
-            to_return
-        )
+        return to_return
 
     def __getitem__(self, int i):
         ## return the Allele objects for the i'th sample.
@@ -1434,20 +1429,32 @@ cdef class Variant(object):
 
         cdef np.ndarray[np.float32_t, mode="c"] afloat
         cdef np.ndarray[np.int32_t, mode="c"] aint
+        cdef char *bytesp
 
-        cdef int size = data.shape[0]
-        if len((<object>data).shape) > 1:
-            size *= data.shape[1]
-
+        cdef int size
         cdef int ret
         if np.issubdtype(data.dtype, np.signedinteger) or np.issubdtype(data.dtype, np.unsignedinteger):
+            size = data.shape[0]
+            if len((<object>data).shape) > 1:
+                size *= data.shape[1]
             aint = data.astype(np.int32).reshape((size,))
             ret = bcf_update_format_int32(self.vcf.hdr, self.b, to_bytes(name), &aint[0], size)
         elif np.issubdtype(data.dtype, np.floating):
+            size = data.shape[0]
+            if len((<object>data).shape) > 1:
+                size *= data.shape[1]
             afloat = data.astype(np.float32).reshape((size,))
             ret = bcf_update_format_float(self.vcf.hdr, self.b, to_bytes(name), &afloat[0], size)
+        elif np.issubdtype(data.dtype, np.bytes_):
+            if len((<object>data).shape) > 1:
+                raise Exception("Setting string type format fields with number>1 are currently not supported")
+            if not data.flags['C_CONTIGUOUS']:
+                data = np.ascontiguousarray(data)
+            size = data.nbytes
+            bytesp = <char *>data.data
+            ret = bcf_update_format(self.vcf.hdr, self.b, to_bytes(name), bytesp, size, BCF_HT_STR)
         else:
-            raise Exception("format: currently only float and int numpy arrays are supported. got %s", data.dtype)
+            raise Exception("format: currently only float, int and string (fixed length ASCII np.bytes_) numpy arrays are supported. got %s", data.dtype)
         if ret < 0:
             raise Exception("error (%d) setting format with: %s" % (ret, data[:100]))
 
@@ -1845,10 +1852,22 @@ cdef class Variant(object):
             return self.INFO.get(b'SVTYPE') is not None
 
     property CHROM:
-        "chromosome of the variant."
+        """Chromosome of the variant."""
         def __get__(self):
             return bcf_hdr_id2name(self.vcf.hdr, self.b.rid).decode()
 
+        def __set__(self, new_chrom):
+            new_rid = bcf_hdr_id2int(self.vcf.hdr, BCF_DT_CTG, new_chrom.encode())
+            if new_rid < 0:
+                self.vcf.add_to_header("##contig=<ID={}>".format(new_chrom))
+                new_rid = bcf_hdr_id2int(self.vcf.hdr, BCF_DT_CTG, new_chrom.encode())
+                if new_rid < 0:
+                    raise ValueError("Unable to add {} to CHROM".format(new_chrom))
+                sys.stderr.write(
+                    "[cyvcf2]: added new contig {} to header".format(new_chrom)
+                )
+            self.b.rid = new_rid
+
     property var_type:
         "type of variant (snp/indel/sv)"
         def __get__(self):
@@ -1913,7 +1932,7 @@ cdef class Variant(object):
     property FILTER:
         """the value of FILTER from the VCF field.
 
-        a value of PASS in the VCF will give None for this function
+        a value of PASS or '.' in the VCF will give None for this function
         """
         def __get__(self):
             cdef int i
@@ -2047,7 +2066,8 @@ cdef class INFO(object):
 
             ret = bcf_update_info_flag(self.hdr, self.b, to_bytes(key), b"", int(value))
             if ret != 0:
-                raise Exception("not able to set flag", key, value, ret)
+                raise Exception("not able to set: %s -> %s (%d)" % (key, value, ret))
+
             return
         cdef int32_t iint
         cdef float ifloat
@@ -2225,6 +2245,22 @@ cdef class Writer(VCF):
         path to file
     tmpl: VCF
         a template to use to create the output header.
+    mode: str
+        | Mode to use for writing the file. If ``None`` (default) is given, the mode is
+          inferred from the filename extension. If stdout (``"-"``) is provided for ``fname``
+          and ``mode`` is left at default, uncompressed VCF will be produced.
+        | Valid values are:
+        |  - ``"wbu"``: uncompressed BCF
+        |  - ``"wb"``: compressed BCF
+        |  - ``"wz"``: compressed VCF
+        |  - ``"w"``: uncompressed VCF
+        | Compression level can also be indicated by adding a single integer to one of
+          the compressed modes (e.g. ``"wz4"`` for VCF with compressions level 4).
+
+    Note
+    ----
+    File extensions ``.bcf`` and ``.bcf.gz`` will both return compressed BCF. If you
+    want uncompressed BCF you must explicitly provide the appropriate ``mode``.
 
     Returns
     -------
@@ -2236,7 +2272,8 @@ cdef class Writer(VCF):
     cdef bint header_written
     cdef const bcf_hdr_t *ohdr
 
-    def __init__(Writer self, fname, VCF tmpl, mode="w"):
+    def __init__(Writer self, fname, VCF tmpl, mode=None):
+        mode = self._infer_file_mode(fname, mode)
         self._open_htsfile(fname, mode)
         bcf_hdr_sync(tmpl.hdr)
         self.ohdr = tmpl.hdr
@@ -2244,6 +2281,28 @@ cdef class Writer(VCF):
         bcf_hdr_sync(self.hdr)
         self.header_written = False
 
+    @staticmethod
+    def _infer_file_mode(fname, mode=None):
+        if mode is not None:
+            return mode
+
+        from_path = isinstance(fname, (basestring, Path))
+        if not from_path:
+            return "w"
+
+        fname = str(fname)
+        is_compressed = fname.endswith(".gz")
+        fmt_idx = -2 if is_compressed else -1
+        file_fmt = fname.split(".")[fmt_idx]
+        # bcftools output write mode chars - https://github.com/samtools/bcftools/blob/76392b3014de70b7fa5c6b5c9d5bc47361951770/version.c#L64-L70
+        inferred_mode = "w"
+        if file_fmt == "bcf":
+            inferred_mode += "b"
+        if is_compressed and file_fmt == "vcf":
+            inferred_mode += "z"
+
+        return inferred_mode
+
     @classmethod
     def from_string(Writer cls, fname, header_string, mode="w"):
         cdef Writer self = Writer.__new__(Writer)


=====================================
cyvcf2/tests/test_reader.py
=====================================
@@ -67,18 +67,6 @@ def test_format_str():
     f = next(vcf).format("RULE")
     assert list(f) == ['F2,F3,F4', 'G2,G3,G4']
 
-"""
-def test_write_format_str():
-    vcf = VCF(os.path.join(HERE, "test-format-string.vcf"))
-    wtr = Writer("x.vcf", vcf)
-    variant = next(vcf)
-    variant.set_format("TSTRING", np.array(["asdfxx","35\0"]))
-    wtr.write_record(variant)
-    wtr.close()
-    assert "asdfxx" in str(variant), str(variant)
-    assert "35" in str(variant)
-    """
-
 def test_missing_samples():
     samples = ['101976-101976', 'sample_not_in_vcf']
     vcf = VCF(VCF_PATH, gts012=True, samples=samples)
@@ -669,6 +657,31 @@ def test_set_format_int3():
 
     assert np.allclose(v.format("P3"), exp)
 
+def test_set_format_str_bytes_second_longer():
+    vcf = VCF('{}/test-format-string.vcf'.format(HERE))
+    assert vcf.add_format_to_header(dict(ID="STR", Number=1, Type="String", Description="String example")) == 0
+    v = next(vcf)
+
+    v.set_format("STR", np.array([b'foo', b'barbaz']))
+    assert np.all(v.format('STR') == np.array(['foo', 'barbaz']))
+
+def test_set_format_str_bytes_first_longer():
+    vcf = VCF('{}/test-format-string.vcf'.format(HERE))
+    assert vcf.add_format_to_header(dict(ID="STR", Number=1, Type="String", Description="String example")) == 0
+    v = next(vcf)
+
+    v.set_format("STR", np.array([b'foobar', b'baz']))
+    assert np.all(v.format('STR') == np.array(['foobar', 'baz']))
+
+def test_set_format_str_bytes_number3():
+    # Confirm currently not supported
+    vcf = VCF('{}/test-format-string.vcf'.format(HERE))
+    assert vcf.add_format_to_header(dict(ID="STR", Number=3, Type="String", Description="String example")) == 0
+    v = next(vcf)
+
+    contents = np.array([[b'foo', b'barbaz', b'biz'], [b'blub', b'bloop', b'blop']])
+    assert_raises(Exception, v.set_format, "STR", contents)
+
 def test_set_gts():
     vcf = VCF('{}/test-format-string.vcf'.format(HERE))
     v = next(vcf)
@@ -881,6 +894,32 @@ def test_set_pos():
     assert v.start == 22
     assert v.POS == 23
 
+def test_set_chrom_when_contig_not_in_header():
+    test_vcf = '{}/test-strict-gt-option-flag.vcf.gz'.format(HERE)
+    new_chrom = "NEW"
+    vcf = VCF(test_vcf, gts012=False)
+    original_seqnames = vcf.seqnames
+    assert new_chrom not in original_seqnames
+    v = next(vcf)
+
+    v.CHROM = new_chrom
+    assert v.CHROM == new_chrom
+    expected_seqnames = sorted(original_seqnames + [new_chrom])
+    assert vcf.seqnames == expected_seqnames
+
+def test_set_chrom_after_contig_is_added_to_header():
+    test_vcf = '{}/test-strict-gt-option-flag.vcf.gz'.format(HERE)
+    new_chrom = "NEW"
+    vcf = VCF(test_vcf, gts012=False)
+    original_seqnames = vcf.seqnames
+    vcf.add_to_header("##contig=<ID={},length=15>".format(new_chrom))
+    expected_seqnames = sorted(original_seqnames + [new_chrom])
+    assert vcf.seqnames == expected_seqnames
+    v = next(vcf)
+
+    v.CHROM = new_chrom
+    assert v.CHROM == new_chrom
+
 def test_set_qual():
     v = VCF(VCF_PATH)
     variant = next(v)


=====================================
cyvcf2/tests/test_writer.py
=====================================
@@ -0,0 +1,109 @@
+from io import StringIO
+
+from ..cyvcf2 import Writer
+
+try:
+    from pathlib import Path
+except ImportError:
+    from pathlib2 import Path  # python 2 backport
+
+
+class TestFileModeInference:
+    def test_defaultModeWithVcfFname_returnsUncompressedVcf(self):
+        fname = "test.vcf"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_defaultModeWithBcfFname_returnsCompressedBcf(self):
+        fname = "test.bcf"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "wb"
+
+        assert actual == expected
+
+    def test_defaultModeWithBcfFnameAndUncompressedMode_returnsUncompressedBcf(self):
+        fname = "test.bcf"
+        mode = "wbu"
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = mode
+
+        assert actual == expected
+
+    def test_defaultModeWithCompressedBcfFname_returnsCompressedBcf(self):
+        fname = "test.bcf.gz"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "wb"
+
+        assert actual == expected
+
+    def test_defaultModeWithCompressedVcfFname_returnsCompressedVcf(self):
+        fname = "test.vcf.gz"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "wz"
+
+        assert actual == expected
+
+    def test_defaultModeWithStdOut_returnsUncompressedVcf(self):
+        fname = "-"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_defaultModeWithNonVcfName_returnsUncompressedVcf(self):
+        fname = "foo"
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_defaultModeWithIntFname_returnsUncompressedVcf(self):
+        fname = 1
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_defaultModeWithHandle_returnsUncompressedVcf(self):
+        fname = StringIO()
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_defaultModeWithPosixPath_returnsUncompressedVcf(self):
+        fname = Path("test.vcf")
+        mode = None
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "w"
+
+        assert actual == expected
+
+    def test_explicitMode_doesNotInferFromFname(self):
+        fname = "test.vcf.gz"
+        mode = "wb4"
+
+        actual = Writer._infer_file_mode(fname, mode)
+        expected = "wb4"
+
+        assert actual == expected


=====================================
docs/source/index.rst
=====================================
@@ -59,10 +59,12 @@ See the :ref:`api` for detailed documentation, but the most common usage is summ
 Modifying Existing Records
 ==========================
 
+.. this example is also in the writing.rst page
+
 `cyvcf2` is optimized for fast reading and extraction from existing files.
 However, it also offers some means of modifying existing VCFs. Here, we
-show an example of how to annotate variants with the genes that they overlap.
-
+show an example of how to modify the INFO field at a locus to annotate
+variants with the genes that they overlap.
 
 .. code-block:: python
 
@@ -88,6 +90,8 @@ show an example of how to annotate variants with the genes that they overlap.
 
     w.close(); vcf.close()
 
+More info on writing vcfs can be found :doc:`here <writing>`
+
 Setting Genotyping Strictness
 =============================
 
@@ -133,13 +137,20 @@ Tests can be run with:
 
     python setup.py test
 
+Known Limitations
+=================
+* `cyvcf2` currently does not support reading VCFs encoded with UTF-8 with non-ASCII characters in the contents of string-typed FORMAT fields.
+
+For limitations on writing VCFs, see :ref:`here <Limitations with writing>`
+
 See Also
 ========
 
-Pysam also [has a cython wrapper to htslib](https://github.com/pysam-developers/pysam/blob/master/pysam/cbcf.pyx) and one block of code here is taken directly from that library. But, the optimizations that we want for gemini are very specific so we have chosen to create a separate project.
+Pysam also `has a cython wrapper to htslib <https://github.com/pysam-developers/pysam/blob/master/pysam/cbcf.pyx>`_ and one block of code here is taken directly from that library. But, the optimizations that we want for gemini are very specific so we have chosen to create a separate project.
 
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: 1
 
    docstrings
+   writing
 


=====================================
docs/source/writing.rst
=====================================
@@ -0,0 +1,93 @@
+Modifying Existing VCFs
+=======================
+
+`cyvcf2` is optimized for fast reading and extraction from existing files.
+However, it also offers some means of modifying existing VCFs.
+
+Modifying the INFO field
+------------------------
+
+.. this is the same example as on the index.rst page
+
+Here, we show an example of how to modify the INFO field at a locus to annotate
+variants with the genes that they overlap.
+
+.. code-block:: python
+
+    from cyvcf2 import VCF, Writer
+    vcf = VCF(VCF_PATH)
+    # adjust the header to contain the new field
+    # the keys 'ID', 'Description', 'Type', and 'Number' are required.
+    vcf.add_info_to_header({'ID': 'gene', 'Description': 'overlapping gene',
+        'Type':'Character', 'Number': '1'})
+
+    # create a new vcf Writer using the input vcf as a template.
+    fname = "out.vcf"
+    w = Writer(fname, vcf)
+
+    for v in vcf:
+        # The get_gene_intersections function is not shown.
+        # This could be any operation to find intersections
+        # or any manipulation required by the user.
+        genes = get_gene_intersections(v)
+        if genes is not None:
+            v.INFO["gene"] = ",".join(genes)
+        w.write_record(v)
+
+    w.close(); vcf.close()
+
+Modifying genotypes and the FORMAT field
+----------------------------------------
+
+Here is an example where we filter some calls from records in a VCF. This demonstrates
+how to add to the FORMAT field and how to modify genotypes at a locus.
+
+.. code-block:: python
+
+    from cyvcf2 import VCF, Writer
+    vcf = VCF(VCF_PATH)
+    # adjust the header to contain the new field
+    # the keys 'ID', 'Description', 'Type', and 'Number' are required.
+    vcf.add_format_to_header({
+        'ID': 'FILTER_CODE',
+        'Description': 'Numeric code for filtering reason',
+        'Type': 'Integer',
+        'Number': '1'
+    })
+
+    # create a new vcf Writer using the input vcf as a template.
+    fname = "out.vcf"
+    w = Writer(fname, vcf)
+
+    for v in vcf:
+        # The filter_samples function is not shown.
+        # This could be any manipulation required by the user.
+        # Since we specified the FILTER_CODE format field is an Integer
+        # with Number=1, reasons must be an n x 1 numpy array of integers
+        # where n is the number of samples.
+        indicies, reasons = filter_samples(v)
+        if indicies:
+            # add the reasons array to the format dictionary at this locus
+            v.set_format('FILTER_CODE', reasons)
+            for index in indicies:
+                # overwrite the genotypes of each filtered locus to be nocalls
+                # Note: until the reassignment to v.genotypes below, this
+                # leaves the v Variant object in an inconsistent state
+                v.genotypes[index] = [-1]*v.ploidy + [False]
+            # it is necessary to reassign the genotypes field
+            # so that the v Variant object reprocess it and future calls
+            # to functions like v.genotype.array() properly reflect
+            # the changed genotypes
+            v.genotypes = v.genotypes
+
+        w.write_record(v)
+
+    w.close(); vcf.close()
+
+.. _Limitations with writing:
+
+Known Limitations with Writing VCFs
+-----------------------------------
+* `cyvcf2` currently does not support writing VCFs encoded with UTF-8 with non-ASCII characters in the contents of string-typed FORMAT fields.
+* `cyvcf2` currently does not support writing string type format fields with number>1.
+



View it on GitLab: https://salsa.debian.org/med-team/cyvcf2/-/commit/df085d3c1b77df9c27e5ace4c1ad2c2bda066efc

-- 
View it on GitLab: https://salsa.debian.org/med-team/cyvcf2/-/commit/df085d3c1b77df9c27e5ace4c1ad2c2bda066efc
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20200918/b1108f90/attachment-0001.html>


More information about the debian-med-commit mailing list