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..77c62802 100755
--- a/fgpyo/sam/builder.py
+++ b/fgpyo/sam/builder.py
@@ -27,6 +27,7 @@
 from pysam import AlignmentHeader
 
 from fgpyo import sam
+from fgpyo import samtools
 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/fgpyo/samtools.py b/fgpyo/samtools.py
new file mode 100644
index 00000000..189dbc5b
--- /dev/null
+++ b/fgpyo/samtools.py
@@ -0,0 +1,377 @@
+"""
+Type-Hinted Wrappers around pysam samtools dispatch functions.
+--------------------------------------------------------------
+
+Calls pysam samtools functions, with type hints for relevant parameters and return types.
+
+Examples
+~~~~~~~~
+.. code-block:: python
+
+   >>> from fgpyo import samtools
+   >>> Path("example1.sorted.bam.bai").exists()
+   False
+   >>> samtools.sort(input=Path("example1.bam"), output=Path("example1.sorted.bam"))
+   ''
+   >>> samtools.index(input=Path("example1.sorted.bam"))
+   ''
+   >>> Path("example1.sorted.bam.bai").exists()
+   True
+
+Module Contents
+~~~~~~~~~~~~~~~
+The module contains the following public method definitions:
+    - :class:`~fgpyo.samtools.dict` -- Wrapper around pysam call to samtools dict with type hints
+        on inputs and return type
+    - :class:`~fgpyo.samtools.sort` -- Wrapper around pysam call to samtools sort with type hints
+        on inputs and return type
+    - :class:`~fgpyo.samtools.faidx` -- Wrapper around pysam call to samtools faidx with type hints
+        on inputs and return type
+    - :class:`~fgpyo.samtools.index` -- Wrapper around pysam call to samtools index with type hints
+        on inputs and return type
+"""
+import enum
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import List
+from typing import Optional
+from typing import Tuple
+
+from fgpyo.sam import SamFileType
+from fgpyo.sam import SamOrder
+
+
+def pysam_fn(*args: str) -> str:
+    """
+    Type hinted import of an example function from the pysam samtools dispatcher.
+    """
+    pass
+
+
+if TYPE_CHECKING:
+    _pysam_dict = pysam_fn
+    _pysam_sort = pysam_fn
+    _pysam_faidx = pysam_fn
+    _pysam_index = pysam_fn
+else:
+    from pysam import dict as _pysam_dict
+    from pysam import faidx as _pysam_faidx
+    from pysam import index as _pysam_index
+    from pysam import sort as _pysam_sort
+
+
+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,
+) -> 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 stdout of the resulting call 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))
+
+    return _pysam_dict(*args)
+
+
+@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,
+) -> 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 stdout of the resulting call 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
+            (<chrom>:<1-based start>-<1-based end>)
+        region_file: Path to file containing regions to extract from the FASTA file in samtools
+            region format (<chrom>:<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,<custom_mark_strand[0]>,<custom_mark_strand[1]>
+            Append string <pos> to names when writing the forward strand and <neg> 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
+            <custom_mark_strand[0]> and <custom_mark_strand[1]>.
+        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)
+
+    return _pysam_faidx(*args)
+
+
+@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,
+) -> 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 stdout of the resulting call 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)])
+
+    return _pysam_index(*args)
+
+
+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,
+) -> 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 stdout of the resulting call 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)])
+
+    return _pysam_sort(*args)
diff --git a/fgpyo/tests/test_samtools.py b/fgpyo/tests/test_samtools.py
new file mode 100644
index 00000000..ee0f97a9
--- /dev/null
+++ b/fgpyo/tests/test_samtools.py
@@ -0,0 +1,664 @@
+from pathlib import Path
+from typing import List
+from typing import Optional
+
+import bgzip
+import pytest
+from pysam.utils import SamtoolsError
+
+from fgpyo import sam
+from fgpyo import samtools
+from fgpyo.sam import SamFileType
+from fgpyo.sam import SamOrder
+from fgpyo.sam.builder import SamBuilder
+from fgpyo.samtools import FaidxMarkStrand
+from fgpyo.samtools import SamIndexType
+
+EXAMPLE_DICT_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_dict_fasta(tmp_path: Path) -> Path:
+    outfile = tmp_path / "example.fa"
+
+    with outfile.open("w") as out_fh:
+        out_fh.write(EXAMPLE_DICT_FASTA)
+
+    return outfile
+
+
+def test_dict_produces_sequence_dict(
+    tmp_path: Path,
+    example_dict_fasta: Path,
+) -> None:
+    output_access = tmp_path / "example.dict"
+    assert not output_access.exists()
+    samtools.dict(input=example_dict_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_dict_fasta: Path,
+) -> None:
+    output_access = tmp_path / "example.dict"
+    assert not output_access.exists()
+    samtools.dict(
+        input=example_dict_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_dict_fasta: Path,
+    bwa_style_alt: Path,
+) -> None:
+    output_access = tmp_path / "example.dict"
+    assert not output_access.exists()
+    samtools.dict(
+        input=example_dict_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
+
+
+EXAMPLE_FAIDX_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_faidx_fasta(tmp_path: Path) -> Path:
+    outfile = tmp_path / "example.fa"
+
+    with outfile.open("w") as out_fh:
+        out_fh.write(EXAMPLE_FAIDX_FASTA)
+
+    return outfile
+
+
+def test_faidx_produces_functional_index(
+    tmp_path: Path,
+    example_faidx_fasta: Path,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not output_index_expected.exists()
+    samtools.faidx(input=example_faidx_fasta)
+    assert output_index_expected.exists()
+
+    output_access = tmp_path / "output_subset.fa"
+    # Make sure the index is functional
+    samtools.faidx(
+        input=example_faidx_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_faidx_fasta: Path,
+) -> None:
+    with pytest.raises(SamtoolsError):
+        output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+        # Make sure we're producing the index
+        assert not output_index_expected.exists()
+        samtools.faidx(input=example_faidx_fasta)
+        assert output_index_expected.exists()
+
+        output_access = tmp_path / "output_subset.fa"
+        # Make sure the index is functional
+        samtools.faidx(
+            input=example_faidx_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_faidx_fasta: Path,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    assert not output_index_expected.exists()
+    samtools.faidx(input=example_faidx_fasta)
+    assert output_index_expected.exists()
+
+    output_access = tmp_path / "output_subset.fa"
+    samtools.faidx(
+        input=example_faidx_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_faidx_fasta: Path,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not output_index_expected.exists()
+    samtools.faidx(input=example_faidx_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_faidx_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_faidx_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_faidx_fasta: Path,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not output_index_expected.exists()
+    samtools.faidx(
+        input=example_faidx_fasta,
+    )
+    assert output_index_expected.exists()
+
+    output_access = tmp_path / "output_subset.fa"
+    # Make sure the index is functional
+    samtools.faidx(
+        input=example_faidx_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_faidx_fasta: Path,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not output_index_expected.exists()
+    samtools.faidx(
+        input=example_faidx_fasta,
+    )
+    assert output_index_expected.exists()
+
+    output_access = tmp_path / "output_subset.fa"
+    # Make sure the index is functional
+    samtools.faidx(
+        input=example_faidx_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_faidx_fasta: Path,
+    mark_strand: FaidxMarkStrand,
+    expected_fwd_mark_strand: str,
+    expected_rev_mark_strand: str,
+) -> None:
+    output_index_expected = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not output_index_expected.exists()
+    samtools.faidx(
+        input=example_faidx_fasta,
+    )
+    assert output_index_expected.exists()
+
+    output_access = tmp_path / "output_subset.fa"
+    # Make sure the index is functional
+    samtools.faidx(
+        input=example_faidx_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_faidx_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"],
+    )
+
+    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_faidx_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_FAIDX_FASTA, "Utf-8"))
+
+    return outfile
+
+
+def test_index_outputs(
+    tmp_path: Path,
+    example_faidx_fasta: Path,
+    example_faidx_fasta_gz: Path,
+) -> None:
+    example_fai = Path(f"{example_faidx_fasta}.fai")
+
+    # Make sure we're producing the index
+    assert not example_fai.exists()
+    samtools.faidx(
+        input=example_faidx_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_faidx_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_faidx_fasta_gz}.gzi")
+    assert not example_gzi.exists()
+    samtools.faidx(
+        input=example_faidx_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_faidx_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
+
+
+@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)
+    samtools.index(input=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()
+
+
+@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/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"