Skip to content

Commit 246b922

Browse files
committed
qa: Add typing annotations and run mypy in "quality" CI job
1 parent 3a2e11f commit 246b922

File tree

8 files changed

+44
-24
lines changed

8 files changed

+44
-24
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ docs:
1111
quality:
1212
black --check .
1313
isort --check-only --diff .
14-
pylint --reports=no --score=no setup.py src tests
14+
mypy run.py src tests
15+
pylint --reports=no --score=no setup.py run.py src tests
1516
python setup.py check --strict --metadata --restructuredtext
1617
check-manifest
1718
python setup.py sdist >/dev/null 2>&1 && twine check dist/*

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
[tool.mypy]
2+
show_error_codes = true
3+
strict = true
4+
strict_equality_for_none = true
5+
warn_unreachable = true
6+
7+
[[tool.mypy.overrides]]
8+
module = "tests.*"
9+
# Don't force us to type test functions, it's boring and useless.
10+
disallow_untyped_defs = false
11+
112

213
[tool.pylint.MASTER]
314
load-plugins = []

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ sphinx_rtd_theme
99
# Tests and Quality
1010
black
1111
check-manifest
12+
mypy
1213
pylint
1314
pytest
1415
vulture

run.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#!/usr/bin/python
22
import argparse
3-
import sys
43

54
from PIL import Image
65

76
import zbarlight
87

98

10-
def main():
9+
def main() -> None:
1110
parser = argparse.ArgumentParser()
1211
parser.add_argument("image", help="input image")
1312
args = parser.parse_args()

src/zbarlight/__init__.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import importlib.metadata
2+
import typing
23
import warnings
34

45
from PIL import Image
56

6-
from . import compat
7+
from ._zbarlight import Symbologies
8+
from ._zbarlight import zbar_code_scanner
79

8-
# FIXME: pylint is capable of reading .pyi stub files, which we'll
9-
# create when we set up a type checker. Remove the following pragmas
10-
# once it's done.
11-
from ._zbarlight import Symbologies # pylint: disable=no-name-in-module
12-
from ._zbarlight import zbar_code_scanner # pylint: disable=no-name-in-module
1310

1411
__version__ = importlib.metadata.version("zbarlight")
1512
__ALL__ = [
@@ -29,7 +26,7 @@ class UnknownSymbologieError(Exception):
2926
pass
3027

3128

32-
def scan_codes(code_types, image):
29+
def scan_codes(code_types: str | list[str], image: Image.Image) -> list[bytes] | None:
3330
"""
3431
Get *code_type* codes from a PIL Image.
3532
@@ -69,9 +66,10 @@ def scan_codes(code_types, image):
6966
if code_type.upper() not in Symbologies
7067
]
7168
raise UnknownSymbologieError("Unknown Symbologies: %s" % bad_code_types)
69+
# mypy cannot narrow the type, despite our membership check above.
70+
if not _has_only_integers(symbologies):
71+
raise RuntimeError("should not happen")
7272

73-
if not compat.is_image(image):
74-
raise RuntimeError("Bad or unknown image format")
7573
# Convert image to gray scale (8 bits per pixel).
7674
converted_image = image.convert("L")
7775
raw = converted_image.tobytes()
@@ -80,7 +78,9 @@ def scan_codes(code_types, image):
8078
return zbar_code_scanner(symbologies, raw, width, height)
8179

8280

83-
def copy_image_on_background(image, color=WHITE):
81+
def copy_image_on_background(
82+
image: Image.Image, color: tuple[int, int, int] = WHITE
83+
) -> Image.Image:
8484
"""
8585
Create a new image by copying the image on a *color* background.
8686
@@ -95,3 +95,7 @@ def copy_image_on_background(image, color=WHITE):
9595
background = Image.new("RGB", image.size, color)
9696
background.paste(image, mask=image.split()[3])
9797
return background
98+
99+
100+
def _has_only_integers(values: list[int | None]) -> typing.TypeGuard[list[int]]:
101+
return None not in values

src/zbarlight/_zbarlight.pyi

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import typing
2+
3+
SymbologyValue: typing.TypeAlias = int
4+
SymbologiesType: typing.TypeAlias = dict[str, SymbologyValue]
5+
6+
Symbologies: SymbologiesType
7+
8+
def zbar_code_scanner(
9+
symbologies: list[SymbologyValue],
10+
raw: bytes,
11+
width: int,
12+
height: int,
13+
) -> list[bytes] | None: ...

src/zbarlight/compat.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/test_scan_codes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import zbarlight
77

88

9-
def get_image(name, ext="png"):
9+
def get_image(name: str, ext: str = "png") -> Image.Image:
1010
directory = pathlib.Path(__file__).parent
1111
file_path = directory / "fixtures" / "{}.{}".format(name, ext)
1212
with open(str(file_path), "rb") as image_file:
@@ -69,6 +69,7 @@ def compute_parametrize_id(value):
6969
def test_scan_codes(image_name, symbologies, excepted_codes):
7070
image = get_image(image_name)
7171
detected_codes = zbarlight.scan_codes(symbologies, image)
72+
assert detected_codes is not None
7273
assert sorted(detected_codes) == sorted(excepted_codes)
7374

7475

0 commit comments

Comments
 (0)