diff --git a/.github/workflows/pytest-builds.yml b/.github/workflows/pytest-builds.yml index a704dd9..298c0dd 100644 --- a/.github/workflows/pytest-builds.yml +++ b/.github/workflows/pytest-builds.yml @@ -2,7 +2,7 @@ name: unit-tests on: push: - branches: [ master ] + branches: [ main ] pull_request: jobs: @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/pylibjpeg/tools/jpegio.py b/pylibjpeg/tools/jpegio.py index 2c2da21..f3f1360 100644 --- a/pylibjpeg/tools/jpegio.py +++ b/pylibjpeg/tools/jpegio.py @@ -1,6 +1,7 @@ import logging import os -from typing import BinaryIO +from pathlib import Path +from typing import BinaryIO, Union, cast from .s10918 import parse, JPEG @@ -33,16 +34,18 @@ def get_specification(fp: BinaryIO) -> str: ) -def jpgread(path: str | os.PathLike[str] | BinaryIO) -> JPEG: +def jpgread(path: Union[str, os.PathLike[str], BinaryIO]) -> JPEG: """Return a represention of the JPEG file at `fpath`.""" LOGGER.debug(f"Reading file: {path}") - if isinstance(path, str | os.PathLike[str]): + if not hasattr(path, "read"): + path = cast(str, path) with open(path, "rb") as fp: jpg_format = get_specification(fp) parser, jpg_class = PARSERS[jpg_format] meta = parser(fp) LOGGER.debug("File parsed successfully") else: + path = cast(BinaryIO, path) jpg_format = get_specification(path) parser, jpg_class = PARSERS[jpg_format] meta = parser(path) diff --git a/pylibjpeg/tools/s10918/_markers.py b/pylibjpeg/tools/s10918/_markers.py index 66b43a4..1788476 100644 --- a/pylibjpeg/tools/s10918/_markers.py +++ b/pylibjpeg/tools/s10918/_markers.py @@ -1,11 +1,11 @@ """JPEG 10918 markers""" -from typing import Callable +from typing import Callable, Dict, Tuple, Union from ._parsers import APP, COM, DAC, DHT, DNL, DQT, DRI, EXP, SOF, SOS -MARKERS: dict[int, tuple[str, str, None | Callable]] = {} +MARKERS: Dict[int, Tuple[str, str, Union[None, Callable]]] = {} # JPEG reserved markers for _marker in range(0xFF02, 0xFFBF + 1): MARKERS[_marker] = ("RES", "Reserved", None) diff --git a/pylibjpeg/tools/s10918/_parsers.py b/pylibjpeg/tools/s10918/_parsers.py index 2958a3a..0293c2e 100644 --- a/pylibjpeg/tools/s10918/_parsers.py +++ b/pylibjpeg/tools/s10918/_parsers.py @@ -51,12 +51,12 @@ """ from struct import unpack -from typing import BinaryIO, Any, cast +from typing import BinaryIO, Any, cast, Dict, Union, List, Tuple from pylibjpeg.tools.utils import split_byte -def APP(fp: BinaryIO) -> dict[str, int | bytes]: +def APP(fp: BinaryIO) -> Dict[str, Union[int, bytes]]: """Return a dict containing APP data. See ISO/IEC 10918-1 Section B.2.4.6. @@ -83,7 +83,7 @@ def APP(fp: BinaryIO) -> dict[str, int | bytes]: return {"Lp": length, "Ap": fp.read(length - 2)} -def COM(fp: BinaryIO) -> dict[str, int | str]: +def COM(fp: BinaryIO) -> Dict[str, Union[int, str]]: """Return a dict containing COM data. See ISO/IEC 10918-1 Section B.2.4.5. @@ -111,7 +111,7 @@ def COM(fp: BinaryIO) -> dict[str, int | str]: return {"Lc": length, "Cm": comment} -def DAC(fp: BinaryIO) -> dict[str, int | list[int]]: +def DAC(fp: BinaryIO) -> Dict[str, Union[int, List[int]]]: """Return a dict containing DAC segment data. See ISO/IEC 10918-1 Section B.2.4.3. @@ -150,7 +150,7 @@ def DAC(fp: BinaryIO) -> dict[str, int | list[int]]: return {"La": length, "Tc": tc, "Tb": tb, "Cs": cs} -def DHT(fp: BinaryIO) -> dict[str, int | list[int] | Any]: +def DHT(fp: BinaryIO) -> Dict[str, Union[int, List[int], Any]]: """Return a dict containing DHT segment data. See ISO/IEC 10918-1 Section B.2.4.2. @@ -182,7 +182,7 @@ def DHT(fp: BinaryIO) -> dict[str, int | list[int] | Any]: bytes_to_read = length - 2 tc, th, li = [], [], [] - vij: dict[tuple[int, int], dict[int, tuple[int]]] = {} + vij: Dict[Tuple[int, int], Dict[int, Tuple[int]]] = {} while bytes_to_read > 0: _tc, _th = split_byte(fp.read(1)) tc.append(_tc) @@ -209,7 +209,7 @@ def DHT(fp: BinaryIO) -> dict[str, int | list[int] | Any]: return {"Lh": length, "Tc": tc, "Th": th, "Li": li, "Vij": vij} -def DNL(fp: BinaryIO) -> dict[str, int | list[int]]: +def DNL(fp: BinaryIO) -> Dict[str, Union[int, List[int]]]: """Return a dict containing DNL segment data. See ISO/IEC 10918-1 Section B.2.5. @@ -237,7 +237,7 @@ def DNL(fp: BinaryIO) -> dict[str, int | list[int]]: return {"Ld": length, "NL": nr_lines} -def DQT(fp: BinaryIO) -> dict[str, int | list[int] | list[list[int]]]: +def DQT(fp: BinaryIO) -> Dict[str, Union[int, List[int], List[List[int]]]]: """Return a dict containing DQT segment data. See ISO/IEC 10918-1 Section B.2.4.1. @@ -290,7 +290,7 @@ def DQT(fp: BinaryIO) -> dict[str, int | list[int] | list[list[int]]]: return {"Lq": length, "Pq": pq, "Tq": tq, "Qk": qk} -def DRI(fp: BinaryIO) -> dict[str, int]: +def DRI(fp: BinaryIO) -> Dict[str, int]: """Return a dict containing DRI segment data. See ISO/IEC 10918-1 Section B.2.4.4. @@ -315,7 +315,7 @@ def DRI(fp: BinaryIO) -> dict[str, int]: return {"Lr": unpack(">H", fp.read(2))[0], "Ri": unpack(">H", fp.read(2))[0]} -def EXP(fp: BinaryIO) -> dict[str, int]: +def EXP(fp: BinaryIO) -> Dict[str, int]: """Return a dict containing EXP segment data. See ISO/IEC 10918-1 Section B.3.3. @@ -344,7 +344,7 @@ def EXP(fp: BinaryIO) -> dict[str, int]: return {"Le": length, "Eh": eh, "Ev": ev} -def SOF(fp: BinaryIO) -> dict[str, int | dict[int, dict[str, int]]]: +def SOF(fp: BinaryIO) -> Dict[str, Union[int, Dict[int, Dict[str, int]]]]: """Return a dict containing SOF header data. See ISO/IEC 10918-1 Section B.2.2. @@ -405,7 +405,7 @@ def SOF(fp: BinaryIO) -> dict[str, int | dict[int, dict[str, int]]]: } -def SOS(fp: BinaryIO) -> dict[str, int | list[int]]: +def SOS(fp: BinaryIO) -> Dict[str, Union[int, List[int]]]: """Return a dict containing SOS header data. See ISO/IEC 10918-1 Section B.2.3. diff --git a/pylibjpeg/tools/s10918/_printers.py b/pylibjpeg/tools/s10918/_printers.py index c85022d..82d8d9f 100644 --- a/pylibjpeg/tools/s10918/_printers.py +++ b/pylibjpeg/tools/s10918/_printers.py @@ -18,7 +18,7 @@ """ from struct import unpack -from typing import Any, cast +from typing import Any, cast, Tuple, Dict, Union ZIGZAG = [ @@ -99,7 +99,7 @@ } -def _print_app(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_app(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for an APP segment.""" _, _, sub = info @@ -160,7 +160,7 @@ def _print_app(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_com(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_com(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a COM segment.""" _m, fill, sub = info @@ -182,7 +182,7 @@ def _print_com(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dac(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dac(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DAC segment.""" m_bytes, fill, sub = info @@ -193,7 +193,7 @@ def _print_dac(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dhp(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dhp(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DHP segment.""" m_bytes, fill, sub = info @@ -219,7 +219,7 @@ def _print_dhp(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dht(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dht(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DHT segment.""" m_bytes, fill, sub = info @@ -248,7 +248,7 @@ def _print_dht(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dqt(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dqt(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DQT segment.""" m_bytes, fill, sub = info @@ -280,7 +280,7 @@ def _print_dqt(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dnl(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dnl(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DNL segment.""" m_bytes, fill, sub = info @@ -291,7 +291,7 @@ def _print_dnl(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_dri(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_dri(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a DRI segment.""" m_bytes, fill, sub = info @@ -302,7 +302,7 @@ def _print_dri(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_eoi(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_eoi(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for an EOI segment.""" m_bytes, fill, sub = info header = f"{marker} marker at offset {offset}" @@ -311,7 +311,7 @@ def _print_eoi(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_exp(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_exp(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for an EXP segment.""" m_bytes, fill, sub = info @@ -322,7 +322,7 @@ def _print_exp(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_sof(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_sof(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a SOF segment.""" m_bytes, fill, sub = info @@ -368,7 +368,7 @@ def _print_sof(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) return "\n".join(ss) -def _print_soi(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) -> str: +def _print_soi(marker: str, offset: int, info: Tuple[int, int, Dict[str, Any]]) -> str: """String output for a SOI segment.""" m_bytes, fill, sub = info @@ -381,7 +381,7 @@ def _print_soi(marker: str, offset: int, info: tuple[int, int, dict[str, Any]]) def _print_sos( marker: str, offset: int, - info: tuple[int, int, dict[str | tuple[str, int], Any]], + info: Tuple[int, int, Dict[Union[str, Tuple[str, int]], Any]], ) -> str: """String output for a SOS segment.""" m_bytes, fill, sub = info @@ -408,7 +408,7 @@ def _print_sos( ss.append(f"\n{' ENC marker at offset {key[1]}':.^63}") ss.append(f"\n{len(sub[key])} bytes of entropy-coded data") else: - (name, offset) = cast(tuple[str, int], key) + (name, offset) = cast(Tuple[str, int], key) ss.append(f"{offset:<7}{name}(FFD{name[-1]})") return "\n".join(ss) diff --git a/pylibjpeg/tools/s10918/io.py b/pylibjpeg/tools/s10918/io.py index 05d1039..310ac7e 100644 --- a/pylibjpeg/tools/s10918/io.py +++ b/pylibjpeg/tools/s10918/io.py @@ -2,7 +2,7 @@ import logging from struct import unpack -from typing import BinaryIO, Any, Callable, cast +from typing import BinaryIO, Any, Callable, cast, Dict, Tuple from ._markers import MARKERS @@ -10,7 +10,7 @@ LOGGER = logging.getLogger(__name__) -def parse(fp: BinaryIO) -> dict[tuple[str, int], Any]: +def parse(fp: BinaryIO) -> Dict[Tuple[str, int], Any]: """Return a JPEG but don't decode yet.""" _fill_bytes = 0 while fp.read(1) == b"\xff": @@ -23,7 +23,7 @@ def parse(fp: BinaryIO) -> dict[tuple[str, int], Any]: if fp.read(2) != b"\xFF\xD8": raise ValueError("SOI marker not found") - info: dict[tuple[str, int], tuple[int, int, Any]] = { + info: Dict[Tuple[str, int], Tuple[int, int, Any]] = { ("SOI", fp.tell() - 2): (unpack(">H", b"\xFF\xD8")[0], _fill_bytes, {}) } diff --git a/pylibjpeg/tools/s10918/rep.py b/pylibjpeg/tools/s10918/rep.py index 92753b4..a313818 100644 --- a/pylibjpeg/tools/s10918/rep.py +++ b/pylibjpeg/tools/s10918/rep.py @@ -1,4 +1,4 @@ -from typing import Any, cast +from typing import Any, cast, Dict, Tuple, List from ._printers import PRINTERS @@ -53,7 +53,7 @@ class JPEG: """ - def __init__(self, meta: dict[tuple[str, int], Any]) -> None: + def __init__(self, meta: Dict[Tuple[str, int], Any]) -> None: """Initialise a new JPEG. Parameters @@ -75,7 +75,7 @@ def columns(self) -> int: "marker was found" ) - def get_keys(self, name: str) -> list[Any]: + def get_keys(self, name: str) -> List[Any]: """Return a list of keys with marker containing `name`.""" return [mm for mm in self._keys if name in mm[0]] @@ -161,12 +161,12 @@ def is_sequential(self) -> bool: return not self.is_hierarchical @property - def _keys(self) -> list[tuple[str, int]]: + def _keys(self) -> List[Tuple[str, int]]: """Return a list of the info keys, ordered by offset.""" return sorted(self.info.keys(), key=lambda x: x[1]) @property - def markers(self) -> list[str]: + def markers(self) -> List[str]: """Return a list of the found JPEG markers, ordered by offset.""" return [mm[0] for mm in self._keys] diff --git a/pylibjpeg/tools/utils.py b/pylibjpeg/tools/utils.py index 30fa7e0..bd147d4 100644 --- a/pylibjpeg/tools/utils.py +++ b/pylibjpeg/tools/utils.py @@ -1,5 +1,7 @@ """Utility functions.""" +from typing import Tuple + def get_bit(byte: bytes, index: int) -> int: """Return the value of the bit at `index` of `byte`. @@ -24,7 +26,7 @@ def get_bit(byte: bytes, index: int) -> int: return (value >> (7 - index)) & 1 -def split_byte(byte: bytes) -> tuple[int, int]: +def split_byte(byte: bytes) -> Tuple[int, int]: """Return the 8-bit `byte` as two 4-bit unsigned integers. Parameters diff --git a/pylibjpeg/utils.py b/pylibjpeg/utils.py index 4fd4d91..cfe597d 100644 --- a/pylibjpeg/utils.py +++ b/pylibjpeg/utils.py @@ -1,9 +1,9 @@ import logging import os +import sys -# from pkg_resources import iter_entry_points from importlib import metadata -from typing import BinaryIO, Any, Protocol +from typing import BinaryIO, Any, Protocol, Union, Dict import numpy as np @@ -11,7 +11,7 @@ LOGGER = logging.getLogger(__name__) -DecodeSource = str | os.PathLike[str] | BinaryIO | bytes +DecodeSource = Union[str, os.PathLike[str], BinaryIO, bytes] class Decoder(Protocol): @@ -31,7 +31,7 @@ def __call__(self, src: bytes, **kwargs: Any) -> np.ndarray: class Encoder(Protocol): - def __call__(self, src: np.ndarray, **kwargs: Any) -> bytes | bytearray: + def __call__(self, src: np.ndarray, **kwargs: Any) -> Union[bytes, bytearray]: ... # pragma: no cover @@ -102,7 +102,7 @@ def decode(data: DecodeSource, decoder: str = "", **kwargs: Any) -> np.ndarray: raise ValueError("Unable to decode the data") -def get_decoders(decoder_type: str = "") -> dict[str, Decoder]: +def get_decoders(decoder_type: str = "") -> Dict[str, Decoder]: """Return a :class:`dict` of JPEG decoders as {package: callable}. Parameters @@ -125,8 +125,22 @@ def get_decoders(decoder_type: str = "") -> dict[str, Decoder]: dict A dict of ``{'package_name': }``. """ - # print(metadata.entry_points()) - # Return all decoders + # TODO: Python 3.10 remove + if sys.version_info[:2] < (3, 10): + ep = metadata.entry_points() + if not decoder_type: + decoders = {} + for entry_point in DECODER_ENTRY_POINTS.values(): + if entry_point in ep: + decoders.update({val.name: val.load() for val in ep[entry_point]}) + + return decoders + + if decoder_type in ep: + return {val.name: val.load() for val in ep[decoder_type]} + + raise ValueError(f"Unknown decoder_type '{decoder_type}'") + if not decoder_type: decoders = {} for entry_point in DECODER_ENTRY_POINTS.values(): @@ -142,15 +156,26 @@ def get_decoders(decoder_type: str = "") -> dict[str, Decoder]: raise ValueError(f"Unknown decoder_type '{decoder_type}'") -def get_pixel_data_decoders() -> dict[str, Decoder]: +def get_pixel_data_decoders() -> Dict[str, Decoder]: """Return a :class:`dict` of ``{UID: callable}``.""" + # TODO: Python 3.10 remove + if sys.version_info[:2] < (3, 10): + ep = metadata.entry_points() + if "pylibjpeg.pixel_data_decoders" in ep: + return { + val.name: val.load() + for val in ep["pylibjpeg.pixel_data_decoders"] + } + + return {} + return { val.name: val.load() for val in metadata.entry_points(group="pylibjpeg.pixel_data_decoders") } -def _encode(arr: np.ndarray, encoder: str = "", **kwargs: Any) -> bytes | bytearray: +def _encode(arr: np.ndarray, encoder: str = "", **kwargs: Any) -> Union[bytes, bytearray]: """Return the encoded `arr` as a :class:`bytes`. .. versionadded:: 1.3.0 @@ -197,7 +222,7 @@ def _encode(arr: np.ndarray, encoder: str = "", **kwargs: Any) -> bytes | bytear raise ValueError("Unable to encode the data") -def get_encoders(encoder_type: str = "") -> dict[str, Encoder]: +def get_encoders(encoder_type: str = "") -> Dict[str, Encoder]: """Return a :class:`dict` of JPEG encoders as {package: callable}. .. versionadded:: 1.3.0 @@ -222,26 +247,53 @@ def get_encoders(encoder_type: str = "") -> dict[str, Encoder]: dict A dict of ``{'package_name': }``. """ + # TODO: Python 3.10 remove + if sys.version_info[:2] < (3, 10): + ep = metadata.entry_points() + if not encoder_type: + encoders = {} + for entry_point in ENCODER_ENTRY_POINTS.values(): + if entry_point in ep: + encoders.update({val.name: val.load() for val in ep[entry_point]}) + + return encoders + + if encoder_type in ep: + return {val.name: val.load() for val in ep[encoder_type]} + + raise ValueError(f"Unknown decoder_type '{encoders}'") + if not encoder_type: encoders = {} for entry_point in ENCODER_ENTRY_POINTS.values(): - result = metadata.entry_points(group=entry_point) + result = metadata.entry_points().select(group=entry_point) encoders.update({val.name: val.load() for val in result}) return encoders try: - result = metadata.entry_points(group=ENCODER_ENTRY_POINTS[encoder_type]) + result = metadata.entry_points().select(group=ENCODER_ENTRY_POINTS[encoder_type]) return {val.name: val.load() for val in result} except KeyError: raise ValueError(f"Unknown encoder_type '{encoder_type}'") -def get_pixel_data_encoders() -> dict[str, Encoder]: +def get_pixel_data_encoders() -> Dict[str, Encoder]: """Return a :class:`dict` of ``{UID: callable}``. .. versionadded:: 1.3.0 """ + # TODO: Python 3.10 remove + if sys.version_info[:2] < (3, 10): + ep = metadata.entry_points() + if "pylibjpeg.pixel_data_encoders" in ep: + return { + val.name: val.load() + for val in ep["pylibjpeg.pixel_data_encoders"] + } + + return {} + return { val.name: val.load() for val in metadata.entry_points(group="pylibjpeg.pixel_data_encoders") diff --git a/pyproject.toml b/pyproject.toml index 0491775..e05cfe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,8 @@ classifiers=[ "Intended Audience :: Science/Research", "Development Status :: 5 - Production/Stable", "Natural Language :: English", +"Programming Language :: Python :: 3.8", +"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -20,7 +22,7 @@ classifiers=[ "Topic :: Scientific/Engineering :: Medical Science Apps.", "Topic :: Software Development :: Libraries", ] -dependencies = ["numpy"] +dependencies = ["numpy", "typing_extension"] description = """\ A Python framework for decoding JPEG and decoding/encoding DICOM\ RLE data, with a focus on supporting pydicom\ @@ -29,7 +31,7 @@ keywords = ["dicom, pydicom, python, imaging jpg jpeg jpg-ls jpeg-ls jpeg2k jpeg license = {text = "MIT"} name = "pylibjpeg" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.8" version = "2.0.0.dev0" @@ -40,6 +42,20 @@ dev = [ "pytest", "pytest-cov", ] +rle = [ + "pylibjpeg-rle" +] +openjpeg = [ + "pylibjpeg-openjpeg" +] +libjpeg = [ + "pylibjpeg-libjpeg" +] +all = [ + "pylibjpeg-rle", + "pylibjpeg-openjpeg", + "pylibjpeg-libjpeg", +] [project.urls] @@ -49,14 +65,14 @@ repository = "https://github.com/pydicom/pylibjpeg" [tool.mypy] -python_version = "3.10" +python_version = "3.8" files = "pylibjpeg" exclude = ["pylibjpeg/tests", "pylibjpeg/tools/tests"] show_error_codes = true warn_redundant_casts = true warn_unused_ignores = true warn_return_any = true -warn_unreachable = false +warn_unreachable = true ignore_missing_imports = true disallow_untyped_calls = true disallow_untyped_defs = true