Skip to content

Commit

Permalink
chore: Update mypy config (#134)
Browse files Browse the repository at this point in the history
* chore: Update mypy config

* chore: rm unused type ignores

* chore: rm redundant cast

* chore: ignore unreachable false positive

* chore: fix any-return

* chore: fix untyped-call

* chore: fix no-any-return

* doc: clarify cast
  • Loading branch information
msto authored Jun 12, 2024
1 parent 520e508 commit cd554be
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 24 deletions.
20 changes: 15 additions & 5 deletions ci/mypy.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
[mypy]
strict_optional = False
ignore_missing_imports = True
disallow_untyped_decorators = False
follow_imports = silent
disallow_untyped_defs = True
python_version = 3.8
strict_optional = false
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_no_return = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true

2 changes: 0 additions & 2 deletions fgpyo/fastx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from typing import Tuple
from typing import Type
from typing import Union
from typing import cast

from pysam import FastxFile
from pysam import FastxRecord
Expand Down Expand Up @@ -77,7 +76,6 @@ def __next__(self) -> Tuple[FastxRecord, ...]:
)
)
else:
records = cast(Tuple[FastxRecord, ...], records)
record_names: Set[str] = {self._name_minus_ordinal(record.name) for record in records}
if len(record_names) != 1:
raise ValueError(f"FASTX record names do not all match: {record_names}")
Expand Down
10 changes: 5 additions & 5 deletions fgpyo/sam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def _pysam_open(
:class:`~pysam.AlignmentFile`; may not include "mode".
"""

if isinstance(path, (str, Path)): # type: ignore
if isinstance(path, (str, Path)):
if str(path) in _STDIN_PATHS and open_for_reading:
path = sys.stdin
elif str(path) in _STDOUT_PATHS and not open_for_reading:
Expand Down Expand Up @@ -505,23 +505,23 @@ def from_cigarstring(cls, cigarstring: str) -> "Cigar":
i = 0
while i < cigarstring_length:
if not cigarstring[i].isdigit():
raise cls._pretty_cigarstring_exception(cigarstring, i) # type: ignore
raise cls._pretty_cigarstring_exception(cigarstring, i)
length = int(cigarstring[i])
i += 1
while i < cigarstring_length and cigarstring[i].isdigit():
length = (length * 10) + int(cigarstring[i])
i += 1
if i == cigarstring_length:
raise cls._pretty_cigarstring_exception(cigarstring, i) # type: ignore
raise cls._pretty_cigarstring_exception(cigarstring, i)
try:
operator = CigarOp.from_character(cigarstring[i])
elements.append(CigarElement(length, operator))
except KeyError as ex:
# cigar operator was not valid
raise cls._pretty_cigarstring_exception(cigarstring, i) from ex # type: ignore
raise cls._pretty_cigarstring_exception(cigarstring, i) from ex
except IndexError as ex:
# missing cigar operator (i == len(cigarstring))
raise cls._pretty_cigarstring_exception(cigarstring, i) from ex # type: ignore
raise cls._pretty_cigarstring_exception(cigarstring, i) from ex
i += 1
return Cigar(tuple(elements))

Expand Down
13 changes: 9 additions & 4 deletions fgpyo/sam/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from typing import List
from typing import Optional
from typing import Tuple
from typing import cast

import pysam
from pysam import AlignedSegment
Expand Down Expand Up @@ -154,7 +155,7 @@ def _next_name(self) -> str:

def _bases(self, length: int) -> str:
"""Returns a random string of bases of the length requested."""
return "".join(self._random.choices("ACGT", k=length)) # type: ignore
return "".join(self._random.choices("ACGT", k=length))

def _new_rec(
self,
Expand Down Expand Up @@ -334,13 +335,17 @@ def _set_mate_info(self, r1: pysam.AlignedSegment, r2: pysam.AlignedSegment) ->

def rg(self) -> Dict[str, Any]:
"""Returns the single read group that is defined in the header."""
rgs = self._header["RG"]
# The `RG` field contains a list of read group mappings
# e.g. `[{"ID": "rg1", "PL": "ILLUMINA"}]`
rgs = cast(List[Dict[str, Any]], self._header["RG"])
assert len(rgs) == 1, "Header did not contain exactly one read group!"
return rgs[0]

def rg_id(self) -> str:
"""Returns the ID of the single read group that is defined in the header."""
return self.rg()["ID"]
# The read group mapping has mixed types of values (e.g. "PI" is numeric), but the "ID"
# field is always a string.
return cast(str, self.rg()["ID"])

def add_pair(
self,
Expand Down Expand Up @@ -585,7 +590,7 @@ def to_path(
file_handle = fp.file

with sam.writer(
file_handle, header=self._samheader, file_type=sam.SamFileType.BAM # type: ignore
file_handle, header=self._samheader, file_type=sam.SamFileType.BAM
) as writer:
for rec in self._records:
if pred(rec):
Expand Down
6 changes: 5 additions & 1 deletion fgpyo/sam/tests/test_sam.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,11 @@ def test_pretty_cigarstring_exception() -> None:
expected = "10M5[U]4M"
with pytest.raises(CigarParsingException, match=r".*Malformed cigar") as ex:
raise Cigar._pretty_cigarstring_exception(cigar, index)
assert expected in str(ex)

# Known issue, `mypy` thinks the `raise` above makes the following unreachable
# https://github.com/python/mypy/issues/8985
# https://github.com/python/mypy/issues/8766
assert expected in str(ex) # type: ignore[unreachable]

expected = cigar + "[]"
with pytest.raises(CigarParsingException, match=r".*Malformed cigar") as ex:
Expand Down
12 changes: 6 additions & 6 deletions fgpyo/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
from attr import fields as get_attr_fields
from attr import fields_dict as get_attr_fields_dict

Attribute: TypeAlias = attr.Attribute # type: ignore[name-defined, no-redef]
Attribute: TypeAlias = attr.Attribute # type: ignore[name-defined]
# dataclasses and attr have internal tokens for missing values, join into a set so that we can
# check if a value is missing without knowing the type of backing class
MISSING = frozenset({DATACLASSES_MISSING, attr.NOTHING})
except ImportError: # pragma: no cover
_use_attr = False
attr = None
Attribute: TypeAlias = TypeVar("Attribute", bound=object) # type: ignore[misc, assignment, no-redef] # noqa: E501
Attribute: TypeAlias = TypeVar("Attribute", bound=object) # type: ignore[misc, no-redef] # noqa: E501

# define empty placeholders for getting attr fields as a tuple or dict. They will never be
# called because the import failed; but they're here to ensure that the function is defined in
Expand Down Expand Up @@ -85,7 +85,7 @@ class AttrsInstance(Protocol): # type: ignore[no-redef]
__attrs_attrs__: ClassVar[Any]


def is_attr_class(cls: type) -> bool: # type: ignore[arg-type]
def is_attr_class(cls: type) -> bool:
"""Return True if the class is an attr class, and False otherwise"""
return hasattr(cls, "__attrs_attrs__")

Expand Down Expand Up @@ -396,7 +396,7 @@ def get_fields_dict(
) -> Mapping[str, FieldType]:
"""Get the fields dict from either a dataclasses or attr dataclass (or instance)"""
if is_dataclasses_class(cls):
return _get_dataclasses_fields_dict(cls) # type: ignore[arg-type]
return _get_dataclasses_fields_dict(cls)
elif is_attr_class(cls): # type: ignore[arg-type]
return get_attr_fields_dict(cls) # type: ignore[arg-type]
else:
Expand All @@ -408,9 +408,9 @@ def get_fields(
) -> Tuple[FieldType, ...]:
"""Get the fields tuple from either a dataclasses or attr dataclass (or instance)"""
if is_dataclasses_class(cls):
return get_dataclasses_fields(cls) # type: ignore[arg-type]
return get_dataclasses_fields(cls)
elif is_attr_class(cls): # type: ignore[arg-type]
return get_attr_fields(cls) # type: ignore[arg-type]
return get_attr_fields(cls) # type: ignore[arg-type, no-any-return]
else:
raise TypeError("cls must a dataclasses or attr class")

Expand Down
3 changes: 3 additions & 0 deletions fgpyo/util/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(
verb: str = "Read",
unit: int = 100000,
) -> None:
self.printer: Callable[[str], Any]
if isinstance(printer, Logger):
self.printer = lambda s: printer.info(s)
else:
Expand Down Expand Up @@ -187,6 +188,8 @@ def _log(

self.printer(f"{self.verb} {self.count:,d} {self.noun}: {coordinate}")

return None

def log_last(
self,
) -> bool:
Expand Down
5 changes: 4 additions & 1 deletion fgpyo/util/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Type
from typing import TypeVar
from typing import Union
from typing import cast

UnionType = TypeVar("UnionType", bound="Union")
EnumType = TypeVar("EnumType", bound="Enum")
Expand Down Expand Up @@ -119,7 +120,9 @@ def _make_literal_parser_worker(
for arg, p in zip(typing.get_args(literal), parsers):
try:
if p(value) == arg:
return arg
# typing.get_args returns `Any` because there's no guarantee on the input type, but
# if we're passing it a literal then the returned args will always be `LiteralType`
return cast(LiteralType, arg)
except ValueError:
pass
raise InspectException(
Expand Down

0 comments on commit cd554be

Please sign in to comment.