From 415df2c1c176ec107cadf80fcc90d8e0b0a53a56 Mon Sep 17 00:00:00 2001 From: Nathan Roach Date: Tue, 27 Dec 2022 15:09:19 -0500 Subject: [PATCH] Adding initial batch of samtools type hinted wrapper functions --- fgpyo/pysam_dispatch/__init__.py | 5 + fgpyo/pysam_dispatch/samtools/__init__.py | 6 + fgpyo/pysam_dispatch/samtools/dict.py | 75 ++++ fgpyo/pysam_dispatch/samtools/faidx.py | 122 ++++++ fgpyo/pysam_dispatch/samtools/index.py | 72 ++++ fgpyo/pysam_dispatch/samtools/sort.py | 116 ++++++ .../samtools/tests/test_dict.py | 127 ++++++ .../samtools/tests/test_faidx.py | 387 ++++++++++++++++++ .../samtools/tests/test_index.py | 86 ++++ .../samtools/tests/test_sort.py | 79 ++++ fgpyo/sam/__init__.py | 9 +- fgpyo/sam/builder.py | 23 +- poetry.lock | 207 ++++++---- pyproject.toml | 1 + 14 files changed, 1221 insertions(+), 94 deletions(-) create mode 100644 fgpyo/pysam_dispatch/__init__.py create mode 100644 fgpyo/pysam_dispatch/samtools/__init__.py create mode 100644 fgpyo/pysam_dispatch/samtools/dict.py create mode 100644 fgpyo/pysam_dispatch/samtools/faidx.py create mode 100644 fgpyo/pysam_dispatch/samtools/index.py create mode 100644 fgpyo/pysam_dispatch/samtools/sort.py create mode 100644 fgpyo/pysam_dispatch/samtools/tests/test_dict.py create mode 100644 fgpyo/pysam_dispatch/samtools/tests/test_faidx.py create mode 100644 fgpyo/pysam_dispatch/samtools/tests/test_index.py create mode 100644 fgpyo/pysam_dispatch/samtools/tests/test_sort.py diff --git a/fgpyo/pysam_dispatch/__init__.py b/fgpyo/pysam_dispatch/__init__.py new file mode 100644 index 00000000..2eeade58 --- /dev/null +++ b/fgpyo/pysam_dispatch/__init__.py @@ -0,0 +1,5 @@ +def pysam_fn(*args: str) -> None: + """ + Type hinted import of an example function from the pysam samtools dispatcher. + """ + pass diff --git a/fgpyo/pysam_dispatch/samtools/__init__.py b/fgpyo/pysam_dispatch/samtools/__init__.py new file mode 100644 index 00000000..ceda1553 --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/__init__.py @@ -0,0 +1,6 @@ +from fgpyo.pysam_dispatch.samtools.dict import dict +from fgpyo.pysam_dispatch.samtools.faidx import faidx +from fgpyo.pysam_dispatch.samtools.index import index +from fgpyo.pysam_dispatch.samtools.sort import sort + +__all__ = ["dict", "faidx", "index", "sort"] diff --git a/fgpyo/pysam_dispatch/samtools/dict.py b/fgpyo/pysam_dispatch/samtools/dict.py new file mode 100644 index 00000000..c17259df --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/dict.py @@ -0,0 +1,75 @@ +""" +Type-Hinted Wrapper around pysam samtools dict dispatch +------------------------------------------------------- +""" + +from pathlib import Path +from typing import TYPE_CHECKING +from typing import List +from typing import Optional + +if TYPE_CHECKING: + from fgpyo.pysam_dispatch import pysam_fn as _pysam_dict +else: + from pysam import dict as _pysam_dict + + +def dict( + input: Path, + output: Path, + no_header: bool = False, + alias: bool = False, + assembly: Optional[str] = None, + alt: Optional[Path] = None, + species: Optional[str] = None, + uri: Optional[str] = None, +) -> List[str]: + """ + Calls the `samtools` function `dict` using the pysam samtools dispatcher. + + Arguments will be formatted into the appropriate command-line call which will be invoked + using the pysam dispatcher. + + Returns the list of arguments passed to pysam.dict() + + Args: + input: Path to the FASTA files to generate a sequence dictionary for. + output: Path to the file to write output to. + no_header: If true will not print the @HD header line. + assembly: The assembly for the AS tag. + alias: Adds an AN tag with the same value as the SN tag, except that a 'chr' prefix is + removed if SN has one or added if it does not. For mitochondria (i.e., when SN is “M” + or “MT”, with or without a “chr” prefix), also adds the remaining combinations of + “chr/M/MT” to the AN tag. + alt: Add an AH tag to each sequence listed in the specified bwa-style .alt file. These + files use SAM records to represent alternate locus sequences (as named in the QNAME + field) and their mappings to the primary assembly. + species: Specify the species for the SP tag. + uri: Specify the URI for the UR tag. Defaults to the absolute path of ref.fasta. + """ + + args: List[str] = ["--output", str(output)] + + if no_header: + args.append("--no-header") + + if alias: + args.append("--alias") + + if assembly is not None: + args.extend(["--assembly", assembly]) + + if alt is not None: + args.extend(["--alt", str(alt)]) + + if species is not None: + args.extend(["--species", species]) + + if uri is not None: + args.extend(["--uri", uri]) + + args.append(str(input)) + + _pysam_dict(*args) + + return args diff --git a/fgpyo/pysam_dispatch/samtools/faidx.py b/fgpyo/pysam_dispatch/samtools/faidx.py new file mode 100644 index 00000000..0b8b043c --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/faidx.py @@ -0,0 +1,122 @@ +""" +Type-Hinted Wrapper around pysam samtools faidx dispatch +-------------------------------------------------------- +""" + +import enum +from pathlib import Path +from typing import TYPE_CHECKING +from typing import List +from typing import Optional +from typing import Tuple + +if TYPE_CHECKING: + from fgpyo.pysam_dispatch import pysam_fn as _pysam_faidx +else: + from pysam import faidx as _pysam_faidx + + +@enum.unique +class FaidxMarkStrand(enum.Enum): + RevComp = "rc" + No = "no" + Sign = "sign" + Custom = "custom" + + +def faidx( + input: Path, + output: Optional[Path] = None, + length: int = 60, + regions: Optional[List[str]] = None, + region_file: Optional[Path] = None, + fai_idx: Optional[Path] = None, + gzi_idx: Optional[Path] = None, + continue_if_non_existent: bool = False, + fastq: bool = False, + reverse_complement: bool = False, + mark_strand: FaidxMarkStrand = FaidxMarkStrand.RevComp, + custom_mark_strand: Optional[Tuple[str, str]] = None, +) -> List[str]: + """ + Calls the `samtools` function `faidx` using the pysam samtools dispatcher. + + Arguments will be formatted into the appropriate command-line call which will be invoked + using the pysam dispatcher. + + Returns the list of arguments passed to pysam.faidx() + + Args: + input: Path to the FAIDX files to index / read from. + output: Path to the file to write FASTA output. + length: Length of FASTA sequence line. + regions: regions to extract from the FASTA file in samtools region format + (:<1-based start>-<1-based end>) + region_file: Path to file containing regions to extract from the FASTA file in samtools + region format (:<1-based start>-<1-based end>). 1 region descriptor per line. + fai_idx: Read/Write to specified FAI index file. + gzi_idx: Read/Write to specified compressed file index (used with .gz files). + continue_if_non_existent: If true continue qorking if a non-existent region is requested. + fastq: Read FASTQ files and output extracted sequences in FASTQ format. Same as using + samtools fqidx. + reverse_complement: Output the sequence as the reverse complement. When this option is + used, “/rc” will be appended to the sequence names. To turn this off or change the + string appended, use the mark_strand parameter. + mark_strand: Append strand indicator to sequence name. Type to append can be one of: + FaidxMarkStrand.RevComp - Append '/rc' when writing the reverse complement. This is the + default. + + FaidxMarkStrand.No - Do not append anything. + + FaidxMarkStrand.Sign - Append '(+)' for forward strand or '(-)' for reverse + complement. This matches the output of “bedtools getfasta -s”. + + FaidxMarkStrand.Custom - custom,, + Append string to names when writing the forward strand and when writing the + reverse strand. Spaces are preserved, so it is possible to move the indicator into the + comment part of the description line by including a leading space in the strings + and . + custom_mark_strand: The custom strand indicators to use in in the Custom MarkStrand + setting. The first value of the tuple will be used as the positive strand indicator, + the second value will be used as the negative strand indicator. + """ + + mark_strand_str: str + if mark_strand == FaidxMarkStrand.Custom: + assert custom_mark_strand is not None, ( + "Cannot use custom mark strand without providing the custom strand indicators to " + + "`custom_mark_string`" + ) + mark_strand_str = f"{mark_strand.value},{custom_mark_strand[0]},{custom_mark_strand[1]}" + else: + mark_strand_str = mark_strand.value + + args: List[str] = ["--length", str(length), "--mark-strand", mark_strand_str] + + if output is not None: + args.extend(["--output", str(output)]) + + if continue_if_non_existent: + args.append("--continue") + if reverse_complement: + args.append("--reverse-complement") + if fastq: + args.append("--fastq") + + if fai_idx is not None: + args.extend(["--fai-idx", str(fai_idx)]) + + if gzi_idx is not None: + args.extend(["--gzi-idx", str(gzi_idx)]) + + if region_file is not None: + args.extend(["--region-file", str(region_file)]) + + args.append(str(input)) + + if regions is not None: + args.extend(regions) + + _pysam_faidx(*args) + + return args diff --git a/fgpyo/pysam_dispatch/samtools/index.py b/fgpyo/pysam_dispatch/samtools/index.py new file mode 100644 index 00000000..79f1cbeb --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/index.py @@ -0,0 +1,72 @@ +""" +Type-Hinted Wrapper around pysam samtools index dispatch +-------------------------------------------------------- +""" + +import enum +from pathlib import Path +from typing import TYPE_CHECKING +from typing import List +from typing import Optional + +if TYPE_CHECKING: + from fgpyo.pysam_dispatch import pysam_fn as _pysam_index +else: + from pysam import index as _pysam_index + + +@enum.unique +class SamIndexType(enum.Enum): + BAI = "-b" + CSI = "-c" + + +def index( + input: Path, + # See https://github.com/pysam-developers/pysam/issues/1155 + # inputs: List[Path], Cant currently accept multiple + output: Optional[Path] = None, + threads: int = 0, + index_type: SamIndexType = SamIndexType.BAI, + csi_index_size: int = 14, +) -> List[str]: + """ + Calls the `samtools` function `index` using the pysam samtools dispatcher. + + Arguments will be formatted into the appropriate command-line call which will be invoked + using the pysam dispatcher. + + Returns the list of arguments passed to pysam.index() + + Args: + input: Path to the SAM/BAM/CRAM file to index. + output: Path to the file to write output. (Currently may only be used when exactly one + alignment file is being indexed.) + threads: Number of input/output compression threads to use in addition to main thread. + index_type: The type of index file to produce when indexing. + csi_index_size: Sets the minimum interval size of CSI indices to 2^INT. + """ + + # assert len(inputs) >= 1, "Must provide at least one input to samtools index." + + # if len(inputs) != 1: + # assert ( + # output is None + # ), "Output currently can only be used if there is exactly one input file being indexed" + # args = ["-M"] + # else: + # args = [] + args = [] + + if index_type != SamIndexType.BAI: + args.append(index_type.value) + + if index_type == SamIndexType.CSI: + args.extend(["-m", str(csi_index_size)]) + args.extend(["-@", str(threads)]) + args.append(str(input)) + if output is not None: + args.extend(["-o", str(output)]) + _pysam_index(*args) + + return args diff --git a/fgpyo/pysam_dispatch/samtools/sort.py b/fgpyo/pysam_dispatch/samtools/sort.py new file mode 100644 index 00000000..a70e6301 --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/sort.py @@ -0,0 +1,116 @@ +""" +Type-Hinted Wrapper around pysam samtools sort dispatch +------------------------------------------------------- +""" + +from pathlib import Path +from typing import TYPE_CHECKING +from typing import List +from typing import Optional + +from fgpyo.sam import SamFileType +from fgpyo.sam import SamOrder + +if TYPE_CHECKING: + from fgpyo.pysam_dispatch import pysam_fn as _pysam_sort +else: + from pysam import sort as _pysam_sort + + +def sort( + input: Path, + output: Path, + index_output: bool = True, + sort_unmapped_reads: bool = False, + kmer_size: int = 20, + compression_level: Optional[int] = None, + memory_per_thread: str = "768MB", + sort_order: SamOrder = SamOrder.Coordinate, + sort_tag: Optional[str] = None, + output_format: SamFileType = SamFileType.BAM, + tempfile_prefix: Optional[str] = None, + threads: int = 1, + no_pg: bool = False, +) -> List[str]: + """ + Calls the `samtools` function sort using the pysam samtools dispatcher. + + Arguments will be formatted into the appropriate command-line call which will be invoked + using the pysam dispatcher. + + Returns the list of arguments passed to pysam.sort() + + Args: + input: Path to the SAM/BAM/CRAM file to sort. + output: Path to the file to write output. + index_output: If true, creates an index for the output file. + sort_unmapped_reads: If true, sort unmapped reads by their sequence minimizer, reverse + complementing where appropriate. This has the effect of collating some similar data + together, improving the compressibility of the unmapped sequence. The minimiser kmer + size is adjusted using the ``kmer_size`` option. Note data compressed in this manner + may need to be name collated prior to conversion back to fastq. + + Mapped sequences are sorted by chromosome and position. + kmer_size: the kmer-size to be used if sorting unmapped reads. + compression_level: The compression level to be used in the final output file. + memory_per_thread: Approximately the maximum required memory per thread, specified either + in bytes or with a K, M, or G suffix. + + To prevent sort from creating a huge number of temporary files, it enforces a minimum + value of 1M for this setting. + sort_order: The sort order to use when sorting the file. + sort_tag: The tag to use to use to sort the SAM/BAM/CRAM records. Will be sorted by + this tag first, followed by position (or name, depending on ``sort_order`` + provided). + output_format: the output file format to write the results as. + By default, will try to select a format based on the ``output`` filename extension; + if no format can be deduced, bam is selected. + tempfile_prefix: The prefix to use for temporary files. Resulting files will be in + format PREFIX.nnnn.bam, or if the specified PREFIX is an existing directory, to + PREFIX/samtools.mmm.mmm.tmp.nnnn.bam, where mmm is unique to this invocation of the + sort command. + + By default, any temporary files are written alongside the output file, as + out.bam.tmp.nnnn.bam, or if output is to standard output, in the current directory + as samtools.mmm.mmm.tmp.nnnn.bam. + threads: The number of threads to use when sorting. By default, operation is + single-threaded. + no_pg: If true, will not add a @PG line to the header of the output file. + """ + + output_string = ( + f"{output}##idx##{output}{output_format.index_extension}" + if index_output and output_format.index_extension is not None + else str(output) + ) + + args = ["-m", memory_per_thread, "-O", output_format._name_, "-@", str(threads)] + + if sort_unmapped_reads: + args.extend(["-M", "-K", str(kmer_size)]) + + if compression_level is not None: + args.extend(["-I", str(compression_level)]) + + if sort_order == SamOrder.QueryName: + args.append("-n") + elif sort_order == SamOrder.TemplateCoordinate: + args.append("--template-coordinate") + else: + assert ( + sort_order == SamOrder.Coordinate + ), "Sort order to samtools sort cannot be Unknown or Unsorted" + + if sort_tag is not None: + args.extend(["-t", sort_tag]) + + if tempfile_prefix is not None: + args.extend(["-T", tempfile_prefix]) + + if no_pg: + args.append("--no-PG") + + args.extend(["-o", output_string, str(input)]) + _pysam_sort(*args) + + return args diff --git a/fgpyo/pysam_dispatch/samtools/tests/test_dict.py b/fgpyo/pysam_dispatch/samtools/tests/test_dict.py new file mode 100644 index 00000000..3ffb3a6c --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/tests/test_dict.py @@ -0,0 +1,127 @@ +from pathlib import Path + +import pytest + +import fgpyo.pysam_dispatch.samtools as samtools + +EXAMPLE_FASTA: str = """\ +>chr1 +GATTACATTTGAGAGA +>chr2 +CCCCTACCCACCC +>chr1_alt +GATTACATGAGAGA +>chr2_alt +CCCCTACCACCC +""" + +EXPECTED_DICT: str = """\ +@HD VN:1.0 SO:unsorted +@SQ SN:chr1 LN:16 M5:0b2bf1b29cf5338d75a8feb8c8a3784b UR:consistent_location +@SQ SN:chr2 LN:13 M5:87afe3395654b1fe1443b54490c47871 UR:consistent_location +@SQ SN:chr1_alt LN:14 M5:23ce480f016f77dfb29d3c17aa98f567 UR:consistent_location +@SQ SN:chr2_alt LN:12 M5:2ee663b21d249ebecaa9ffbdb6e0b970 UR:consistent_location +""" + +HEADERLESS_DICT: str = """\ +@SQ SN:chr1 LN:16 M5:0b2bf1b29cf5338d75a8feb8c8a3784b UR:consistent_location +@SQ SN:chr2 LN:13 M5:87afe3395654b1fe1443b54490c47871 UR:consistent_location +@SQ SN:chr1_alt LN:14 M5:23ce480f016f77dfb29d3c17aa98f567 UR:consistent_location +@SQ SN:chr2_alt LN:12 M5:2ee663b21d249ebecaa9ffbdb6e0b970 UR:consistent_location +""" + +OTHER_TAG_DICT: str = """\ +@HD VN:1.0 SO:unsorted +@SQ SN:chr1 LN:16 M5:0b2bf1b29cf5338d75a8feb8c8a3784b AN:1 UR:consistent_location AS:test1 SP:test3 +@SQ SN:chr2 LN:13 M5:87afe3395654b1fe1443b54490c47871 AN:2 UR:consistent_location AS:test1 SP:test3 +@SQ SN:chr1_alt LN:14 M5:23ce480f016f77dfb29d3c17aa98f567 AH:* AN:1_alt UR:consistent_location AS:test1 SP:test3 +@SQ SN:chr2_alt LN:12 M5:2ee663b21d249ebecaa9ffbdb6e0b970 AH:* AN:2_alt UR:consistent_location AS:test1 SP:test3 +""" # noqa: E501 + + +@pytest.fixture +def example_fasta(tmp_path: Path) -> Path: + outfile = tmp_path / "example.fa" + + with outfile.open("w") as out_fh: + out_fh.write(EXAMPLE_FASTA) + + return outfile + + +def test_dict_produces_sequence_dict( + tmp_path: Path, + example_fasta: Path, +) -> None: + # Make sure we're producing the index + output_access = tmp_path / "example.dict" + assert not output_access.exists() + samtools.dict(input=example_fasta, output=output_access, uri="consistent_location") + assert output_access.exists() + + output_contents: str + with output_access.open("r") as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == EXPECTED_DICT + + +def test_dict_no_header_works( + tmp_path: Path, + example_fasta: Path, +) -> None: + # Make sure we're producing the index + output_access = tmp_path / "example.dict" + assert not output_access.exists() + samtools.dict( + input=example_fasta, output=output_access, no_header=True, uri="consistent_location" + ) + assert output_access.exists() + + output_contents: str + with output_access.open("r") as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == HEADERLESS_DICT + + +ALT_FILE: str = """\ +chr1_alt 0 chr1 1 30 7M2D7M * 0 0 * * NM:i:2 +chr2_alt 0 chr2 1 30 6M1D6M * 0 0 * * NM:i:1 +""" + + +@pytest.fixture +def bwa_style_alt(tmp_path: Path) -> Path: + outfile = tmp_path / "example.alt" + + with outfile.open("w") as out_fh: + out_fh.write(ALT_FILE) + + return outfile + + +def test_dict_other_tags_work( + tmp_path: Path, + example_fasta: Path, + bwa_style_alt: Path, +) -> None: + # Make sure we're producing the index + output_access = tmp_path / "example.dict" + assert not output_access.exists() + samtools.dict( + input=example_fasta, + output=output_access, + alias=True, + assembly="test1", + alt=bwa_style_alt, + species="test3", + uri="consistent_location", + ) + assert output_access.exists() + + output_contents: str + with output_access.open("r") as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == OTHER_TAG_DICT diff --git a/fgpyo/pysam_dispatch/samtools/tests/test_faidx.py b/fgpyo/pysam_dispatch/samtools/tests/test_faidx.py new file mode 100644 index 00000000..f12e5196 --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/tests/test_faidx.py @@ -0,0 +1,387 @@ +from pathlib import Path + +import bgzip +import pytest +from pysam.utils import SamtoolsError + +import fgpyo.pysam_dispatch.samtools as samtools +from fgpyo.pysam_dispatch.samtools.faidx import FaidxMarkStrand + +EXAMPLE_FASTA: str = """\ +>chr1 +GATTACATTTGAGAGA +>chr2 +CCCCTACCCACCC +""" + +SUBSET_FASTA_TEMPLATE: str = """\ +>chr1:1-7{mark_strand} +GATTACA +>chr2:8-13{mark_strand} +CCACCC +""" +SUBSET_FASTA: str = SUBSET_FASTA_TEMPLATE.format(mark_strand="") + +WRAPPED_SUBSET_FASTA: str = """\ +>chr1:1-7 +GATTA +CA +>chr2:8-13 +CCACC +C +""" + + +@pytest.fixture +def example_fasta(tmp_path: Path) -> Path: + outfile = tmp_path / "example.fa" + + with outfile.open("w") as out_fh: + out_fh.write(EXAMPLE_FASTA) + + return outfile + + +def test_faidx_produces_functional_index( + tmp_path: Path, + example_fasta: Path, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx(input=example_fasta) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx(input=example_fasta, output=output_access, regions=["chr1:1-7", "chr2:8-13"]) + + output_contents: str + with output_access.open("r") as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == SUBSET_FASTA + + +def test_faidx_fails_if_non_existent_region_requested( + tmp_path: Path, + example_fasta: Path, +) -> None: + with pytest.raises(SamtoolsError): + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx(input=example_fasta) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx(input=example_fasta, output=output_access, regions=["chr3:1-4", "chr1:1-7"]) + + +def test_faidx_passes_if_non_existent_region_requested_when_continue_passed( + tmp_path: Path, + example_fasta: Path, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + assert not output_index_expected.exists() + samtools.faidx(input=example_fasta) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + samtools.faidx( + input=example_fasta, + output=output_access, + continue_if_non_existent=True, + regions=["chr3:1-4", "chr1:1-7"], + ) + + +def test_faidx_regions_and_regions_file_result_in_same_thing( + tmp_path: Path, + example_fasta: Path, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx(input=example_fasta) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + + regions = ["chr1:1-7", "chr2:8-13"] + + region_file = tmp_path / "regions.txt" + with region_file.open("w") as region_fh: + region_fh.writelines([f"{region}\n" for region in regions]) + + samtools.faidx(input=example_fasta, output=output_access, regions=regions) + + manually_passed_output_contents: str + with output_access.open("r") as subset_fasta: + manually_passed_output_contents = subset_fasta.read() + + samtools.faidx(input=example_fasta, output=output_access, region_file=region_file) + + file_passed_output_contents: str + with output_access.open("r") as subset_fasta: + file_passed_output_contents = subset_fasta.read() + + assert manually_passed_output_contents == file_passed_output_contents + + +def test_length_parameter( + tmp_path: Path, + example_fasta: Path, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx( + input=example_fasta, + ) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx( + input=example_fasta, + output=output_access, + regions=["chr1:1-7", "chr2:8-13"], + length=5, + ) + + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == WRAPPED_SUBSET_FASTA + + +RC_SUBSET_FASTA: str = """\ +>chr1:1-7{mark_strand} +TGTAATC +>chr2:8-13{mark_strand} +GGGTGG +""" + + +def test_rc_parameter( + tmp_path: Path, + example_fasta: Path, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx( + input=example_fasta, + ) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx( + input=example_fasta, + output=output_access, + reverse_complement=True, + regions=["chr1:1-7", "chr2:8-13"], + ) + + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == RC_SUBSET_FASTA.format(mark_strand="/rc") + + +@pytest.mark.parametrize( + argnames=["mark_strand", "expected_fwd_mark_strand", "expected_rev_mark_strand"], + argvalues=[ + (FaidxMarkStrand.RevComp, "", "/rc"), + (FaidxMarkStrand.No, "", ""), + (FaidxMarkStrand.Sign, "(+)", "(-)"), + (FaidxMarkStrand.Custom, "ex1a", "ex1b"), + (FaidxMarkStrand.Custom, "ex2a", "ex2b"), + ], + ids=["rev comp", "no", "sign", "custom1", "custom2"], +) +def test_mark_strand_parameters( + tmp_path: Path, + example_fasta: Path, + mark_strand: FaidxMarkStrand, + expected_fwd_mark_strand: str, + expected_rev_mark_strand: str, +) -> None: + output_index_expected = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx( + input=example_fasta, + ) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx( + input=example_fasta, + output=output_access, + reverse_complement=False, + mark_strand=mark_strand, + custom_mark_strand=(expected_fwd_mark_strand, expected_rev_mark_strand), + regions=["chr1:1-7", "chr2:8-13"], + ) + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == SUBSET_FASTA_TEMPLATE.format(mark_strand=expected_fwd_mark_strand) + + samtools.faidx( + input=example_fasta, + output=output_access, + reverse_complement=True, + mark_strand=mark_strand, + custom_mark_strand=(expected_fwd_mark_strand, expected_rev_mark_strand), + regions=["chr1:1-7", "chr2:8-13"], + ) + + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == RC_SUBSET_FASTA.format(mark_strand=expected_rev_mark_strand) + + +EXAMPLE_FASTQ: str = """\ +@chr1 +GATTACATTTGAGAGA ++ +;;;;;;;;;;;;;;;; +@chr2 +CCCCTACCCACCC ++ +;;;;;;;;;;;;; +""" + +SUBSET_FASTQ: str = """\ +@chr1:1-7 +GATTACA ++ +;;;;;;; +@chr2:8-13 +CCACCC ++ +;;;;;; +""" + + +@pytest.fixture +def example_fastq(tmp_path: Path) -> Path: + outfile = tmp_path / "example.fq" + + with outfile.open("w") as out_fh: + out_fh.write(EXAMPLE_FASTQ) + + return outfile + + +def test_fastq_parameter( + tmp_path: Path, + example_fastq: Path, +) -> None: + output_index_expected = Path(f"{example_fastq}.fai") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.faidx( + input=example_fastq, + fastq=True, + ) + assert output_index_expected.exists() + + output_access = tmp_path / "output_subset.fq" + # Make sure the index is functional + samtools.faidx( + input=example_fastq, + output=output_access, + regions=["chr1:1-7", "chr2:8-13"], + fastq=True, + ) + + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + + assert output_contents == SUBSET_FASTQ + + +@pytest.fixture +def example_fasta_gz(tmp_path: Path) -> Path: + outfile = tmp_path / "example.fa.gz" + + with outfile.open(mode="wb") as out_fh: + with bgzip.BGZipWriter(out_fh) as fh: + fh.write(bytes(EXAMPLE_FASTA, "Utf-8")) + + return outfile + + +def test_index_outputs( + tmp_path: Path, + example_fasta: Path, + example_fasta_gz: Path, +) -> None: + example_fai = Path(f"{example_fasta}.fai") + + # Make sure we're producing the index + assert not example_fai.exists() + samtools.faidx( + input=example_fasta, + fai_idx=example_fai, + ) + assert example_fai.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx( + input=example_fasta, + output=output_access, + regions=["chr1:1-7", "chr2:8-13"], + fai_idx=example_fai, + ) + + output_contents: str + with output_access.open() as subset_fasta: + output_contents = subset_fasta.read() + assert output_contents == SUBSET_FASTA + + example_gzi = Path(f"{example_fasta_gz}.gzi") + assert not example_gzi.exists() + samtools.faidx( + input=example_fasta_gz, + gzi_idx=example_gzi, + ) + assert example_gzi.exists() + + output_access = tmp_path / "output_subset.fa" + # Make sure the index is functional + samtools.faidx( + input=example_fasta, + output=output_access, + regions=["chr1:1-7", "chr2:8-13"], + gzi_idx=example_gzi, + ) + + compressed_output_contents: str + with output_access.open() as subset_fasta: + compressed_output_contents = subset_fasta.read() + + assert compressed_output_contents == SUBSET_FASTA diff --git a/fgpyo/pysam_dispatch/samtools/tests/test_index.py b/fgpyo/pysam_dispatch/samtools/tests/test_index.py new file mode 100644 index 00000000..b4e436cf --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/tests/test_index.py @@ -0,0 +1,86 @@ +from pathlib import Path + +import pytest + +import fgpyo.pysam_dispatch.samtools as samtools +from fgpyo.pysam_dispatch.samtools.index import SamIndexType +from fgpyo.sam import SamOrder +from fgpyo.sam.builder import SamBuilder + + +@pytest.mark.parametrize( + argnames=["index_type"], + argvalues=[ + (SamIndexType.BAI,), + (SamIndexType.CSI,), + ], + ids=["BAI", "CSI"], +) +def test_index_works_with_one_input( + tmp_path: Path, + index_type: SamIndexType, +) -> None: + builder = SamBuilder(sort_order=SamOrder.Coordinate) + builder.add_pair(name="test1", chrom="chr1", start1=4000, start2=4300) + builder.add_pair( + name="test2", chrom="chr1", start1=5000, start2=4700, strand1="-", strand2="+" + ) + builder.add_pair(name="test3", chrom="chr2", start1=4000, start2=4300) + builder.add_pair(name="test4", chrom="chr5", start1=4000, start2=4300) + + # At the moment sam builder doesnt support generating CRAM and SAM formats, so for now we're + # only testing on BAMs + input_file = tmp_path / "test_input.bam" + builder.to_path(input_file) + + output_index_expected = Path(f"{input_file}.{index_type._name_.lower()}") + + # Make sure we're producing the index + assert not output_index_expected.exists() + samtools.index(inputs=[input_file], index_type=index_type) + assert output_index_expected.exists() + + +# Can't accept multiple inputs at the moment. +# See https://github.com/pysam-developers/pysam/issues/1155 +# @pytest.mark.parametrize( +# argnames=["index_type"], +# argvalues=[ +# (SamIndexType.BAI,), +# (SamIndexType.CSI,), +# ], +# ids=["BAI", "CSI"], +# ) +# def test_index_works_with_multiple_inputs( +# tmp_path: Path, +# index_type: SamIndexType, +# ) -> None: +# builder = SamBuilder(sort_order=SamOrder.Coordinate) +# builder.add_pair(name="test1", chrom="chr1", start1=4000, start2=4300) +# builder.add_pair( +# name="test2", chrom="chr1", start1=5000, start2=4700, strand1="-", strand2="+" +# ) +# builder.add_pair(name="test3", chrom="chr2", start1=4000, start2=4300) +# builder.add_pair(name="test4", chrom="chr5", start1=4000, start2=4300) + +# # At the moment sam builder doesnt support generating CRAM and SAM formats, so for now we're +# # only testing on BAMs +# input_file1 = tmp_path / "test_input1.bam" +# builder.to_path(input_file1) +# input_file2 = tmp_path / "test_input2.bam" +# builder.to_path(input_file2) + +# inputs = [ +# input_file1, +# input_file2, +# ] + +# # Make sure we're producing the indices +# for input in inputs: +# output_index_expected = Path(f"{input}.{index_type._name_.lower()}") +# assert not output_index_expected.exists() + +# samtools.index(inputs=inputs, index_type=index_type) +# for input in inputs: +# output_index_expected = Path(f"{input}.{index_type._name_.lower()}") +# assert output_index_expected.exists() diff --git a/fgpyo/pysam_dispatch/samtools/tests/test_sort.py b/fgpyo/pysam_dispatch/samtools/tests/test_sort.py new file mode 100644 index 00000000..e277dd4b --- /dev/null +++ b/fgpyo/pysam_dispatch/samtools/tests/test_sort.py @@ -0,0 +1,79 @@ +from pathlib import Path +from typing import List +from typing import Optional + +import pytest + +import fgpyo.pysam_dispatch.samtools as samtools +from fgpyo import sam +from fgpyo.sam import SamFileType +from fgpyo.sam import SamOrder +from fgpyo.sam.builder import SamBuilder + + +@pytest.mark.parametrize( + argnames=["file_type"], + argvalues=[ + (SamFileType.SAM,), + (SamFileType.BAM,), + (SamFileType.CRAM,), + ], + ids=["SAM", "BAM", "CRAM"], +) +@pytest.mark.parametrize( + argnames=["index_output"], + argvalues=[ + (True,), + (False,), + ], + ids=["indexed", "not_indexed"], +) +@pytest.mark.parametrize( + argnames=["sort_order", "expected_name_order"], + argvalues=[ + (SamOrder.Coordinate, ["test2", "test3", "test4", "test1"]), + (SamOrder.QueryName, ["test1", "test2", "test3", "test4"]), + (SamOrder.TemplateCoordinate, ["test2", "test3", "test4", "test1"]), + ], + ids=["Coordinate sorting", "Query name sorting", "Template Sorted"], +) +def test_sort_types( + tmp_path: Path, + file_type: SamFileType, + index_output: bool, + sort_order: Optional[SamOrder], + expected_name_order: List[str], +) -> None: + + builder = SamBuilder(sort_order=SamOrder.Unsorted) + builder.add_pair( + name="test3", chrom="chr1", start1=5000, start2=4700, strand1="-", strand2="+" + ) + builder.add_pair(name="test2", chrom="chr1", start1=4000, start2=4300) + builder.add_pair(name="test1", chrom="chr5", start1=4000, start2=4300) + builder.add_pair(name="test4", chrom="chr2", start1=4000, start2=4300) + + input_file = tmp_path / "test_input.bam" + output_file = tmp_path / f"test_output{file_type.extension}" + + builder.to_path(input_file) + + samtools.sort( + input=input_file, + output=output_file, + index_output=index_output, + sort_order=sort_order, + ) + with sam.reader(output_file) as in_bam: + for name in expected_name_order: + read1 = next(in_bam) + assert ( + name == read1.query_name + ), "Position based read sort order did not match expectation" + read2 = next(in_bam) + assert ( + name == read2.query_name + ), "Position based read sort order did not match expectation" + + if index_output and file_type != SamFileType.SAM: + assert Path(f"{output_file}{file_type.index_extension}") diff --git a/fgpyo/sam/__init__.py b/fgpyo/sam/__init__.py index 8290916d..b68ce5e6 100644 --- a/fgpyo/sam/__init__.py +++ b/fgpyo/sam/__init__.py @@ -197,13 +197,14 @@ class SamFileType(enum.Enum): ext (str): The standard file extension for this file type. """ - def __init__(self, mode: str, ext: str) -> None: + def __init__(self, mode: str, ext: str, index_ext: Optional[str]) -> None: self.mode = mode self.extension = ext + self.index_extension = index_ext - SAM = ("", ".sam") - BAM = ("b", ".bam") - CRAM = ("c", ".cram") + SAM = ("", ".sam", None) + BAM = ("b", ".bam", ".bai") + CRAM = ("c", ".cram", ".crai") @classmethod def from_path(cls, path: Union[Path, str]) -> "SamFileType": diff --git a/fgpyo/sam/builder.py b/fgpyo/sam/builder.py index 6209763e..ef9e87f8 100755 --- a/fgpyo/sam/builder.py +++ b/fgpyo/sam/builder.py @@ -26,6 +26,7 @@ from pysam import AlignedSegment from pysam import AlignmentHeader +import fgpyo.pysam_dispatch.samtools as samtools from fgpyo import sam from fgpyo.sam import SamOrder @@ -544,7 +545,7 @@ def to_path( with NamedTemporaryFile(suffix=".bam", delete=True) as fp: file_handle: IO - if self.sort_order is SamOrder.Unsorted: + if self.sort_order in [SamOrder.Unsorted, SamOrder.Unknown]: file_handle = path.open("w") else: file_handle = fp.file @@ -555,20 +556,14 @@ def to_path( for rec in self._records: if pred(rec): writer.write(rec) - - default_samtools_opt_list = ["-o", str(path), fp.name] - file_handle.close() - if self.sort_order == SamOrder.QueryName: - # Ignore type hints for now until we have wrappers to use here. - pysam.sort(*(["-n"] + default_samtools_opt_list)) # type: ignore - elif self.sort_order == SamOrder.Coordinate: - # Ignore type hints for now until we have wrappers to use here. - pysam.sort(*default_samtools_opt_list) # type: ignore - if index: - # Ignore type hints for now until we have wrappers to use here. - pysam.index(str(path)) # type: ignore - + if self.sort_order not in [SamOrder.Unsorted, SamOrder.Unknown]: + samtools.sort( + input=Path(fp.name), + output=path, + index_output=index, + sort_order=self.sort_order, + ) return path def __len__(self) -> int: diff --git a/poetry.lock b/poetry.lock index 30722455..50755d93 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,6 +47,14 @@ python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" +[[package]] +name = "bgzip" +version = "0.4.0" +description = "Utilities working with blocked gzip streams." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "black" version = "20.8b1" @@ -58,7 +66,6 @@ python-versions = ">=3.6" [package.dependencies] appdirs = "*" click = ">=7.1.2" -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.6,<1" regex = ">=2020.1.8" @@ -111,26 +118,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.2" +version = "7.0.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] -[[package]] -name = "dataclasses" -version = "0.8" -description = "A backport of the dataclasses module for Python 3.6" -category = "dev" -optional = false -python-versions = ">=3.6, <3.7" - [[package]] name = "docutils" version = "0.17.1" @@ -607,8 +606,8 @@ docs = ["sphinx"] [metadata] lock-version = "1.1" -python-versions = ">=3.6.1,<4.0" -content-hash = "2b4e8a884aa17b49f71b3d692d7b9b45c1182d73803a9b28060e28cc0080c91c" +python-versions = ">=3.7.0,<4.0" +content-hash = "954260de2d0450920ea4e87211637fc9553c7fcf455c88e6b8e390f55d059c5c" [metadata.files] alabaster = [ @@ -619,12 +618,20 @@ appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] -atomicwrites = [] -attrs = [] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] babel = [ {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, ] +bgzip = [ + {file = "bgzip-0.4.0.tar.gz", hash = "sha256:2562202a99b0cff1777d051cd3ed7ffa9a568ff8486f922935808e1a0478f01c"}, +] black = [ {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] @@ -640,65 +647,71 @@ click = [ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] -colorama = [] -coverage = [ - {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, - {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, - {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, - {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, - {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, - {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, - {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, - {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, - {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, - {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, - {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, - {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, - {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, - {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, - {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, - {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, - {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, - {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, - {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, - {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +coverage = [ + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -flake8 = [] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, @@ -711,7 +724,10 @@ importlib-metadata = [ {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, ] -iniconfig = [] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, @@ -791,8 +807,35 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] -mccabe = [] -mypy = [] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +mypy = [ + {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, + {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, + {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, + {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, + {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, + {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, + {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, + {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, + {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, + {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, + {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, + {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, + {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, + {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, + {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, + {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, + {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, + {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, + {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, + {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, + {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, + {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, + {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, +] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, @@ -801,14 +844,26 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pathspec = [] -pluggy = [] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycodestyle = [] -pyflakes = [] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] pygments = [ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, diff --git a/pyproject.toml b/pyproject.toml index d86ba9f9..667926b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ pytest = ">=5.4.2" mypy = ">=0.770" flake8 = ">=3.8.1" black = ">=19.10b0" +bgzip = ">=0.4.0" pytest-cov = ">=2.8.1" isort = ">=5.10.1"