From f8dfaa2658df0e7d4c9b41ef3ce9abb79edfb7ea Mon Sep 17 00:00:00 2001 From: Jerome Kelleher Date: Tue, 3 Dec 2024 13:49:09 +0000 Subject: [PATCH] Revert "Drop 3.9" #95 This reverts 3 commits in PR #95 --- .github/workflows/ci.yml | 4 +++- pyproject.toml | 7 ++++--- tests/test_regions.py | 4 +++- tests/test_vcf_writer.py | 6 +++--- vcztools/filter.py | 2 +- vcztools/query.py | 33 +++++++++++++++------------------ vcztools/regions.py | 27 ++++++++++++++------------- vcztools/stats.py | 2 +- vcztools/utils.py | 2 +- vcztools/vcf_writer.py | 5 +++-- 10 files changed, 48 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2830346..e6683f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: # Use macos-13 because pip binary packages for ARM aren't # available for many dependencies os: [macos-13, macos-14, ubuntu-latest] - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] exclude: # Just run macos tests on one Python version - os: macos-13 @@ -36,6 +36,8 @@ jobs: python-version: "3.11" - os: macos-13 python-version: "3.12" + - os: macos-14 + python-version: "3.9" - os: macos-14 python-version: "3.10" - os: macos-14 diff --git a/pyproject.toml b/pyproject.toml index 35b250f..ff5ad0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "pyranges", "pyparsing>=3" ] -requires-python = ">=3.10" +requires-python = ">=3.9" classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: Apache Software License", @@ -28,6 +28,7 @@ classifiers = [ "Intended Audience :: Science/Research", "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -61,8 +62,8 @@ addopts = "--cov=vcztools --cov-report=term-missing" write_to = "vcztools/_version.py" [tool.ruff] -# Assume Python 3.10 -target-version = "py310" +# Assume Python 3.9 +target-version = "py39" # Same as Black. line-length = 88 diff --git a/tests/test_regions.py b/tests/test_regions.py index f8243af..0bc0855 100644 --- a/tests/test_regions.py +++ b/tests/test_regions.py @@ -1,3 +1,5 @@ +from typing import Optional + import pytest from vcztools.regions import parse_region_string @@ -13,6 +15,6 @@ ], ) def test_parse_region_string( - targets: str, expected: tuple[str, int | None, int | None] + targets: str, expected: tuple[str, Optional[int], Optional[int]] ): assert parse_region_string(targets) == expected diff --git a/tests/test_vcf_writer.py b/tests/test_vcf_writer.py index 0ad47df..7f680f4 100644 --- a/tests/test_vcf_writer.py +++ b/tests/test_vcf_writer.py @@ -85,7 +85,7 @@ def test_write_vcf__filtering(tmp_path, include, exclude, expected_chrom_pos): assert len(variants) == len(expected_chrom_pos) assert v.samples == ["NA00001", "NA00002", "NA00003"] - for variant, chrom_pos in zip(variants, expected_chrom_pos, strict=True): + for variant, chrom_pos in zip(variants, expected_chrom_pos): chrom, pos = chrom_pos assert variant.CHROM == chrom assert variant.POS == pos @@ -141,7 +141,7 @@ def test_write_vcf__regions(tmp_path, regions, targets, assert v.samples == ["NA00001", "NA00002", "NA00003"] - for variant, chrom_pos in zip(variants, expected_chrom_pos, strict=True): + for variant, chrom_pos in zip(variants, expected_chrom_pos): chrom, pos = chrom_pos assert variant.CHROM == chrom assert variant.POS == pos @@ -230,7 +230,7 @@ def test_write_vcf__regions_samples_filtering( assert len(variants) == len(expected_chrom_pos) assert v.samples == ["NA00001"] - for variant, chrom_pos in zip(variants, expected_chrom_pos, strict=True): + for variant, chrom_pos in zip(variants, expected_chrom_pos): chrom, pos = chrom_pos assert variant.CHROM == chrom assert variant.POS == pos diff --git a/vcztools/filter.py b/vcztools/filter.py index 3929e13..19059b7 100644 --- a/vcztools/filter.py +++ b/vcztools/filter.py @@ -1,6 +1,6 @@ import functools import operator -from collections.abc import Callable +from typing import Callable import numpy as np import pyparsing as pp diff --git a/vcztools/query.py b/vcztools/query.py index 689af41..bfa55b3 100644 --- a/vcztools/query.py +++ b/vcztools/query.py @@ -1,7 +1,7 @@ import functools import itertools import math -from collections.abc import Callable +from typing import Callable, Optional, Union import numpy as np import pyparsing as pp @@ -62,10 +62,10 @@ def __call__(self, *args, **kwargs): class QueryFormatGenerator: def __init__( self, - query_format: str | pp.ParseResults, + query_format: Union[str, pp.ParseResults], *, - include: str | None = None, - exclude: str | None = None, + include: Optional[str] = None, + exclude: Optional[str] = None, ): if isinstance(query_format, str): parser = QueryFormatParser() @@ -99,7 +99,7 @@ def generate(root): end = start + v_chunk_size for gt_row, phase in zip( - gt_zarray[start:end], phase_zarray[start:end], strict=True + gt_zarray[start:end], phase_zarray[start:end] ): def stringify(gt_and_phase: tuple): @@ -113,7 +113,7 @@ def stringify(gt_and_phase: tuple): return separator.join(gt) gt_row = gt_row.tolist() - yield map(stringify, zip(gt_row, phase, strict=True)) + yield map(stringify, zip(gt_row, phase)) else: # TODO: Support datasets without the phasing data raise NotImplementedError @@ -230,8 +230,8 @@ def _compose_sample_loop_generator( def generate(root): iterables = (generator(root) for generator in generators) - zipped = zip(*iterables, strict=True) - zipped_zipped = (zip(*element, strict=True) for element in zipped) + zipped = zip(*iterables) + zipped_zipped = (zip(*element) for element in zipped) flattened_zipped_zipped = ( ( subsubelement @@ -245,7 +245,7 @@ def generate(root): return generate def _compose_element_generator( - self, element: str | pp.ParseResults, *, sample_loop=False + self, element: Union[str, pp.ParseResults], *, sample_loop=False ) -> Callable: if isinstance(element, pp.ParseResults): if element.get_name() == "subfield": @@ -272,7 +272,7 @@ def generate(root): return generate def _compose_filter_generator( - self, *, include: str | None = None, exclude: str | None = None + self, *, include: Optional[str] = None, exclude: Optional[str] = None ) -> Callable: assert not (include and exclude) @@ -305,8 +305,8 @@ def _compose_generator( self, parse_results: pp.ParseResults, *, - include: str | None = None, - exclude: str | None = None, + include: Optional[str] = None, + exclude: Optional[str] = None, ) -> Callable: generators = ( self._compose_element_generator(element) for element in parse_results @@ -318,10 +318,7 @@ def _compose_generator( def generate(root) -> str: iterables = (generator(root) for generator in generators) filter_iterable = filter_generator(root) - - for results, filter_indicator in zip( - zip(*iterables, strict=True), filter_iterable, strict=True - ): + for results, filter_indicator in zip(zip(*iterables), filter_iterable): if filter_indicator: results = map(str, results) yield "".join(results) @@ -334,8 +331,8 @@ def write_query( output=None, *, query_format: str, - include: str | None = None, - exclude: str | None = None, + include: Optional[str] = None, + exclude: Optional[str] = None, ): if include and exclude: raise ValueError( diff --git a/vcztools/regions.py b/vcztools/regions.py index f0a10e3..98db3a2 100644 --- a/vcztools/regions.py +++ b/vcztools/regions.py @@ -1,12 +1,12 @@ import re -from typing import Any +from typing import Any, Optional import numpy as np import pandas as pd from pyranges import PyRanges -def parse_region_string(region: str) -> tuple[str, int | None, int | None]: +def parse_region_string(region: str) -> tuple[str, Optional[int], Optional[int]]: """Return the contig, start position and end position from a region string.""" if re.search(r":\d+-\d*$", region): contig, start_end = region.rsplit(":", 1) @@ -21,7 +21,7 @@ def parse_region_string(region: str) -> tuple[str, int | None, int | None]: def regions_to_pyranges( - regions: list[tuple[str, int | None, int | None]], all_contigs: list[str] + regions: list[tuple[str, Optional[int], Optional[int]]], all_contigs: list[str] ) -> PyRanges: """Convert region tuples to a PyRanges object.""" @@ -44,7 +44,7 @@ def regions_to_pyranges( return PyRanges(chromosomes=chromosomes, starts=starts, ends=ends) -def parse_regions(regions: str | None, all_contigs: list[str]) -> PyRanges | None: +def parse_regions(regions: Optional[str], all_contigs: list[str]) -> Optional[PyRanges]: """Return a PyRanges object from a comma-separated set of region strings.""" if regions is None: return None @@ -54,21 +54,22 @@ def parse_regions(regions: str | None, all_contigs: list[str]) -> PyRanges | Non def parse_targets( - targets: str | None, all_contigs: list[str] -) -> tuple[PyRanges | None, bool]: + targets: Optional[str], all_contigs: list[str] +) -> tuple[Optional[PyRanges], bool]: """Return a PyRanges object from a comma-separated set of region strings, optionally preceeded by a ^ character to indicate complement.""" if targets is None: return None, False complement = targets.startswith("^") - return parse_regions( - targets[1:] if complement else targets, all_contigs - ), complement + return ( + parse_regions(targets[1:] if complement else targets, all_contigs), + complement, + ) def regions_to_chunk_indexes( - regions: PyRanges | None, - targets: PyRanges | None, + regions: Optional[PyRanges], + targets: Optional[PyRanges], complement: bool, regions_index: Any, ): @@ -112,8 +113,8 @@ def regions_to_chunk_indexes( def regions_to_selection( - regions: PyRanges | None, - targets: PyRanges | None, + regions: Optional[PyRanges], + targets: Optional[PyRanges], complement: bool, variant_contig: Any, variant_position: Any, diff --git a/vcztools/stats.py b/vcztools/stats.py index 05b2ce7..0aac357 100644 --- a/vcztools/stats.py +++ b/vcztools/stats.py @@ -38,7 +38,7 @@ def stats(vcz, output): ).astype(np.int64) for contig, contig_length, nr in zip( - contigs, contig_lengths, num_records_per_contig, strict=True + contigs, contig_lengths, num_records_per_contig ): if nr > 0: print(f"{contig}\t{contig_length}\t{nr}", file=output) diff --git a/vcztools/utils.py b/vcztools/utils.py index b3f774b..977433c 100644 --- a/vcztools/utils.py +++ b/vcztools/utils.py @@ -18,7 +18,7 @@ def open_file_like(file): """A context manager for opening a file path or string (and closing on exit), or passing a file-like object through.""" with ExitStack() as stack: - if isinstance(file, str | Path): + if isinstance(file, (str, Path)): file = stack.enter_context(open(file, mode="w")) yield file diff --git a/vcztools/vcf_writer.py b/vcztools/vcf_writer.py index 63af9df..fdc7e9b 100644 --- a/vcztools/vcf_writer.py +++ b/vcztools/vcf_writer.py @@ -4,6 +4,7 @@ import re import sys from datetime import datetime +from typing import Optional import numpy as np import zarr @@ -89,8 +90,8 @@ def write_vcf( no_update=None, samples=None, drop_genotypes: bool = False, - include: str | None = None, - exclude: str | None = None, + include: Optional[str] = None, + exclude: Optional[str] = None, ) -> None: """Convert a dataset to a VCF file.