From d222f9dfb1cec97871c8f3824ce579a1c2ea92b9 Mon Sep 17 00:00:00 2001 From: "endrebak.ada" Date: Thu, 15 Oct 2026 10:15:09 +0200 Subject: [PATCH] Switch type checker from pyright to ty and drop type ignores Co-Authored-By: Claude Opus 5.5 --- check_typing_linting_and_formatting.py | 2 +- pyproject.toml | 27 ++++++------ pyranges1/cli.py | 2 +- pyranges1/core/example_data.py | 10 ++--- pyranges1/core/options.py | 4 +- pyranges1/core/out.py | 12 +++--- pyranges1/core/pyranges_main.py | 54 ++++++++++++------------ pyranges1/core/remote.py | 2 +- pyranges1/ext/orfs.py | 2 +- pyranges1/ext/stats.py | 2 +- pyranges1/methods/bigwig_stats.py | 18 +++----- pyranges1/methods/boundaries.py | 4 +- pyranges1/methods/complement.py | 4 +- pyranges1/methods/complement_overlaps.py | 6 +-- pyranges1/methods/coverage_ranges.py | 2 +- pyranges1/methods/join.py | 4 +- pyranges1/methods/map_to_global.py | 4 +- pyranges1/methods/merge.py | 2 +- pyranges1/methods/overlap.py | 4 +- pyranges1/methods/slice_ranges.py | 10 ++--- pyranges1/methods/sort.py | 10 ++--- pyranges1/methods/summary.py | 2 +- pyranges1/methods/to_rle.py | 4 +- pyranges1/range_frame/range_frame.py | 36 ++++++++-------- pyranges1/readers.py | 14 +++--- 25 files changed, 117 insertions(+), 124 deletions(-) diff --git a/check_typing_linting_and_formatting.py b/check_typing_linting_and_formatting.py index ed4d5320..438b98cd 100755 --- a/check_typing_linting_and_formatting.py +++ b/check_typing_linting_and_formatting.py @@ -30,7 +30,7 @@ def main() -> int: commands = { "ruff format": ["ruff", "format", "--check", "--diff", "pyranges1"], "ruff check": ["ruff", "check", "pyranges1"], - "pyright": ["pyright", "pyranges1"], + "ty": ["ty", "check"], "pytest": ["pytest", "--doctest-modules", "pyranges1/", "tests/unit/"], # "more doctest": ["python", "tests/run_doctest_tutorial_howto.py"], } diff --git a/pyproject.toml b/pyproject.toml index 5c13d2d2..cd2a3062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,10 @@ add-ons = [ dev = [ "tox", "ruff == 0.14.1", - "pyright", + "ty == 0.0.87", # Cap to the pandas-2.x stub line: unpinned, pandas-stubs resolves to the # 3.0.x series (which targets pandas 3.0 and changed e.g. the Series.map / - # astype overloads), producing spurious pyright errors against pandas 2.x. + # astype overloads), producing spurious type errors against pandas 2.x. "pandas-stubs<3", "types-tabulate", "pytest-watcher", @@ -129,7 +129,7 @@ deps = pandas-stubs<3 types-tabulate pytest - pyright == 1.1.406 + ty == 0.0.87 joblib gtfreader >= 0.3.0 ruranges >= 0.1.8 @@ -138,7 +138,7 @@ deps = pyarrow # fisher commands = - pyright + ty check python tests/run_doctest_tutorial_howto.py pytest --doctest-modules pyranges1 pytest tests/unit @@ -180,18 +180,17 @@ ignore = [ [tool.pytest-watcher] now = true -[tool.pyright] -include = ["pyranges1"] -# typeCheckingMode = "strict" -pythonVersion = "3.12" +[tool.ty.environment] +python-version = "3.12" -ignore = ["pyranges1/core/pyranges_groupby.py"] +[tool.ty.src] +include = ["pyranges1"] +exclude = ["pyranges1/core/pyranges_groupby.py"] -# specific rules to disable -reportUnknownParameterType = false -reportUnknownMemberType = false -reportUnknownArgumentType = false -reportUnknownVariableType = false +[tool.ty.analysis] +# optional dependencies that are either compiled without stubs (pyBigWig, gtfreader._parser) +# or not installed in CI +allowed-unresolved-imports = ["pyBigWig", "gtfreader._parser", "fisher", "fire"] [tool.pytest.ini_options] markers = [ diff --git a/pyranges1/cli.py b/pyranges1/cli.py index 3e2ccedf..6b572f75 100644 --- a/pyranges1/cli.py +++ b/pyranges1/cli.py @@ -9,7 +9,7 @@ from pyranges1.core.pyranges_helpers import ensure_pyranges try: - import fire # type: ignore[reportMissingImports] + import fire except ImportError: fire = None diff --git a/pyranges1/core/example_data.py b/pyranges1/core/example_data.py index 708dd03d..4f0244dd 100644 --- a/pyranges1/core/example_data.py +++ b/pyranges1/core/example_data.py @@ -308,7 +308,7 @@ def aorta(self) -> "pr.PyRanges": From the epigenomics roadmap. """ - return pr.read_bed(ExampleData.files["aorta.bed"]) # type: ignore[index, misc] + return pr.read_bed(ExampleData.files["aorta.bed"]) @property def aorta2(self) -> "pr.PyRanges": @@ -316,7 +316,7 @@ def aorta2(self) -> "pr.PyRanges": From the epigenomics roadmap. """ - return pr.read_bed(ExampleData.files["aorta2.bed"]) # type: ignore[index, misc] + return pr.read_bed(ExampleData.files["aorta2.bed"]) @property def ncbi_gff(self) -> "pr.PyRanges": @@ -324,7 +324,7 @@ def ncbi_gff(self) -> "pr.PyRanges": Subset of the NCBI annotation of D.gyrociliatus assembly GCA_904063045.1. """ - return pr.read_gff3(ExampleData.files["ncbi.gff.gz"]) # type: ignore[index, misc] + return pr.read_gff3(ExampleData.files["ncbi.gff.gz"]) @property def ncbi_fasta(self) -> "pyfaidx.Fasta": @@ -336,7 +336,7 @@ def ncbi_fasta(self) -> "pyfaidx.Fasta": To retrieve the location of the fasta file, use pyranges.example_data.files['ncbi.fasta'] """ try: - import pyfaidx # type: ignore[import] + import pyfaidx except ImportError: LOGGER.exception( "To use this method, pyfaidx must be installed. To get just the fasta file path, use pyranges.example_data.files['ncbi.fasta']. Use `conda install -c bioconda pyfaidx` or `pip install pyfaidx` to install pyfaidx.", @@ -344,7 +344,7 @@ def ncbi_fasta(self) -> "pyfaidx.Fasta": sys.exit(1) # note: example data include ncbi.fasta.fai, the pyfaidx index - return pyfaidx.Fasta(ExampleData.files["ncbi.fasta"]) # type: ignore[index, misc] + return pyfaidx.Fasta(ExampleData.files["ncbi.fasta"]) example_data = ExampleData() diff --git a/pyranges1/core/options.py b/pyranges1/core/options.py index 4a842e20..993ea70a 100644 --- a/pyranges1/core/options.py +++ b/pyranges1/core/options.py @@ -1,10 +1,12 @@ # below: Class to manage PyRanges options # purposedly not using a docstring to avoid it being included in the docs +from typing import Any + class PyRangesOptions: def __init__(self) -> None: - self.options_in_use = { + self.options_in_use: dict[str, tuple[Any, str]] = { "max_rows_to_show": (8, "the max number of rows to show in PyRanges repr"), "max_column_names_to_show": ( 3, diff --git a/pyranges1/core/out.py b/pyranges1/core/out.py index 4e451eba..332dba47 100644 --- a/pyranges1/core/out.py +++ b/pyranges1/core/out.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd -from natsort import natsorted # type: ignore[import] +from natsort import natsorted from pandas.core.frame import DataFrame from pyranges1.core.names import BIGWIG_SCORE_COL, CHROM_COL, END_COL, PANDAS_COMPRESSION_TYPE, START_COL @@ -13,7 +13,7 @@ from pyranges1.core.pyranges_main import PyRanges if TYPE_CHECKING: - from runlength import RleBatch # type: ignore[import-not-found] + from runlength import RleBatch GTF_COLUMNS_TO_PYRANGES = { "seqname": "Chromosome", @@ -199,7 +199,7 @@ def _to_bed_tabix(df: DataFrame, path: str | Path | None, compression: PANDAS_CO msg = "tabix=True writes a bgzip-compressed file: give a path ending in .gz or .bgz, and no other compression." raise ValueError(msg) try: - import pysam # type: ignore[import] + import pysam except ImportError: msg = "pysam must be installed to write a tabix-indexed BED file. Use `pip install pysam`." raise ImportError(msg) from None @@ -238,7 +238,7 @@ def _to_bigwig( return_data: bool = False, ) -> PyRanges | None: try: - import pyBigWig # type: ignore[import] + import pyBigWig except ModuleNotFoundError: msg = "pybigwig must be installed to create bigwigs. Use `conda install -c bioconda pybigwig` or `pip install pybigwig` to install it." raise ImportError(msg) from None @@ -532,7 +532,7 @@ def _to_bigbed( autosql: str | None = None, ) -> None: try: - import pybigtools # type: ignore[import] + import pybigtools except ImportError: msg = "pybigtools must be installed to write BigBed files. Use `pip install pybigtools` to install it." raise ImportError(msg) from None @@ -566,5 +566,5 @@ def _to_bigbed( sql = autosql ordered_sizes = {c: sizes[c] for c in sorted(sizes)} - writer = pybigtools.open(str(path), "w") # type: ignore[attr-defined] + writer = pybigtools.open(str(path), "w") writer.write(ordered_sizes, iter(vals), autosql=sql) diff --git a/pyranges1/core/pyranges_main.py b/pyranges1/core/pyranges_main.py index e6a9c950..bad29436 100644 --- a/pyranges1/core/pyranges_main.py +++ b/pyranges1/core/pyranges_main.py @@ -8,7 +8,7 @@ import numpy as np import pandas as pd -from natsort import natsorted # type: ignore[import] +from natsort import natsorted import pyranges1 as pr from pyranges1.core.loci_getter import LociGetter @@ -68,8 +68,8 @@ from pyranges1.range_frame.range_frame_validator import InvalidRangesReason if TYPE_CHECKING: - import pyfaidx # type: ignore[import] - from runlength import RleBatch # type: ignore[import-not-found] + import pyfaidx + from runlength import RleBatch __all__ = ["PyRanges"] @@ -177,7 +177,7 @@ class PyRanges(RangeFrame): # ever calling __init__, so this cannot be set there alone. _loci: "LociGetter | None" = None - def __new__(cls, *args, **kwargs) -> "pr.PyRanges": # type: ignore[misc] + def __new__(cls, *args, **kwargs) -> "pr.PyRanges": """Create a new instance of a PyRanges object.""" # __new__ is a special static method used for creating and # returning a new instance of a class. It is called before @@ -239,7 +239,7 @@ def _constructor_with_fallback(cls, *args, **kwargs) -> "pr.PyRanges | pd.DataFr return cls(df) @classmethod - def from_arrow(cls, data: Any) -> "PyRanges": # type: ignore[override] + def from_arrow(cls, data: Any) -> "PyRanges": """Create a PyRanges from an Arrow table, or anything that exports one (polars, DuckDB, ...). As pandas.DataFrame.from_arrow, but the result is a PyRanges. Like the constructor, @@ -255,7 +255,7 @@ def from_arrow(cls, data: Any) -> "PyRanges": # type: ignore[override] """ if hasattr(pd.DataFrame, "from_arrow"): # pandas >= 3 - return cls(pd.DataFrame.from_arrow(data)) # type: ignore[attr-defined] + return cls(pd.DataFrame.from_arrow(data)) import pyarrow as pa return cls(pa.table(data).to_pandas()) @@ -811,7 +811,7 @@ def cluster_overlaps( return ensure_pyranges(result) - def copy(self, *args, **kwargs) -> "pr.PyRanges": + def copy(self, *args, **kwargs) -> "pr.PyRanges": # ty: ignore[override-of-final-method] """Return a copy of the PyRanges.""" # a copy keeps every column, so pandas hands this straight back as our own class return self._rebuild_as_self(super().copy(*args, **kwargs)) @@ -898,7 +898,7 @@ def coverage_ranges( by = prepare_by_single(self, use_strand=use_strand, match_by=match_by) return _coverage_ranges(self, by, value_col, coverage_col) - def count_overlaps( # type: ignore[override] + def count_overlaps( # ty: ignore[invalid-method-override] self, other: "PyRanges", strand_behavior: VALID_STRAND_BEHAVIOR_TYPE = "auto", @@ -1239,7 +1239,7 @@ def extend_ranges( groups = factorize(self, group_by) if group_by is not None else np.arange(len(self), dtype=np.uint32) starts, ends = ruranges.numpy.extend( - groups=groups, # type: ignore[arg-type] + groups=groups, starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), negative_strand=( @@ -1434,7 +1434,7 @@ def has_strand(self) -> bool: """ return STRAND_COL in self.columns - def join_overlaps( # type: ignore[override] + def join_overlaps( # ty: ignore[invalid-method-override] self, other: "PyRanges", *, @@ -2469,7 +2469,7 @@ def merge_overlaps( return ensure_pyranges(result) - def nearest_ranges( # type: ignore[override] + def nearest_ranges( # ty: ignore[invalid-method-override] self, other: "PyRanges", strand_behavior: VALID_STRAND_BEHAVIOR_TYPE = "auto", @@ -2797,7 +2797,7 @@ def nearest_ranges( # type: ignore[override] del combined[TEMP_INDEX_COL] return ensure_pyranges(combined) - def overlap( # type: ignore[override] + def overlap( # ty: ignore[invalid-method-override] self, other: "PyRanges", strand_behavior: VALID_STRAND_BEHAVIOR_TYPE = "auto", @@ -3245,7 +3245,7 @@ def set_union_overlaps( return gr.merge_overlaps(use_strand=use_strand, match_by=match_by) - def sort_ranges( # type: ignore[override] + def sort_ranges( self, by: VALID_BY_TYPES = None, *, @@ -3434,7 +3434,7 @@ def sort_ranges( # type: ignore[override] use_natsort=natsort, reverse_rows=None if not use_strand else (self[STRAND_COL] == "-").to_numpy(dtype=bool), ) - res = self.take(idxs) # type: ignore[arg-type] + res = self.take(idxs) return ensure_pyranges(res) @@ -3846,7 +3846,7 @@ def split_overlaps( between=between, ) - res = ensure_pyranges(self.take(idxs).reset_index(drop=True)) # type: ignore[arg-type] + res = ensure_pyranges(self.take(idxs).reset_index(drop=True)) if between: # A gap row descends from no input row, so it carries no metadata; and if # Strand was not a grouping key the gap spans both strands, so no strand @@ -3965,7 +3965,7 @@ def make_strand_valid(self) -> "PyRanges": return ensure_pyranges(result) - def subtract_overlaps( # type: ignore[override] + def subtract_overlaps( # ty: ignore[invalid-method-override] self, other: "pr.PyRanges", strand_behavior: VALID_STRAND_BEHAVIOR_TYPE = "auto", @@ -4275,14 +4275,14 @@ def tile_ranges( factorize_arange(self, match_by) negative_strand = (self[STRAND_COL] == "-").to_numpy() if use_strand else np.zeros(len(self), dtype=bool) - indices, starts, ends, overlap_fraction = ruranges.numpy.tile( # type: ignore[attr-defined] + indices, starts, ends, overlap_fraction = ruranges.numpy.tile( starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), negative_strand=negative_strand, tile_size=tile_size, ) - res = self.take(indices) # type: ignore[arg-type] + res = self.take(indices) res.loc[:, START_COL] = starts res.loc[:, END_COL] = ends if overlap_column: @@ -4445,7 +4445,7 @@ def to_bed( return _to_bed(self, path, keep=keep, compression=compression, tabix=tabix) - def to_parquet(self, path: str | None = None, *, index: bool = False, **kwargs) -> bytes | None: # type: ignore[override] + def to_parquet(self, path: str | None = None, *, index: bool = False, **kwargs) -> bytes | None: # ty: ignore[invalid-method-override] r"""Write to Parquet. Parquet preserves column dtypes (categoricals included) and reads back @@ -5923,14 +5923,14 @@ def window_ranges( group_ids = factorize_arange(gr, group_by) negative_strand = (gr[STRAND_COL] == "-").to_numpy() if use_strand else np.zeros(len(gr), dtype=bool) - idx, starts, ends = ruranges.numpy.window( # type: ignore[attr-defined] + idx, starts, ends = ruranges.numpy.window( groups=group_ids, starts=gr[START_COL].to_numpy(), ends=gr[END_COL].to_numpy(), negative_strand=negative_strand, window_size=window_size, ) - df = gr.take(idx) # type: ignore[arg-type] + df = gr.take(idx) df.loc[:, START_COL] = starts df.loc[:, END_COL] = ends df = ensure_pyranges(df) @@ -6152,7 +6152,7 @@ def group_cumsum( forward = (self[STRAND_COL] == FORWARD_STRAND).to_numpy() if strand else np.ones(self.shape[0], dtype=np.bool_) - idx, cumsum_start, cumsum_end = ruranges.numpy.group_cumsum( # type: ignore[attr-defined] + idx, cumsum_start, cumsum_end = ruranges.numpy.group_cumsum( starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), groups=group_ids, @@ -6160,7 +6160,7 @@ def group_cumsum( sort=keep_order, ) - res = self.take(idx).copy() # type: ignore[arg-type] + res = self.take(idx).copy() if cumsum_start_column is None: res.loc[:, START_COL] = cumsum_start res.loc[:, END_COL] = cumsum_end @@ -6170,7 +6170,7 @@ def group_cumsum( return ensure_pyranges(res) - def intersect_overlaps( # type: ignore[override] + def intersect_overlaps( self, other: "PyRanges", strand_behavior: VALID_STRAND_BEHAVIOR_TYPE = "auto", @@ -6987,7 +6987,7 @@ def get_sequence( """ try: - import pyfaidx # type: ignore[import] + import pyfaidx except ImportError: LOGGER.exception( "pyfaidx must be installed to get fasta sequences. Use `conda install -c bioconda pyfaidx` or `pip install pyfaidx` to install it.", @@ -7234,7 +7234,7 @@ def clip_ranges( chrom_lengths_vec = lengths_per_code[codes] idxs, starts, ends = ruranges.numpy.genome_bounds( - groups=codes, # type: ignore[arg-type] + groups=codes, starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), chrom_length=chrom_lengths_vec, # **row-aligned!** @@ -7242,7 +7242,7 @@ def clip_ranges( only_right=only_right, ) - res = self.take(idxs) # type: ignore[arg-type] + res = self.take(idxs) if not remove: res.loc[:, START_COL] = starts res.loc[:, END_COL] = ends diff --git a/pyranges1/core/remote.py b/pyranges1/core/remote.py index 56b7c8df..1e8aa7b8 100644 --- a/pyranges1/core/remote.py +++ b/pyranges1/core/remote.py @@ -37,7 +37,7 @@ def _local_copy(url: str, storage_options: dict | None) -> Generator[Path]: shutil.copyfileobj(source, copy, 1 << 23) else: try: - import fsspec # type: ignore[import] + import fsspec except ImportError: msg = f"Reading {scheme}:// paths requires fsspec: `pip install fsspec` and the package for {scheme}." raise ImportError(msg) from None diff --git a/pyranges1/ext/orfs.py b/pyranges1/ext/orfs.py index 3fcbdf14..377bf384 100644 --- a/pyranges1/ext/orfs.py +++ b/pyranges1/ext/orfs.py @@ -357,7 +357,7 @@ def extend_orfs( # noqa: C901,PLR0912,PLR0915 """ try: - import pyfaidx # type: ignore[import] + import pyfaidx except ImportError: msg = "To use extend_orfs, pyfaidx must be installed. Use `conda install -c bioconda pyfaidx` or `pip install pyfaidx` to install pyfaidx." raise ImportError(msg) from None diff --git a/pyranges1/ext/stats.py b/pyranges1/ext/stats.py index f7fe4a26..38e1c26a 100644 --- a/pyranges1/ext/stats.py +++ b/pyranges1/ext/stats.py @@ -231,7 +231,7 @@ def fisher_exact(tp: Series, fp: Series, fn: Series, tn: Series, pseudocount: in """ try: - from fisher import pvalue_npy # type: ignore[import] + from fisher import pvalue_npy except ImportError: msg = ( "fisher needs to be installed to use fisher exact. pip install fisher or conda install -c bioconda fisher." diff --git a/pyranges1/methods/bigwig_stats.py b/pyranges1/methods/bigwig_stats.py index e7ed7a1b..3bc1d367 100644 --- a/pyranges1/methods/bigwig_stats.py +++ b/pyranges1/methods/bigwig_stats.py @@ -28,13 +28,9 @@ def _entries(bw: Any, chromosome: str, starts: np.ndarray, ends: np.ndarray, siz block_starts = starts[new_block] block_ends = np.maximum.reduceat(ends, np.flatnonzero(new_block)) if len(ends) else ends if len(block_starts) > MAX_REGION_READS: - intervals = bw.intervals(chromosome) # type: ignore[attr-defined] + intervals = bw.intervals(chromosome) return np.array(intervals or [], dtype=float).reshape(-1, 3) - parts = [ - bw.intervals(chromosome, int(s), int(e)) # type: ignore[attr-defined] - for s, e in zip(block_starts, block_ends, strict=True) - if e > s - ] + parts = [bw.intervals(chromosome, int(s), int(e)) for s, e in zip(block_starts, block_ends, strict=True) if e > s] entries = np.array([entry for part in parts if part for entry in part], dtype=float).reshape(-1, 3) # An entry that crosses from one stretch into the next is read with both. _, first = np.unique(entries[:, 0], return_index=True) @@ -50,8 +46,8 @@ def _read_track( ) -> tuple[Any, dict[str, int]]: """Read the signal under the intervals into one Rle per chromosome, null where the bigWig has no data.""" import pyarrow as pa - import pyBigWig # type: ignore[import] - import runlength as rl # type: ignore[import-not-found] + import pyBigWig + import runlength as rl bw = pyBigWig.open(str(path)) sizes = bw.chroms() @@ -78,7 +74,7 @@ def _slice_agg(signal: Any, bins: tuple[object, np.ndarray, np.ndarray], agg: st """Compute one statistic over every bin, as floats with NaN for null.""" import pyarrow as pa - result = signal.slice_agg(*bins, agg) # type: ignore[attr-defined] + result = signal.slice_agg(*bins, agg) return pa.array(result).to_numpy(zero_copy_only=False).astype(float) @@ -93,13 +89,13 @@ def _bigwig_stats( prefix: str, ) -> "PyRanges": try: - import pyBigWig # type: ignore[import] # noqa: F401 + import pyBigWig # noqa: F401 except ImportError: msg = "pyBigWig must be installed to read bigwigs. Use `pip install pyBigWig` to install it." raise ImportError(msg) from None try: import pyarrow as pa - import runlength # type: ignore[import-not-found] # noqa: F401 + import runlength # noqa: F401 except ImportError: msg = "bigwig_stats requires runlength and pyarrow: pip install runlength pyarrow" raise ImportError(msg) from None diff --git a/pyranges1/methods/boundaries.py b/pyranges1/methods/boundaries.py index 58057ab4..1810df97 100644 --- a/pyranges1/methods/boundaries.py +++ b/pyranges1/methods/boundaries.py @@ -22,13 +22,13 @@ def _bounds[T: ("pr.PyRanges", "pd.DataFrame")](df: T, by: list[str]) -> pd.Data group_ids = factorize(df, by=by) - idxs, starts, ends, _counts = ruranges.numpy.boundary( # type: ignore[attr-defined] + idxs, starts, ends, _counts = ruranges.numpy.boundary( groups=group_ids, starts=df[START_COL].to_numpy(), ends=df[END_COL].to_numpy(), ) - ids = df.take(idxs).loc[:, by] # type: ignore[arg-type] + ids = df.take(idxs).loc[:, by] result = RangeFrame({START_COL: starts, END_COL: ends} | {_by: ids[_by] for _by in by})[col_order] diff --git a/pyranges1/methods/complement.py b/pyranges1/methods/complement.py index 94b5f8e2..1dc02157 100644 --- a/pyranges1/methods/complement.py +++ b/pyranges1/methods/complement.py @@ -89,7 +89,7 @@ def _complement( starts=df.Start.to_numpy(), ends=df.End.to_numpy(), slack=slack, - chrom_len_ids=chrom_len_ids, # type: ignore[arg-type] + chrom_len_ids=chrom_len_ids, chrom_lens=chrom_lens, include_first_interval=include_first_interval, ) @@ -101,7 +101,7 @@ def _complement( if not keep.all(): chrs, start, end, idxs = chrs[keep], start[keep], end[keep], idxs[keep] - ids = df.take(idxs) # type: ignore[arg-type] + ids = df.take(idxs) result = RangeFrame({CHROM_COL: chrs, START_COL: start, END_COL: end} | {_by: ids[_by] for _by in by})[col_order] diff --git a/pyranges1/methods/complement_overlaps.py b/pyranges1/methods/complement_overlaps.py index 6735a33c..0d4418c7 100644 --- a/pyranges1/methods/complement_overlaps.py +++ b/pyranges1/methods/complement_overlaps.py @@ -24,14 +24,14 @@ def _complement_overlaps( factorized, factorized2 = factorize_binary(df, df2, by) indices = ruranges.numpy.complement_overlaps( - groups=factorized, # type: ignore[arg-type] + groups=factorized, starts=df.Start.to_numpy(), ends=df.End.to_numpy(), - groups2=factorized2, # type: ignore[arg-type] + groups2=factorized2, starts2=df2.Start.to_numpy(), ends2=df2.End.to_numpy(), slack=slack or 0, sort_output=preserve_input_order, ) - return ensure_rangeframe(df.take(indices)) # type: ignore[arg-type] + return ensure_rangeframe(df.take(indices)) diff --git a/pyranges1/methods/coverage_ranges.py b/pyranges1/methods/coverage_ranges.py index e4bc292a..e0f41bf3 100644 --- a/pyranges1/methods/coverage_ranges.py +++ b/pyranges1/methods/coverage_ranges.py @@ -17,7 +17,7 @@ def _coverage_ranges(self: "PyRanges", by: list[str], value_col: str | None, cov already maximal, so only the uncovered ones are left out. """ try: - import runlength as rl # type: ignore[import-not-found] + import runlength as rl except ImportError as e: msg = "coverage_ranges requires that runlength is installed: pip install runlength" raise ImportError(msg) from e diff --git a/pyranges1/methods/join.py b/pyranges1/methods/join.py index 0af77c7f..4a3213bc 100644 --- a/pyranges1/methods/join.py +++ b/pyranges1/methods/join.py @@ -36,9 +36,9 @@ def _both_dfs( # _self_indexes / _other_indexes are positional (from the overlap engine). Take the # matching rows, then temporarily reset both indexes to a RangeIndex so the column-wise # join pairs them by position rather than by label. - _df = df.take(_self_indexes) # type: ignore[arg-type] + _df = df.take(_self_indexes) _df.index = pd.RangeIndex(len(_df)) - _df2 = pd.DataFrame(df2).take(_other_indexes) # type: ignore[arg-type] + _df2 = pd.DataFrame(df2).take(_other_indexes) _df2.index = pd.RangeIndex(len(_df2)) j = _df.join(_df2, how="inner") # Restore the original input index, like overlap()/nearest_ranges()/etc. The primary diff --git a/pyranges1/methods/map_to_global.py b/pyranges1/methods/map_to_global.py index d2039b31..46308cc6 100644 --- a/pyranges1/methods/map_to_global.py +++ b/pyranges1/methods/map_to_global.py @@ -48,7 +48,7 @@ def _map_to_global_pandas( suffixes=("_local", ""), ) - z = m.slice_ranges( # type: ignore[reportCallIssue] + z = m.slice_ranges( group_by="_temp_index_", start=m[out_local_start].to_numpy(), end=m[out_local_end].to_numpy(), @@ -174,7 +174,7 @@ def _map_to_global_ruranges( out_chr_code = tx2chr_code[q_tx_code[keep_idx]] out_chr_str = chr_table[out_chr_code] - mapped_df = local_gr.take(keep_idx).copy() # type: ignore[arg-type] + mapped_df = local_gr.take(keep_idx).copy() if keep_id: mapped_df[global_on] = mapped_df[CHROM_COL].copy() diff --git a/pyranges1/methods/merge.py b/pyranges1/methods/merge.py index 844728af..b9b5222f 100644 --- a/pyranges1/methods/merge.py +++ b/pyranges1/methods/merge.py @@ -33,7 +33,7 @@ def _merge( slack=slack or 0, ) - by_subset = df[col_order].take(indices) # type: ignore[arg-type] + by_subset = df[col_order].take(indices) by_subset.loc[:, START_COL] = start by_subset.loc[:, END_COL] = end diff --git a/pyranges1/methods/overlap.py b/pyranges1/methods/overlap.py index 10f2728c..d0c8aff1 100644 --- a/pyranges1/methods/overlap.py +++ b/pyranges1/methods/overlap.py @@ -69,7 +69,7 @@ def _overlap( slack=slack, preserve_input_order=preserve_input_order, ) - return df.take(idx1) # type: ignore[] + return df.take(idx1) def _intersect( @@ -92,7 +92,7 @@ def _intersect( preserve_input_order=preserve_input_order, ) - rf, rf2 = df.take(idx1), df2.take(idx2).loc[:, RANGE_COLS] # type: ignore[arg-type] + rf, rf2 = df.take(idx1), df2.take(idx2).loc[:, RANGE_COLS] new_starts = np.where(rf.Start.to_numpy() > rf2.Start.to_numpy(), rf.Start, rf2.Start) diff --git a/pyranges1/methods/slice_ranges.py b/pyranges1/methods/slice_ranges.py index 81444065..04d88d4c 100644 --- a/pyranges1/methods/slice_ranges.py +++ b/pyranges1/methods/slice_ranges.py @@ -35,19 +35,19 @@ def _spliced_subseq( chrs = factorize(df, by) if by else np.arange(len(df), dtype=np.uint32) outidx, outstarts, outends = ruranges.numpy.spliced_subsequence( - groups=chrs, # type: ignore[arg-type] + groups=chrs, starts=df[START_COL].to_numpy(), ends=df[END_COL].to_numpy(), strand_flags=(df[STRAND_COL] == FORWARD_STRAND).to_numpy() if (STRAND_COL in df and not force_plus_strand) else np.ones(len(df), dtype=bool), - start=start, # type: ignore[type] - end=end, # type: ignore[type] + start=start, + end=end, force_plus_strand=force_plus_strand, sort_output=preserve_input_order, ) - outdf = df.take(outidx) # type: ignore[arg-type] + outdf = df.take(outidx) outdf.loc[:, START_COL] = outstarts outdf.loc[:, END_COL] = outends @@ -72,7 +72,7 @@ def _subseq( chrs = factorize(df, by) - outidx, outstarts, outends = ruranges.numpy.subsequence_numpy( # type: ignore[attr-defined] + outidx, outstarts, outends = ruranges.numpy.subsequence_numpy( chrs, df[START_COL].to_numpy(), df[END_COL].to_numpy(), diff --git a/pyranges1/methods/sort.py b/pyranges1/methods/sort.py index 76d4f3d6..ba8e6254 100644 --- a/pyranges1/methods/sort.py +++ b/pyranges1/methods/sort.py @@ -112,11 +112,7 @@ def _column_ranks(values: pd.Series, *, use_natsort: bool, descending: bool) -> ruranges = require_ruranges() as_text = [str(value) for value in uniques] - rank = ( - ruranges.numpy.natural_rank(as_text) # type: ignore[attr-defined] - if use_natsort - else ruranges.numpy.lexical_rank(as_text) # type: ignore[attr-defined] - ) + rank = ruranges.numpy.natural_rank(as_text) if use_natsort else ruranges.numpy.lexical_rank(as_text) if descending: rank = (len(uniques) - 1 - rank).astype(np.uint32) @@ -151,7 +147,7 @@ def group_ids(df: pd.DataFrame, keys: list[str], descending: list[bool], *, use_ cardinality = 1 for name, reverse in zip(keys, descending, strict=True): codes, count = _column_ranks(df[name], use_natsort=use_natsort, descending=reverse) - group, cardinality = ruranges.numpy.fold_ranks(group, cardinality, codes, count) # type: ignore[attr-defined] + group, cardinality = ruranges.numpy.fold_ranks(group, cardinality, codes, count) return group @@ -222,7 +218,7 @@ def sort_order( values.insert(0, np.zeros(len(df), dtype=np.int64)) starts, ends = values - return ruranges.numpy.sort_intervals( # type: ignore[attr-defined] + return ruranges.numpy.sort_intervals( starts, ends, groups=group, diff --git a/pyranges1/methods/summary.py b/pyranges1/methods/summary.py index ed1ac71e..a701eab0 100644 --- a/pyranges1/methods/summary.py +++ b/pyranges1/methods/summary.py @@ -52,6 +52,6 @@ def _summary( if return_df: return summary_lengths - str_repr = tabulate(summary_lengths, headers=list(summary_lengths.columns)) # type: ignore[arg-type] + str_repr = tabulate(summary_lengths.itertuples(), headers=list(summary_lengths.columns)) print(str_repr) # noqa: T201 return None diff --git a/pyranges1/methods/to_rle.py b/pyranges1/methods/to_rle.py index 0ba73535..218362b2 100644 --- a/pyranges1/methods/to_rle.py +++ b/pyranges1/methods/to_rle.py @@ -8,7 +8,7 @@ from pyranges1 import PyRanges with contextlib.suppress(ImportError): - from runlength import RleBatch # type: ignore[import-not-found] + from runlength import RleBatch def _to_rle( @@ -19,7 +19,7 @@ def _to_rle( rpm: bool = False, ) -> "RleBatch": try: - import runlength as rl # type: ignore[import-not-found] + import runlength as rl except ImportError as e: msg = "Using the coverage method requires that runlength is installed: pip install runlength" raise ImportError(msg) from e diff --git a/pyranges1/range_frame/range_frame.py b/pyranges1/range_frame/range_frame.py index fbccb576..a43f335f 100644 --- a/pyranges1/range_frame/range_frame.py +++ b/pyranges1/range_frame/range_frame.py @@ -77,8 +77,8 @@ def _constructor_from_mgr(self, mgr, axes) -> "RangeFrame | pd.DataFrame": # _from_mgr is pandas' own way of doing this (see DataFrame._constructor_from_mgr); # pandas-stubs does not declare it, hence the ignores. if not self._required_columns.issubset(axes[0]): - return pd.DataFrame._from_mgr(mgr, axes=axes) # noqa: SLF001 # pyright: ignore[reportAttributeAccessIssue] - return type(self)._from_mgr(mgr, axes=axes) # noqa: SLF001 # pyright: ignore[reportAttributeAccessIssue] + return pd.DataFrame._from_mgr(mgr, axes=axes) # noqa: SLF001 # ty: ignore[unresolved-attribute] + return type(self)._from_mgr(mgr, axes=axes) # noqa: SLF001 # ty: ignore[unresolved-attribute] def __str__( self, @@ -171,7 +171,7 @@ def count_overlaps( f1, f2 = factorize_binary(self, other, match_by) - return ruranges.numpy.count_overlaps( # type: ignore[attr-defined] + return ruranges.numpy.count_overlaps( self[START_COL].to_numpy(), self[END_COL].to_numpy(), other[START_COL].to_numpy(), @@ -279,14 +279,14 @@ def cluster_overlaps( match_by = arg_to_list(match_by) factorized = factorize(self, match_by) - cluster, idx = ruranges.numpy.cluster( # type: ignore[attr-defined] + cluster, idx = ruranges.numpy.cluster( self[START_COL].to_numpy(), self[END_COL].to_numpy(), factorized, slack, ) - res = self.take(idx).copy() # type: ignore[arg-type] + res = self.take(idx).copy() res.insert(res.shape[1], cluster_column, cluster) return _mypy_ensure_rangeframe(res) @@ -470,14 +470,14 @@ def max_disjoint_overlaps( factorized = factorize(self, match_by) - idx = ruranges.numpy.max_disjoint( # type: ignore[attr-defined] + idx = ruranges.numpy.max_disjoint( starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), groups=factorized, slack=slack, sort_output=preserve_input_order, ) - return _mypy_ensure_rangeframe(self.take(idx)) # type: ignore[arg-type] + return _mypy_ensure_rangeframe(self.take(idx)) def nearest_ranges( self, @@ -566,7 +566,7 @@ def nearest_ranges( ruranges = require_ruranges() f1, f2 = factorize_binary(self, other, match_by) - idx1, idx2, dist = ruranges.numpy.nearest( # type: ignore[attr-defined] + idx1, idx2, dist = ruranges.numpy.nearest( groups=f1, starts=self[START_COL].to_numpy(), ends=self[END_COL].to_numpy(), @@ -581,10 +581,10 @@ def nearest_ranges( sort_output=preserve_input_order, ) - left = self.take(idx1) # type: ignore[arg-type] + left = self.take(idx1) to_concat: list[pd.DataFrame | pd.Series] = [ left.reset_index(drop=True), - pd.DataFrame(other).take(idx2).add_suffix(suffix).reset_index(drop=True), # type: ignore[arg-type] + pd.DataFrame(other).take(idx2).add_suffix(suffix).reset_index(drop=True), ] if dist_col is not None: to_concat.append(pd.Series(dist, name=dist_col)) @@ -726,7 +726,7 @@ def sort_ranges( sort_descending=arg_to_list(sort_descending), ) idxs = sort_order(self, keys, descending, use_natsort=False) - return _mypy_ensure_rangeframe(self.take(idxs)) # type: ignore[arg-type] + return _mypy_ensure_rangeframe(self.take(idxs)) def subtract_overlaps( self: "RangeFrame", @@ -775,7 +775,7 @@ def subtract_overlaps( f1, f2 = factorize_binary(self, other, match_by) - idx, start, end = ruranges.numpy.subtract( # type: ignore[attr-defined] + idx, start, end = ruranges.numpy.subtract( self[START_COL].to_numpy(), self[END_COL].to_numpy(), other[START_COL].to_numpy(), @@ -785,7 +785,7 @@ def subtract_overlaps( sort_output=preserve_input_order, ) - output = self.take(idx).copy() # type: ignore[arg-type] + output = self.take(idx).copy() output[START_COL], output[END_COL] = start, end return _mypy_ensure_rangeframe(output) @@ -871,7 +871,7 @@ def ensure_valid_ranges(self) -> "RangeFrame": raise ValueError(msg) return self - def copy(self, *args, **kwargs) -> "RangeFrame": # pyright: ignore[reportIncompatibleMethodOverride] # noqa: D102 + def copy(self, *args, **kwargs) -> "RangeFrame": # noqa: D102 # ty: ignore[override-of-final-method] # a copy keeps every column, so pandas hands this straight back as our own class return self._rebuild_as_self(super().copy(*args, **kwargs)) @@ -898,14 +898,14 @@ def _rebuild_as_self(self, result: Any) -> Any: return result return self._constructor_with_fallback(result) - def drop(self, *args, **kwargs) -> "RangeFrame | pd.DataFrame | None": # type: ignore[override] # noqa: D102 + def drop(self, *args, **kwargs) -> "RangeFrame | pd.DataFrame | None": # noqa: D102 # ty: ignore[invalid-method-override] return self._rebuild_as_self(super().drop(*args, **kwargs)) def drop_and_return(self, *args: Any, **kwargs: Any) -> "RangeFrame | pd.DataFrame": # noqa: D102 kwargs["inplace"] = False return self._rebuild_as_self(super().drop(*args, **kwargs)) - def reindex(self, *args, **kwargs) -> "RangeFrame | pd.DataFrame": # noqa: D102 + def reindex(self, *args, **kwargs) -> "RangeFrame | pd.DataFrame": # noqa: D102 # ty: ignore[invalid-method-override] return self._rebuild_as_self(super().reindex(*args, **kwargs)) @@ -920,8 +920,8 @@ def _mypy_ensure_rangeframe(r: pd.DataFrame) -> "RangeFrame": def assert_valid_ranges(function: Callable, *args: "RangeFrame") -> None: """Raise ValueError because function is called on invalid ranges.""" if any(r.reasons_why_frame_is_invalid() for r in args): - is_not_lambda = function.__name__ != "" - function_repr = function.__name__ if is_not_lambda else inspect.getsource(function).strip() + name = getattr(function, "__name__", "") + function_repr = name if name != "" else inspect.getsource(function).strip() msg = f"Cannot perform function on invalid ranges (function was {function_repr})." raise ValueError(msg) diff --git a/pyranges1/readers.py b/pyranges1/readers.py index 38c8a9a8..31c7b5d8 100644 --- a/pyranges1/readers.py +++ b/pyranges1/readers.py @@ -6,7 +6,7 @@ import gtfreader import numpy as np import pandas as pd -from natsort import natsorted # type: ignore[import] +from natsort import natsorted from pyranges1.core.options import option_manager from pyranges1.core.pyranges_helpers import ensure_pyranges @@ -131,7 +131,7 @@ def from_string(s: str) -> "PyRanges": def _read_bed_region(path: "str | Path", region: str, columns: list[str]) -> "PyRanges": """Read the records of a tabix-indexed BED file that overlap `region`.""" try: - import pysam # type: ignore[import] + import pysam except ImportError: msg = "pysam must be installed to read a region of a BED file. Use `pip install pysam`." raise ImportError(msg) from None @@ -593,7 +593,7 @@ def read_bam( return _read_pysam_alignment(f, "rb", mapq, required_flag, filter_flag, None, sparse=sparse, region=region) path = Path(f) try: - import bamread # type: ignore[import] + import bamread except ImportError: msg = "bamread must be installed to read bam. Use `conda install -c bioconda bamread` or `pip install bamread` to install it." raise ImportError(msg) from None @@ -1093,7 +1093,7 @@ def read_bigwig(f: str | Path, *, region: str | None = None) -> "PyRanges": """ try: - import pyBigWig # type: ignore[import] + import pyBigWig except ModuleNotFoundError: msg = "pyBigWig must be installed to read bigwigs. Use `pip install pyBigWig` to install it." raise ImportError(msg) from None @@ -1216,7 +1216,7 @@ def _read_pysam_alignment( Unmapped reads are always skipped. """ try: - import pysam # type: ignore[import] + import pysam except ImportError: msg = ( "pysam must be installed to read SAM/CRAM files. " @@ -1447,7 +1447,7 @@ def read_bigbed(f: "str | Path", *, region: str | None = None) -> "PyRanges": """ try: - import pyBigWig # type: ignore[import] + import pyBigWig except ModuleNotFoundError: msg = "pyBigWig must be installed to read BigBed files. Use `pip install pyBigWig` to install it." raise ImportError(msg) from None @@ -2185,7 +2185,7 @@ def read_vcf( """ try: - import pysam # type: ignore[import] + import pysam except ImportError: msg = ( "pysam must be installed to read VCF/BCF files. "