Added type hints

This commit is contained in:
Andrew Murray 2024-08-15 08:08:43 +10:00
parent 497080f63b
commit 8afb7ddb4e
5 changed files with 45 additions and 35 deletions

View File

@ -5,6 +5,7 @@ ipython
numpy numpy
packaging packaging
pytest pytest
sphinx
types-defusedxml types-defusedxml
types-olefile types-olefile
types-setuptools types-setuptools

View File

@ -14,6 +14,7 @@ from __future__ import annotations
import struct import struct
from io import BytesIO from io import BytesIO
from typing import IO
from PIL import Image, ImageFile from PIL import Image, ImageFile
@ -94,26 +95,26 @@ DXT3_FOURCC = 0x33545844
DXT5_FOURCC = 0x35545844 DXT5_FOURCC = 0x35545844
def _decode565(bits): def _decode565(bits: int) -> tuple[int, int, int]:
a = ((bits >> 11) & 0x1F) << 3 a = ((bits >> 11) & 0x1F) << 3
b = ((bits >> 5) & 0x3F) << 2 b = ((bits >> 5) & 0x3F) << 2
c = (bits & 0x1F) << 3 c = (bits & 0x1F) << 3
return a, b, c return a, b, c
def _c2a(a, b): def _c2a(a: int, b: int) -> int:
return (2 * a + b) // 3 return (2 * a + b) // 3
def _c2b(a, b): def _c2b(a: int, b: int) -> int:
return (a + b) // 2 return (a + b) // 2
def _c3(a, b): def _c3(a: int, b: int) -> int:
return (2 * b + a) // 3 return (2 * b + a) // 3
def _dxt1(data, width, height): def _dxt1(data: IO[bytes], width: int, height: int) -> bytes:
# TODO implement this function as pixel format in decode.c # TODO implement this function as pixel format in decode.c
ret = bytearray(4 * width * height) ret = bytearray(4 * width * height)
@ -151,7 +152,7 @@ def _dxt1(data, width, height):
return bytes(ret) return bytes(ret)
def _dxtc_alpha(a0, a1, ac0, ac1, ai): def _dxtc_alpha(a0: int, a1: int, ac0: int, ac1: int, ai: int) -> int:
if ai <= 12: if ai <= 12:
ac = (ac0 >> ai) & 7 ac = (ac0 >> ai) & 7
elif ai == 15: elif ai == 15:
@ -175,7 +176,7 @@ def _dxtc_alpha(a0, a1, ac0, ac1, ai):
return alpha return alpha
def _dxt5(data, width, height): def _dxt5(data: IO[bytes], width: int, height: int) -> bytes:
# TODO implement this function as pixel format in decode.c # TODO implement this function as pixel format in decode.c
ret = bytearray(4 * width * height) ret = bytearray(4 * width * height)
@ -211,7 +212,7 @@ class DdsImageFile(ImageFile.ImageFile):
format = "DDS" format = "DDS"
format_description = "DirectDraw Surface" format_description = "DirectDraw Surface"
def _open(self): def _open(self) -> None:
if not _accept(self.fp.read(4)): if not _accept(self.fp.read(4)):
msg = "not a DDS file" msg = "not a DDS file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -242,19 +243,20 @@ class DdsImageFile(ImageFile.ImageFile):
elif fourcc == b"DXT5": elif fourcc == b"DXT5":
self.decoder = "DXT5" self.decoder = "DXT5"
else: else:
msg = f"Unimplemented pixel format {fourcc}" msg = f"Unimplemented pixel format {repr(fourcc)}"
raise NotImplementedError(msg) raise NotImplementedError(msg)
self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
def load_seek(self, pos): def load_seek(self, pos: int) -> None:
pass pass
class DXT1Decoder(ImageFile.PyDecoder): class DXT1Decoder(ImageFile.PyDecoder):
_pulls_fd = True _pulls_fd = True
def decode(self, buffer): def decode(self, buffer: bytes) -> tuple[int, int]:
assert self.fd is not None
try: try:
self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize)) self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize))
except struct.error as e: except struct.error as e:
@ -266,7 +268,8 @@ class DXT1Decoder(ImageFile.PyDecoder):
class DXT5Decoder(ImageFile.PyDecoder): class DXT5Decoder(ImageFile.PyDecoder):
_pulls_fd = True _pulls_fd = True
def decode(self, buffer): def decode(self, buffer: bytes) -> tuple[int, int]:
assert self.fd is not None
try: try:
self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize)) self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize))
except struct.error as e: except struct.error as e:
@ -279,7 +282,7 @@ Image.register_decoder("DXT1", DXT1Decoder)
Image.register_decoder("DXT5", DXT5Decoder) Image.register_decoder("DXT5", DXT5Decoder)
def _accept(prefix): def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"DDS " return prefix[:4] == b"DDS "

View File

