Skip to content

Commit

Permalink
Complete coverage for the qrcode package (#12675)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjpieters authored Sep 20, 2024
1 parent f1bf1c0 commit bde71c5
Show file tree
Hide file tree
Showing 20 changed files with 455 additions and 304 deletions.
1 change: 0 additions & 1 deletion pyrightconfig.stricter.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
"stubs/pywin32",
"stubs/pyxdg",
"stubs/PyYAML",
"stubs/qrcode",
"stubs/redis",
"stubs/reportlab",
"stubs/requests",
Expand Down
11 changes: 11 additions & 0 deletions stubs/qrcode/@tests/stubtest_allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ qrcode\.compat\..*
qrcode\.tests
qrcode\.tests\..*

# Stub-only module
qrcode._types

# Parameter "data" has unhelpful default value, which creates a QR code with string "None".
qrcode\.make
qrcode\.main\.make
Expand All @@ -12,5 +15,13 @@ qrcode\.main\.make
# class of the default class assigned to the attribute.
qrcode\.image\..*\.default_drawer_class

# Implementation has marked these methods as abstract without the class
# or its bases deriving from abc.ABCMeta
qrcode\.image\.base\.BaseImage\.(drawrect|new_image|save)

# The implementation sets this attribute to None on the class but instances
# always set this to a PIL image instance.
qrcode\.image\.styles\.moduledrawers\.(pil\.)?CircleModuleDrawer.circle

# Leaked loop counter
qrcode.base.i
3 changes: 2 additions & 1 deletion stubs/qrcode/METADATA.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
version = "7.4.*"
upstream_repository = "https://github.com/lincolnloop/python-qrcode"
requires = ["types-Pillow>=9.1.0"]

[tool.stubtest]
extras = ["lxml"]
extras = ["pil"]
4 changes: 2 additions & 2 deletions stubs/qrcode/qrcode/LUT.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from _typeshed import Incomplete
from typing import Final

rsPoly_LUT: Incomplete
rsPoly_LUT: Final[dict[int, list[int]]]
16 changes: 14 additions & 2 deletions stubs/qrcode/qrcode/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
from _typeshed import ConvertibleToInt

from qrcode import image as image
from qrcode.constants import (
ERROR_CORRECT_H as ERROR_CORRECT_H,
ERROR_CORRECT_L as ERROR_CORRECT_L,
ERROR_CORRECT_M as ERROR_CORRECT_M,
ERROR_CORRECT_Q as ERROR_CORRECT_Q,
)
from qrcode.main import make as make
from qrcode.main import GenericImage, QRCode as QRCode, make as make

from ._types import ErrorCorrect, MaskPattern

def run_example(data: str = "http://www.lincolnloop.com", *args, **kwargs) -> None: ...
def run_example(
data: str = "http://www.lincolnloop.com",
version: ConvertibleToInt | None = None,
error_correction: ErrorCorrect = 0,
box_size: ConvertibleToInt = 10,
border: ConvertibleToInt = 4,
image_factory: type[GenericImage] | None = None,
mask_pattern: MaskPattern | None = None,
) -> None: ...
15 changes: 15 additions & 0 deletions stubs/qrcode/qrcode/_types.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Type aliases used in this stub package
from _typeshed import SupportsWrite
from typing import Any, Protocol
from typing_extensions import TypeAlias

Box: TypeAlias = tuple[tuple[int, int], tuple[int, int]]
Ink: TypeAlias = tuple[int, int, int] | tuple[int, int, int, int]

# Don't try to make these Literal[x, y, z] as this really wreaks
# havoc with overloads in mypy.
ErrorCorrect: TypeAlias = int
MaskPattern: TypeAlias = int

class Writeable(SupportsWrite[bytes], Protocol):
def seek(self, offset: int, /) -> Any: ...
32 changes: 17 additions & 15 deletions stubs/qrcode/qrcode/base.pyi
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
from _typeshed import Incomplete
from typing import NamedTuple
from collections.abc import Iterator
from typing import NamedTuple, SupportsIndex

EXP_TABLE: Incomplete
LOG_TABLE: Incomplete
RS_BLOCK_OFFSET: Incomplete
RS_BLOCK_TABLE: Incomplete
from ._types import ErrorCorrect

def glog(n): ...
def gexp(n): ...
EXP_TABLE: list[int]
LOG_TABLE: list[int]
RS_BLOCK_OFFSET: dict[ErrorCorrect, int]
RS_BLOCK_TABLE: tuple[tuple[int, int, int] | tuple[int, int, int, int, int, int], ...]

def glog(n: int) -> int: ...
def gexp(n: int) -> int: ...

class Polynomial:
num: Incomplete
def __init__(self, num, shift) -> None: ...
def __getitem__(self, index): ...
def __iter__(self): ...
num: list[int]
def __init__(self, num: list[int], shift: int) -> None: ...
def __getitem__(self, index: SupportsIndex) -> int: ...
def __iter__(self) -> Iterator[int]: ...
def __len__(self) -> int: ...
def __mul__(self, other): ...
def __mod__(self, other): ...
def __mul__(self, other: Polynomial) -> Polynomial: ...
def __mod__(self, other: Polynomial) -> Polynomial: ...

class RSBlock(NamedTuple):
total_count: int
data_count: int

def rs_blocks(version, error_correction): ...
def rs_blocks(version: int, error_correction: ErrorCorrect) -> list[RSBlock]: ...
12 changes: 6 additions & 6 deletions stubs/qrcode/qrcode/console_scripts.pyi
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from _typeshed import Incomplete
from collections.abc import Iterable
from collections.abc import Iterable, Sequence

from qrcode.image.base import BaseImage, DrawerAliases as DrawerAliases
from ._types import ErrorCorrect
from .image.base import BaseImage, DrawerAliases as DrawerAliases

default_factories: Incomplete
error_correction: Incomplete
default_factories: dict[str, str]
error_correction: dict[str, ErrorCorrect]

def main(args: Incomplete | None = None) -> None: ...
def main(args: Sequence[str] | None = None) -> None: ...
def get_factory(module: str) -> type[BaseImage]: ...
def get_drawer_help() -> str: ...
def commas(items: Iterable[str], joiner: str = "or") -> str: ...
71 changes: 44 additions & 27 deletions stubs/qrcode/qrcode/image/base.pyi
Original file line number Diff line number Diff line change
@@ -1,50 +1,67 @@
import abc
from _typeshed import Incomplete
from typing import Any
from collections.abc import Callable
from typing import IO, Any
from typing_extensions import TypeAlias

from qrcode.image.styles.moduledrawers.base import QRModuleDrawer
from qrcode.main import QRCode
from ..main import ModulesType, QRCode
from .styles.moduledrawers.base import QRModuleDrawer

# The second element of the value tuple are keyword arguments used when
# constructing instances of the QRModuleDrawer type.
DrawerAliases: TypeAlias = dict[str, tuple[type[QRModuleDrawer], dict[str, Any]]]

class BaseImage(metaclass=abc.ABCMeta):
class BaseImage:
kind: str | None
allowed_kinds: tuple[str] | None
needs_context: bool
needs_processing: bool
needs_drawrect: bool
border: Incomplete
width: Incomplete
box_size: Incomplete
pixel_size: Incomplete
modules: Incomplete
def __init__(self, border, width, box_size, *args, **kwargs) -> None: ...
@abc.abstractmethod
def drawrect(self, row, col): ...
def drawrect_context(self, row: int, col: int, qr: QRCode[Incomplete]): ...
border: int
width: int
box_size: int
pixel_size: int
modules: list[list[bool | None]]
# the class accepts arbitrary additional positional arguments to accommodate
# subclasses with additional arguments. kwargs are forwarded to the `new_image()` call.
def __init__(
self, border: int, width: int, box_size: int, *args: Any, qrcode_modules: ModulesType | None, **kwargs: Any
) -> None: ...
def drawrect(self, row: int, col: int) -> None: ...
def drawrect_context(self, row: int, col: int, qr: QRCode[Any]) -> None: ...
def process(self) -> None: ...
@abc.abstractmethod
def save(self, stream, kind: Incomplete | None = None): ...
def pixel_box(self, row, col): ...
@abc.abstractmethod
def new_image(self, **kwargs) -> Any: ...
def save(self, stream: IO[bytes], kind: str | None = None) -> None: ...
def pixel_box(self, row: int, col: int) -> tuple[tuple[int, int], tuple[int, int]]: ...
# the new_image method accepts arbitrary keyword arguments to accommodate
# subclasses with additional arguments.
def new_image(self, **kwargs: Any) -> Any: ...
def init_new_image(self) -> None: ...
def get_image(self, **kwargs): ...
def check_kind(self, kind, transform: Incomplete | None = None): ...
def is_eye(self, row: int, col: int): ...
# the get_image method accepts arbitrary keyword arguments to accommodate
# subclasses with additional arguments.
def get_image(self, **kwargs: Any) -> Any: ...
def check_kind(self, kind: str | None, transform: Callable[[str | None], str | None] | None = None) -> str | None: ...
def is_eye(self, row: int, col: int) -> bool: ...

class BaseImageWithDrawer(BaseImage, metaclass=abc.ABCMeta):
class BaseImageWithDrawer(BaseImage):
default_drawer_class: type[QRModuleDrawer]
drawer_aliases: DrawerAliases
def get_default_module_drawer(self) -> QRModuleDrawer: ...
def get_default_eye_drawer(self) -> QRModuleDrawer: ...
needs_context: bool
module_drawer: QRModuleDrawer
eye_drawer: QRModuleDrawer
# the class accepts arbitrary additional positional arguments to accommodate
# subclasses with additional arguments. kwargs are forwarded to the `new_image()` call
# via the BaseImage.__init__ method.
def __init__(
self, *args, module_drawer: QRModuleDrawer | str | None = None, eye_drawer: QRModuleDrawer | str | None = None, **kwargs
self,
border: int,
width: int,
box_size: int,
*args: Any,
qrcode_modules: ModulesType | None,
module_drawer: QRModuleDrawer | str | None = None,
eye_drawer: QRModuleDrawer | str | None = None,
**kwargs: Any,
) -> None: ...
def get_drawer(self, drawer: QRModuleDrawer | str | None) -> QRModuleDrawer | None: ...
def init_new_image(self): ...
def drawrect_context(self, row: int, col: int, qr: QRCode[Incomplete]): ...
def init_new_image(self) -> None: ...
def drawrect_context(self, row: int, col: int, qr: QRCode[Any]) -> None: ...
37 changes: 28 additions & 9 deletions stubs/qrcode/qrcode/image/pil.pyi
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
from _typeshed import Incomplete
from pathlib import Path
from typing import Any, Literal

import qrcode.image.base
from PIL import Image

class PilImage(qrcode.image.base.BaseImage):
kind: str
fill_color: Incomplete
def new_image(self, **kwargs): ...
def drawrect(self, row, col) -> None: ...
def save(self, stream, format: Incomplete | None = None, **kwargs) -> None: ... # type: ignore[override]
def __getattr__(self, name): ...
from .._types import Writeable
from . import base

class PilImage(base.BaseImage):
kind: Literal["PNG"]
fill_color: str
# the new_image and get_image methods accept arbitrary keyword arguments to
# accommodate subclasses with additional arguments.
def new_image(self, *, back_color: str = "white", fill_color: str = "black", **kwargs: Any) -> Image.Image: ...
def get_image(self, **kwargs: Any) -> Image.Image: ...
def drawrect(self, row: int, col: int) -> None: ...
# kwargs are passed on to PIL.Image.save, which also accepts arbitrary keyword arguments.
def save( # type: ignore[override]
self,
stream: str | bytes | Path | Writeable,
format: str | None = None,
*,
kind: str | None = None,
save_all: bool = ...,
bitmap_format: Literal["bmp", "png"] = ...,
optimize: bool = ...,
**kwargs: Any,
) -> None: ...
# attribute access is forwarded to the wrapped PIL.Image.Image instance.
def __getattr__(self, name: str) -> Any: ...
27 changes: 17 additions & 10 deletions stubs/qrcode/qrcode/image/pure.pyi
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from _typeshed import Incomplete
from _typeshed import SupportsWrite
from collections.abc import Generator
from typing import Any, Literal
from typing_extensions import TypeAlias

import qrcode.image.base
from . import base

class PyPNGImage(qrcode.image.base.BaseImage):
# png.Writer; no types available
_Writer: TypeAlias = Any

class PyPNGImage(base.BaseImage):
kind: str
allowed_kinds: Incomplete
needs_drawrect: bool
def new_image(self, **kwargs): ...
def drawrect(self, row, col) -> None: ...
def save(self, stream, kind: Incomplete | None = None) -> None: ...
def rows_iter(self) -> Generator[Incomplete, Incomplete, None]: ...
def border_rows_iter(self) -> Generator[Incomplete, None, None]: ...
allowed_kinds: tuple[Literal["PNG"]]
# the new_image and get_image methods accept arbitrary keyword arguments to
# accommodate subclasses with additional arguments.
def new_image(self, **kwargs: Any) -> _Writer: ...
def get_image(self, **kwargs: Any) -> _Writer: ...
def drawrect(self, row: int, col: int) -> None: ...
def save(self, stream: SupportsWrite[bytes], kind: str | None = None) -> None: ...
def rows_iter(self) -> Generator[list[int], Any, None]: ...
def border_rows_iter(self) -> Generator[list[int], Any, None]: ...

PymagingImage = PyPNGImage
70 changes: 52 additions & 18 deletions stubs/qrcode/qrcode/image/styledpil.pyi
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
import abc
from _typeshed import Incomplete
from _typeshed import SupportsRead
from pathlib import Path
from typing import Any, Literal

import qrcode.image.base
from qrcode.image.styles.colormasks import QRColorMask
from qrcode.image.styles.moduledrawers import SquareModuleDrawer
from PIL import Image

class StyledPilImage(qrcode.image.base.BaseImageWithDrawer, metaclass=abc.ABCMeta):
kind: str
needs_processing: bool
from .._types import Ink, Writeable
from ..main import ModulesType
from . import base
from .styles.colormasks import QRColorMask
from .styles.moduledrawers import SquareModuleDrawer
from .styles.moduledrawers.base import QRModuleDrawer

class StyledPilImage(base.BaseImageWithDrawer):
kind: Literal["PNG"]
color_mask: QRColorMask
default_drawer_class = SquareModuleDrawer
embeded_image: Incomplete
embeded_image_resample: Incomplete
paint_color: Incomplete
def __init__(self, *args, **kwargs) -> None: ...
def new_image(self, **kwargs): ...
def init_new_image(self) -> None: ...
def process(self) -> None: ...
default_drawer_class: type[SquareModuleDrawer]
embeded_image: Image.Image
embeded_image_resample: Image.Resampling
paint_color: Ink
# the class accepts arbitrary additional positional arguments to accommodate
# subclasses with additional arguments. kwargs are forwarded to the `new_image()` call
# via the BaseImage.__init__ method.
def __init__(
self,
border: int,
width: int,
box_size: int,
*args: Any,
qrcode_modules: ModulesType | None,
module_drawer: QRModuleDrawer | str | None = None,
eye_drawer: QRModuleDrawer | str | None = None,
color_mask: QRColorMask = ...,
embeded_image_path: str | bytes | Path | SupportsRead[bytes] | None = None,
embeded_image: Image.Image | None = None,
embeded_image_resample: Image.Resampling = ...,
**kwargs: Any,
) -> None: ...
# the new_image method accepts arbitrary keyword arguments to accommodate
# subclasses with additional arguments.
def new_image(self, **kwargs: Any) -> Image.Image: ...
def draw_embeded_image(self) -> None: ...
def save(self, stream, format: Incomplete | None = None, **kwargs) -> None: ... # type: ignore[override]
def __getattr__(self, name): ...
# kwargs are passed on to PIL.Image.save, which also accepts arbitrary keyword arguments.
def save( # type: ignore[override]
self,
stream: str | bytes | Path | Writeable,
format: str | None = None,
*,
kind: str | None = None,
save_all: bool = ...,
bitmap_format: Literal["bmp", "png"] = ...,
optimize: bool = ...,
**kwargs: Any,
) -> None: ...
# attribute access is forwarded to the wrapped PIL.Image.Image instance.
def __getattr__(self, name: str) -> Any: ...
Loading

0 comments on commit bde71c5

Please sign in to comment.