Skip to content

Commit c222cae

Browse files
committed
Ruff cleanup of the entire codebase and better deprecation.
Squashed commit of the following: commit 60fdcc2 Author: Martin Mahner <[email protected]> Date: Fri Jul 25 11:05:57 2025 +0200 Revert supress change commit d453a5e Author: Martin Mahner <[email protected]> Date: Fri Jul 25 11:00:21 2025 +0200 File is automatically deleted commit 84b31ec Author: Martin Mahner <[email protected]> Date: Thu Jul 24 13:43:46 2025 +0200 Refactor temporary file handling in tests and simplify deprecation test structure commit 2096319 Author: Martin Mahner <[email protected]> Date: Thu Jul 24 13:38:39 2025 +0200 Revert EM101 102 commit f6e1715 Author: Martin Mahner <[email protected]> Date: Thu Jul 24 13:12:59 2025 +0200 Fix tuple annotation commit b2a1f3f Author: Martin Mahner <[email protected]> Date: Thu Jul 24 13:08:34 2025 +0200 Revert contextlib change to improve performance. commit fa212b7 Author: Martin Mahner <[email protected]> Date: Thu Jul 24 12:51:27 2025 +0200 Simplify Import handling within tests for Ruff commit c473ebb Author: Martin Mahner <[email protected]> Date: Thu Jul 24 12:51:14 2025 +0200 Test and document deprecations. commit c38fb6f Author: Martin Mahner <[email protected]> Date: Thu Jul 24 12:50:46 2025 +0200 Add a global constant whether PIL is available or not. commit eb73ec6 Author: Martin Mahner <[email protected]> Date: Thu Jul 24 12:50:00 2025 +0200 Make an import from 'moduledrawers' directly raise a deprecation warning commit 7528208 Author: Martin Mahner <[email protected]> Date: Thu Jul 24 09:57:38 2025 +0200 Revert "Removed backwards-compatible imports for PIL drawers in qrcode.image.styles.moduledrawers." This reverts commit b132ef9. commit b132ef9 Author: Martin Mahner <[email protected]> Date: Wed Jul 23 19:15:43 2025 +0200 Removed backwards-compatible imports for PIL drawers in qrcode.image.styles.moduledrawers. commit 82729c2 Author: Martin Mahner <[email protected]> Date: Wed Jul 23 16:18:41 2025 +0200 Reformat Imports commit 94fd322 Author: Martin Mahner <[email protected]> Date: Wed Jul 23 16:15:44 2025 +0200 Implement Ruff rules and perform comprehensive code cleanup.
1 parent ac2dda2 commit c222cae

30 files changed

+498
-239
lines changed

CHANGES.rst

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,43 @@
22
Change log
33
==========
44

5+
Deprecation Warnings
6+
====================
7+
8+
Removed in v9.0:
9+
----------------
10+
11+
- Importing a PIL drawer from ``qrcode.image.styles.moduledrawers`` has been deprecated.
12+
Update your code to import directly from the ``pil`` module instead:
13+
14+
.. code-block:: python
15+
16+
from qrcode.image.styles.moduledrawers import SquareModuleDrawer # Old
17+
from qrcode.image.styles.moduledrawers.pil import SquareModuleDrawer # New
18+
19+
- Calling ``QRCode.make_image`` or ``StyledPilImage`` with the arguments ``embeded_image``
20+
or ``embeded_image_path`` have been deprecated due to typographical errors. Update
21+
your code to use the correct arguments ``embedded_image`` and ``embededd_image_path``:
22+
23+
.. code-block:: python
24+
25+
qr = QRCode()
26+
qr.make_image(embeded_image=..., embeded_image_path=...) # Old
27+
qr.make_image(embedded_image=..., embedded_image_path=...) # New
28+
29+
StyledPilImage(embeded_image=..., embeded_image_path=...) # Old
30+
StyledPilImage(embedded_image=..., embedded_image_path=...) # New
31+
532
WIP
633
===
734

8-
- Added ``GappedCircleModuleDrawer`` (PIL) to render QR code modules as non-contiguous circles. (BenwestGate in `#373`_)
9-
- Removed the hardcoded 'id' argument from SVG elements. The fixed element ID caused conflicts when embedding multiple QR codes in a single document. (m000 in `#385`_)
35+
- **Added** ``GappedCircleModuleDrawer`` (PIL) to render QR code modules as non-contiguous circles. (BenwestGate in `#373`_)
36+
- **Added** ability to execute as a Python module: ``python -m qrcode --output qrcode.png "hello world"`` (stefansjs in `#400`_)
37+
- **Removed** the hardcoded 'id' argument from SVG elements. The fixed element ID caused conflicts when embedding multiple QR codes in a single document. (m000 in `#385`_)
1038
- Improved test coveraged (akx in `#315`_)
1139
- Fixed typos in code that used ``embeded`` instead of ``embedded``. For backwards compatibility, the misspelled parameter names are still accepted but now emit deprecation warnings. These deprecated parameter names will be removed in v9.0. (benjnicholls in `#349`_)
1240
- Migrate pyproject.toml to PEP 621-compliant [project] metadata format. (hroncok in `#399`_)
13-
- Allow execution as a Python module. (stefansjs in `#400`_)
14-
15-
::
16-
17-
python -m qrcode --output qrcode.png "hello world"
41+
- Implement Ruff rules and perform comprehensive code cleanup.
1842

1943
.. _#315: https://github.com/lincolnloop/python-qrcode/pull/315
2044
.. _#349: https://github.com/lincolnloop/python-qrcode/pull/349

pyproject.toml

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@ name = "qrcode"
77
version = "8.2"
88
description = "QR Code image generator"
99
authors = [
10-
{ name = "Lincoln Loop", email = "[email protected]" },
10+
{ name = "Lincoln Loop", email = "[email protected]" },
1111
]
1212
license = { text = "BSD-3-Clause" }
13-
dynamic = [ "readme" ]
13+
dynamic = ["readme"]
1414
keywords = ["qr", "denso-wave", "IEC18004"]
1515
classifiers = [
16-
"Development Status :: 5 - Production/Stable",
17-
"License :: OSI Approved :: BSD License",
18-
"Operating System :: OS Independent",
19-
"Intended Audience :: Developers",
20-
"Programming Language :: Python",
21-
"Programming Language :: Python :: 3",
22-
"Programming Language :: Python :: 3.9",
23-
"Programming Language :: Python :: 3.10",
24-
"Programming Language :: Python :: 3.11",
25-
"Programming Language :: Python :: 3.12",
26-
"Programming Language :: Python :: 3.13",
27-
"Programming Language :: Python :: 3 :: Only",
28-
"Topic :: Multimedia :: Graphics",
29-
"Topic :: Software Development :: Libraries :: Python Modules",
16+
"Development Status :: 5 - Production/Stable",
17+
"License :: OSI Approved :: BSD License",
18+
"Operating System :: OS Independent",
19+
"Intended Audience :: Developers",
20+
"Programming Language :: Python",
21+
"Programming Language :: Python :: 3",
22+
"Programming Language :: Python :: 3.9",
23+
"Programming Language :: Python :: 3.10",
24+
"Programming Language :: Python :: 3.11",
25+
"Programming Language :: Python :: 3.12",
26+
"Programming Language :: Python :: 3.13",
27+
"Programming Language :: Python :: 3 :: Only",
28+
"Topic :: Multimedia :: Graphics",
29+
"Topic :: Software Development :: Libraries :: Python Modules",
3030
]
3131
requires-python = "~=3.9"
3232
dependencies = [
33-
"colorama; sys_platform == 'win32'",
34-
"deprecation",
33+
"colorama; sys_platform == 'win32'",
34+
"deprecation",
3535
]
3636

3737

@@ -51,7 +51,7 @@ changelog = "https://github.com/lincolnloop/python-qrcode/blob/main/CHANGES.rst"
5151
qr = "qrcode.console_scripts:main"
5252

5353
[tool.poetry]
54-
packages = [{include = "qrcode"}]
54+
packages = [{ include = "qrcode" }]
5555
readme = ["README.rst", "CHANGES.rst"]
5656

5757
# There is no support for data files yet.
@@ -62,24 +62,24 @@ readme = ["README.rst", "CHANGES.rst"]
6262
# ]
6363

6464
[tool.poetry.group.dev.dependencies]
65-
pytest = {version = "*"}
66-
pytest-cov = {version = "*"}
67-
tox = {version = "*"}
68-
ruff = {version = "*"}
69-
pypng = {version = "*"}
70-
pillow = {version = ">=9.1.0"}
65+
pytest = { version = "*" }
66+
pytest-cov = { version = "*" }
67+
tox = { version = "*" }
68+
ruff = { version = "*" }
69+
pypng = { version = "*" }
70+
pillow = { version = ">=9.1.0" }
7171
docutils = "^0.21.2"
72-
zest-releaser = {extras = ["recommended"], version = "^9.2.0"}
72+
zest-releaser = { extras = ["recommended"], version = "^9.2.0" }
7373

7474
[tool.zest-releaser]
7575
less-zeros = "yes"
7676
version-levels = 2
7777
tag-format = "v{version}"
7878
tag-message = "Version {version}"
7979
tag-signing = "yes"
80-
date-format =" %%-d %%B %%Y"
80+
date-format = " %%-d %%B %%Y"
8181
prereleaser.middle = [
82-
"qrcode.release.update_manpage"
82+
"qrcode.release.update_manpage"
8383
]
8484

8585
[tool.coverage.run]
@@ -88,10 +88,59 @@ parallel = true
8888

8989
[tool.coverage.report]
9090
exclude_lines = [
91-
"@abc.abstractmethod",
92-
"@overload",
93-
"if (typing\\.)?TYPE_CHECKING:",
94-
"pragma: no cover",
95-
"raise NotImplementedError"
91+
"@abc.abstractmethod",
92+
"@overload",
93+
"if (typing\\.)?TYPE_CHECKING:",
94+
"pragma: no cover",
95+
"raise NotImplementedError"
9696
]
9797
skip_covered = true
98+
99+
[tool.ruff]
100+
target-version = "py39"
101+
exclude = ["migrations"]
102+
lint.select = ["ALL"]
103+
lint.ignore = [
104+
# Safe to ignore
105+
"A001", # Variable is shadowing a Python builtin
106+
"A002", # Function argument is shadowing a Python builtin
107+
"ANN", # Missing Type Annotation
108+
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`"
109+
"ARG001", # Unused function argument (request, ...)
110+
"ARG002", # Unused method argument (*args, **kwargs)
111+
"ARG005", # Unused lambda argument
112+
"D", # Missing or badly formatted docstrings
113+
"E501", # Line too long (>88)
114+
"EM101", # Exception must not use a string literal, assign to variable first
115+
"EM102", # Exception must not use an f-string literal, assign to variable first "ERA001", # Found commented-out code
116+
"ERA001", # Found commented-out code
117+
"FBT", # Flake Boolean Trap (don't use arg=True in functions)
118+
"N999", # Invalid module name
119+
"PLR091", # Too many statements/branches/arguments
120+
"PLR2004", # Magic value used in comparison, consider replacing <int> with a constant variable
121+
"RUF012", # Mutable class attributes https://github.com/astral-sh/ruff/issues/5243
122+
"SIM105", # Use contextlib.suppress(ImportError) instead of try-except-pass
123+
"SLF001", # private-member-access
124+
"TRY003", # Avoid specifying long messages outside the exception class
125+
126+
# Should be fixed later
127+
"C901", # Too complex
128+
"N802", # Function name should be lowercase
129+
"N803", # Argument name should be lowercase
130+
"N806", # Variable should be lowercase
131+
"PERF401", # Use `list.extend` to create a transformed list
132+
"S101", # Use of 'assert' detected
133+
134+
# Required for `ruff format` to work correctly
135+
"COM812", # Checks for the absence of trailing commas
136+
"ISC001", # Checks for implicitly concatenated strings on a single line
137+
]
138+
139+
[tool.ruff.lint.extend-per-file-ignores]
140+
"qrcode/tests/*.py" = [
141+
"F401", # Unused import
142+
"PLC0415", # Import not at top of a file
143+
"PT011", # pytest.raises is too broad
144+
"S101", # Use of 'assert' detected
145+
"S603", # `subprocess` call: check for execution of untrusted input
146+
]

qrcode/LUT.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
# Result. Usage: input: ecCount, output: Polynomial.num
2626
# e.g. rsPoly = base.Polynomial(LUT.rsPoly_LUT[ecCount], 0)
27-
rsPoly_LUT = {
27+
rsPoly_LUT = { # noqa: N816
2828
7: [1, 127, 122, 154, 164, 11, 68, 117],
2929
10: [1, 216, 194, 159, 111, 199, 94, 95, 113, 157, 193],
3030
13: [1, 137, 73, 227, 17, 177, 17, 52, 13, 46, 43, 83, 132, 120],

qrcode/__init__.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
from qrcode.main import QRCode
2-
from qrcode.main import make # noqa
3-
from qrcode.constants import ( # noqa
1+
from qrcode import image
2+
from qrcode.constants import (
3+
ERROR_CORRECT_H,
44
ERROR_CORRECT_L,
55
ERROR_CORRECT_M,
66
ERROR_CORRECT_Q,
7-
ERROR_CORRECT_H,
87
)
9-
10-
from qrcode import image # noqa
8+
from qrcode.main import QRCode, make
9+
10+
__all__ = [
11+
"ERROR_CORRECT_H",
12+
"ERROR_CORRECT_L",
13+
"ERROR_CORRECT_M",
14+
"ERROR_CORRECT_Q",
15+
"QRCode",
16+
"image",
17+
"make",
18+
"run_example",
19+
]
1120

1221

1322
def run_example(data="http://www.lincolnloop.com", *args, **kwargs):

qrcode/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import NamedTuple
2+
23
from qrcode import constants
34

45
EXP_TABLE = list(range(256))
@@ -244,7 +245,7 @@ def gexp(n):
244245
class Polynomial:
245246
def __init__(self, num, shift):
246247
if not num: # pragma: no cover
247-
raise Exception(f"{len(num)}/{shift}")
248+
raise ValueError(f"{len(num)}/{shift}")
248249

249250
offset = 0
250251
for offset in range(len(num)):
@@ -296,9 +297,8 @@ class RSBlock(NamedTuple):
296297

297298
def rs_blocks(version, error_correction):
298299
if error_correction not in RS_BLOCK_OFFSET: # pragma: no cover
299-
raise Exception(
300-
"bad rs block @ version: %s / error_correction: %s"
301-
% (version, error_correction)
300+
raise ValueError(
301+
f"bad rs block @ version: {version} / error_correction: {error_correction}"
302302
)
303303
offset = RS_BLOCK_OFFSET[error_correction]
304304
rs_block = RS_BLOCK_TABLE[(version - 1) * 4 + offset]

qrcode/compat/etree.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
try:
2-
import lxml.etree as ET # type: ignore # noqa: F401
2+
import lxml.etree as ET # noqa: N812
33
except ImportError:
4-
import xml.etree.ElementTree as ET # type: ignore # noqa: F401
4+
import xml.etree.ElementTree as ET # noqa: F401

qrcode/compat/png.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
PngWriter = None
33

44
try:
5-
from png import Writer as PngWriter # type: ignore # noqa: F401
6-
except ImportError: # pragma: no cover
5+
from png import Writer as PngWriter # noqa: F401
6+
except ImportError:
77
pass

qrcode/console_scripts.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@
66
a pipe to a file an image is written. The default image format is PNG.
77
"""
88

9+
from __future__ import annotations
10+
911
import optparse
1012
import os
1113
import sys
12-
from typing import NoReturn, Optional
13-
from collections.abc import Iterable
1414
from importlib import metadata
15+
from pathlib import Path
16+
from typing import TYPE_CHECKING, NoReturn
1517

1618
import qrcode
17-
from qrcode.image.base import BaseImage, DrawerAliases
19+
20+
if TYPE_CHECKING:
21+
from collections.abc import Iterable
22+
23+
from qrcode.image.base import BaseImage, DrawerAliases
1824

1925
# The next block is added to get the terminal to display properly on MS platforms
2026
if sys.platform.startswith(("win", "cygwin")): # pragma: no cover
21-
import colorama # type: ignore
27+
import colorama
2228

2329
colorama.init()
2430

@@ -50,7 +56,7 @@ def main(args=None):
5056
# Wrap parser.error in a typed NoReturn method for better typing.
5157
def raise_error(msg: str) -> NoReturn:
5258
parser.error(msg)
53-
raise # pragma: no cover
59+
raise # pragma: no cover # noqa: PLE0704
5460

5561
parser.add_option(
5662
"--factory",
@@ -114,15 +120,15 @@ def raise_error(msg: str) -> NoReturn:
114120

115121
if opts.output:
116122
img = qr.make_image()
117-
with open(opts.output, "wb") as out:
123+
with Path(opts.output).open("wb") as out:
118124
img.save(out)
119125
else:
120126
if image_factory is None and (os.isatty(sys.stdout.fileno()) or opts.ascii):
121127
qr.print_ascii(tty=not opts.ascii)
122128
return
123129

124130
kwargs = {}
125-
aliases: Optional[DrawerAliases] = getattr(
131+
aliases: DrawerAliases | None = getattr(
126132
qr.image_factory, "drawer_aliases", None
127133
)
128134
if opts.factory_drawer:
@@ -151,12 +157,13 @@ def get_factory(module: str) -> type[BaseImage]:
151157

152158
def get_drawer_help() -> str:
153159
help: dict[str, set] = {}
160+
154161
for alias, module in default_factories.items():
155162
try:
156163
image = get_factory(module)
157164
except ImportError: # pragma: no cover
158165
continue
159-
aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None)
166+
aliases: DrawerAliases | None = getattr(image, "drawer_aliases", None)
160167
if not aliases:
161168
continue
162169
factories = help.setdefault(commas(aliases), set())

qrcode/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
from importlib.util import find_spec
2+
13
# QR error correct levels
24
ERROR_CORRECT_L = 1
35
ERROR_CORRECT_M = 0
46
ERROR_CORRECT_Q = 3
57
ERROR_CORRECT_H = 2
8+
9+
# Constant whether the PIL library is installed.
10+
PIL_AVAILABLE = find_spec("PIL") is not None

0 commit comments

Comments
 (0)