@ -456,8 +456,11 @@ class IFDRational(Rational):
__int__ = _delegate("__int__") __int__ = _delegate("__int__")
def _register_loader(idx: int, size: int): _LoaderFunc = Callable[["ImageFileDirectory_v2", bytes, bool], Any]
def decorator(func):
def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc]:
def decorator(func: _LoaderFunc) -> _LoaderFunc:
from .TiffTags import TYPES from .TiffTags import TYPES
if func.__name__.startswith("load_"): if func.__name__.startswith("load_"):
@ -482,12 +485,13 @@ def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
idx, fmt, name = idx_fmt_name idx, fmt, name = idx_fmt_name
TYPES[idx] = name TYPES[idx] = name
size = struct.calcsize(f"={fmt}") size = struct.calcsize(f"={fmt}")
_load_dispatch[idx] = ( # noqa: F821
size, def basic_handler(
lambda self, data, legacy_api=True: ( self: ImageFileDirectory_v2, data: bytes, legacy_api: bool = True
self._unpack(f"{len(data) // size}{fmt}", data) ) -> tuple[Any, ...]:
), return self._unpack(f"{len(data) // size}{fmt}", data)
)
_load_dispatch[idx] = size, basic_handler # noqa: F821
_write_dispatch[idx] = lambda self, *values: ( # noqa: F821 _write_dispatch[idx] = lambda self, *values: ( # noqa: F821
b"".join(self._pack(fmt, value) for value in values) b"".join(self._pack(fmt, value) for value in values)
) )
@ -560,7 +564,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
""" """
_load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {} _load_dispatch: dict[int, tuple[int, _LoaderFunc]] = {}
_write_dispatch: dict[int, Callable[..., Any]] = {} _write_dispatch: dict[int, Callable[..., Any]] = {}
def __init__( def __init__(
@ -653,10 +657,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __contains__(self, tag: object) -> bool: def __contains__(self, tag: object) -> bool:
return tag in self._tags_v2 or tag in self._tagdata return tag in self._tags_v2 or tag in self._tagdata
def __setitem__(self, tag: int, value) -> None: def __setitem__(self, tag: int, value: Any) -> None:
self._setitem(tag, value, self.legacy_api) self._setitem(tag, value, self.legacy_api)
def _setitem(self, tag: int, value, legacy_api: bool) -> None: def _setitem(self, tag: int, value: Any, legacy_api: bool) -> None:
basetypes = (Number, bytes, str) basetypes = (Number, bytes, str)
info = TiffTags.lookup(tag, self.group) info = TiffTags.lookup(tag, self.group)
@ -744,10 +748,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __iter__(self) -> Iterator[int]: def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v2)) return iter(set(self._tagdata) | set(self._tags_v2))
def _unpack(self, fmt: str, data: bytes): def _unpack(self, fmt: str, data: bytes) -> tuple[Any, ...]:
return struct.unpack(self._endian + fmt, data) return struct.unpack(self._endian + fmt, data)
def _pack(self, fmt: str, *values) -> bytes: def _pack(self, fmt: str, *values: Any) -> bytes:
return struct.pack(self._endian + fmt, *values) return struct.pack(self._endian + fmt, *values)
list( list(
@ -824,7 +828,9 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value return value
@_register_loader(10, 8) @_register_loader(10, 8)
def load_signed_rational(self, data: bytes, legacy_api: bool = True): def load_signed_rational(
self, data: bytes, legacy_api: bool = True
) -> tuple[tuple[int, int] | IFDRational, ...]:
vals = self._unpack(f"{len(data) // 4}l", data) vals = self._unpack(f"{len(data) // 4}l", data)
def combine(a: int, b: int) -> tuple[int, int] | IFDRational: def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
@ -1088,7 +1094,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
def __iter__(self) -> Iterator[int]: def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v1)) return iter(set(self._tagdata) | set(self._tags_v1))
def __setitem__(self, tag: int, value) -> None: def __setitem__(self, tag: int, value: Any) -> None:
for legacy_api in (False, True): for legacy_api in (False, True):
self._setitem(tag, value, legacy_api) self._setitem(tag, value, legacy_api)

View File

@ -36,4 +36,4 @@ deps =
extras = extras =
typing typing
commands = commands =
mypy src Tests {posargs} mypy docs src winbuild Tests {posargs}

View File

@ -7,6 +7,7 @@ import re
import shutil import shutil
import struct import struct
import subprocess import subprocess
from typing import Any
def cmd_cd(path: str) -> str: def cmd_cd(path: str) -> str:
@ -43,21 +44,19 @@ def cmd_nmake(
target: str = "", target: str = "",
params: list[str] | None = None, params: list[str] | None = None,
) -> str: ) -> str:
params = "" if params is None else " ".join(params)
return " ".join( return " ".join(
[ [
"{nmake}", "{nmake}",
"-nologo", "-nologo",
f'-f "{makefile}"' if makefile is not None else "", f'-f "{makefile}"' if makefile is not None else "",
f"{params}", f'{" ".join(params)}' if params is not None else "",
f'"{target}"', f'"{target}"',
] ]
) )
def cmds_cmake( def cmds_cmake(
target: str | tuple[str, ...] | list[str], *params, build_dir: str = "." target: str | tuple[str, ...] | list[str], *params: str, build_dir: str = "."
) -> list[str]: ) -> list[str]:
if not isinstance(target, str): if not isinstance(target, str):
target = " ".join(target) target = " ".join(target)
@ -129,7 +128,7 @@ V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
# dependencies, listed in order of compilation # dependencies, listed in order of compilation
DEPS = { DEPS: dict[str, dict[str, Any]] = {
"libjpeg": { "libjpeg": {
"url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/" "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/"
f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download", f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download",
@ -538,7 +537,7 @@ def write_script(
print(" " + line) print(" " + line)
def get_footer(dep: dict) -> list[str]: def get_footer(dep: dict[str, Any]) -> list[str]:
lines = [] lines = []
for out in dep.get("headers", []): for out in dep.get("headers", []):
lines.append(cmd_copy(out, "{inc_dir}")) lines.append(cmd_copy(out, "{inc_dir}"))
@ -583,6 +582,7 @@ def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str:
license_text += f.read() license_text += f.read()
if "license_pattern" in dep: if "license_pattern" in dep:
match = re.search(dep["license_pattern"], license_text, re.DOTALL) match = re.search(dep["license_pattern"], license_text, re.DOTALL)
assert match is not None
license_text = "\n".join(match.groups()) license_text = "\n".join(match.groups())
assert len(license_text) > 50 assert len(license_text) > 50
with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f: with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f: