diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ac181bc..da4a852 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 2ee2165..dc1f74d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3 :: Only", "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/qrcode/console_scripts.py b/qrcode/console_scripts.py index 124265a..ebe8810 100755 --- a/qrcode/console_scripts.py +++ b/qrcode/console_scripts.py @@ -9,7 +9,8 @@ import optparse import os import sys -from typing import Dict, Iterable, NoReturn, Optional, Set, Type +from typing import NoReturn, Optional +from collections.abc import Iterable from importlib import metadata import qrcode @@ -140,7 +141,7 @@ def raise_error(msg: str) -> NoReturn: img.save(sys.stdout.buffer) -def get_factory(module: str) -> Type[BaseImage]: +def get_factory(module: str) -> type[BaseImage]: if "." not in module: raise ValueError("The image factory is not a full python path") module, name = module.rsplit(".", 1) @@ -149,7 +150,7 @@ def get_factory(module: str) -> Type[BaseImage]: def get_drawer_help() -> str: - help: Dict[str, Set] = {} + help: dict[str, set] = {} for alias, module in default_factories.items(): try: image = get_factory(module) diff --git a/qrcode/image/base.py b/qrcode/image/base.py index 4e8468b..119c30a 100644 --- a/qrcode/image/base.py +++ b/qrcode/image/base.py @@ -1,5 +1,5 @@ import abc -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Optional, Union from qrcode.image.styles.moduledrawers.base import QRModuleDrawer @@ -7,7 +7,7 @@ from qrcode.main import ActiveWithNeighbors, QRCode -DrawerAliases = Dict[str, Tuple[Type[QRModuleDrawer], Dict[str, Any]]] +DrawerAliases = dict[str, tuple[type[QRModuleDrawer], dict[str, Any]]] class BaseImage: @@ -16,7 +16,7 @@ class BaseImage: """ kind: Optional[str] = None - allowed_kinds: Optional[Tuple[str]] = None + allowed_kinds: Optional[tuple[str]] = None needs_context = False needs_processing = False needs_drawrect = True @@ -108,7 +108,7 @@ def is_eye(self, row: int, col: int): class BaseImageWithDrawer(BaseImage): - default_drawer_class: Type[QRModuleDrawer] + default_drawer_class: type[QRModuleDrawer] drawer_aliases: DrawerAliases = {} def get_default_module_drawer(self) -> QRModuleDrawer: diff --git a/qrcode/image/styles/moduledrawers/pil.py b/qrcode/image/styles/moduledrawers/pil.py index 89eeffa..4aa4249 100644 --- a/qrcode/image/styles/moduledrawers/pil.py +++ b/qrcode/image/styles/moduledrawers/pil.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from PIL import Image, ImageDraw from qrcode.image.styles.moduledrawers.base import QRModuleDrawer @@ -136,7 +136,7 @@ def setup_corners(self): self.SE_ROUND = self.NW_ROUND.transpose(Image.Transpose.ROTATE_180) self.NE_ROUND = self.NW_ROUND.transpose(Image.Transpose.FLIP_LEFT_RIGHT) - def drawrect(self, box: List[List[int]], is_active: "ActiveWithNeighbors"): + def drawrect(self, box: list[list[int]], is_active: "ActiveWithNeighbors"): if not is_active: return # find rounded edges diff --git a/qrcode/image/svg.py b/qrcode/image/svg.py index 4ad371b..4117559 100644 --- a/qrcode/image/svg.py +++ b/qrcode/image/svg.py @@ -1,6 +1,6 @@ import decimal from decimal import Decimal -from typing import List, Optional, Type, Union, overload, Literal +from typing import Optional, Union, overload, Literal import qrcode.image.base from qrcode.compat.etree import ET @@ -18,7 +18,7 @@ class SvgFragmentImage(qrcode.image.base.BaseImageWithDrawer): _SVG_namespace = "http://www.w3.org/2000/svg" kind = "SVG" allowed_kinds = ("SVG",) - default_drawer_class: Type[QRModuleDrawer] = svg_drawers.SvgSquareDrawer + default_drawer_class: type[QRModuleDrawer] = svg_drawers.SvgSquareDrawer def __init__(self, *args, **kwargs): ET.register_namespace("svg", self._SVG_namespace) @@ -123,7 +123,7 @@ class SvgPathImage(SvgImage): needs_processing = True path: Optional[ET.Element] = None - default_drawer_class: Type[QRModuleDrawer] = svg_drawers.SvgPathSquareDrawer + default_drawer_class: type[QRModuleDrawer] = svg_drawers.SvgPathSquareDrawer drawer_aliases = { "circle": (svg_drawers.SvgPathCircleDrawer, {}), "gapped-circle": ( @@ -137,7 +137,7 @@ class SvgPathImage(SvgImage): } def __init__(self, *args, **kwargs): - self._subpaths: List[str] = [] + self._subpaths: list[str] = [] super().__init__(*args, **kwargs) def _svg(self, viewBox=None, **kwargs): diff --git a/qrcode/main.py b/qrcode/main.py index 46116b5..09469bf 100644 --- a/qrcode/main.py +++ b/qrcode/main.py @@ -1,12 +1,9 @@ import sys from bisect import bisect_left from typing import ( - Dict, Generic, - List, NamedTuple, Optional, - Type, TypeVar, cast, overload, @@ -17,9 +14,9 @@ from qrcode.image.base import BaseImage from qrcode.image.pure import PyPNGImage -ModulesType = List[List[Optional[bool]]] +ModulesType = list[list[Optional[bool]]] # Cache modules generated just based on the QR Code version -precomputed_qr_blanks: Dict[int, ModulesType] = {} +precomputed_qr_blanks: dict[int, ModulesType] = {} def make(data=None, **kwargs): @@ -84,7 +81,7 @@ def __init__( error_correction=constants.ERROR_CORRECT_M, box_size=10, border=4, - image_factory: Optional[Type[GenericImage]] = None, + image_factory: Optional[type[GenericImage]] = None, mask_pattern=None, ): _check_box_size(box_size) @@ -336,7 +333,7 @@ def make_image( @overload def make_image( - self, image_factory: Type[GenericImageLocal] = None, **kwargs + self, image_factory: type[GenericImageLocal] = None, **kwargs ) -> GenericImageLocal: ... def make_image(self, image_factory=None, **kwargs): @@ -527,13 +524,13 @@ def get_matrix(self): code = [[False] * width] * self.border x_border = [False] * self.border for module in self.modules: - code.append(x_border + cast(List[bool], module) + x_border) + code.append(x_border + cast(list[bool], module) + x_border) code += [[False] * width] * self.border return code def active_with_neighbors(self, row: int, col: int) -> ActiveWithNeighbors: - context: List[bool] = [] + context: list[bool] = [] for r in range(row - 1, row + 2): for c in range(col - 1, col + 2): context.append(self.is_constrained(r, c) and bool(self.modules[r][c])) diff --git a/qrcode/util.py b/qrcode/util.py index 02fe11d..fe25548 100644 --- a/qrcode/util.py +++ b/qrcode/util.py @@ -1,6 +1,5 @@ import math import re -from typing import List from qrcode import LUT, base, exceptions from qrcode.base import RSBlock @@ -470,7 +469,7 @@ def __repr__(self): class BitBuffer: def __init__(self): - self.buffer: List[int] = [] + self.buffer: list[int] = [] self.length = 0 def __repr__(self): @@ -496,14 +495,14 @@ def put_bit(self, bit): self.length += 1 -def create_bytes(buffer: BitBuffer, rs_blocks: List[RSBlock]): +def create_bytes(buffer: BitBuffer, rs_blocks: list[RSBlock]): offset = 0 maxDcCount = 0 maxEcCount = 0 - dcdata: List[List[int]] = [] - ecdata: List[List[int]] = [] + dcdata: list[list[int]] = [] + ecdata: list[list[int]] = [] for rs_block in rs_blocks: dcCount = rs_block.data_count diff --git a/tox.ini b/tox.ini index 96d72d0..017cc3f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] distribute = False -envlist = py{39,310,311,312}-{pil,png,none} +envlist = py{39,310,311,312,313}-{pil,png,none} skip_missing_interpreters = True [gh-actions] @@ -9,6 +9,7 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [testenv] commands =