Skip to content

Commit 94fd322

Browse files
committed
Implement Ruff rules and perform comprehensive code cleanup.
1 parent ac2dda2 commit 94fd322

26 files changed

+331
-250
lines changed

CHANGES.rst

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ Change log
55
WIP
66
===
77

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`_)
8+
- **Added** ``GappedCircleModuleDrawer`` (PIL) to render QR code modules as non-contiguous circles. (BenwestGate in `#373`_)
9+
- **Added** ability to execute as a Python module: ``python -m qrcode --output qrcode.png "hello world"`` (stefansjs in `#400`_)
10+
- **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`_)
1011
- Improved test coveraged (akx in `#315`_)
1112
- 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`_)
1213
- 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"
14+
- Implement Ruff rules and perform comprehensive code cleanup.
1815

1916
.. _#315: https://github.com/lincolnloop/python-qrcode/pull/315
2017
.. _#349: https://github.com/lincolnloop/python-qrcode/pull/349

pyproject.toml

Lines changed: 76 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,53 @@ 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+
"ERA001", # Found commented-out code
115+
"FBT", # Flake Boolean Trap (don't use arg=True in functions)
116+
"N999", # Invalid module name
117+
"PLR091", # Too many statements/branches/arguments
118+
"PLR2004", # Magic value used in comparison, consider replacing <int> with a constant variable
119+
"RUF012", # Mutable class attributes https://github.com/astral-sh/ruff/issues/5243
120+
"SLF001", # private-member-access
121+
122+
# Should be fixed later
123+
"C901", # Too complex
124+
"N802", # Function name should be lowercase
125+
"N803", # Argument name should be lowercase
126+
"N806", # Variable should be lowercase
127+
"PERF401", # Use `list.extend` to create a transformed list
128+
"S101", # Use of 'assert' detected
129+
130+
# Required for `ruff format` to work correctly
131+
"COM812", # Checks for the absence of trailing commas
132+
"ISC001", # Checks for implicitly concatenated strings on a single line
133+
]
134+
135+
[tool.ruff.lint.extend-per-file-ignores]
136+
"qrcode/tests/*.py" = [
137+
"S101", # Use of 'assert' detected
138+
"S603", # `subprocess` call: check for execution of untrusted input
139+
"PT011", # pytest.raises is too broad
140+
]

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: 8 additions & 5 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))
@@ -233,7 +234,8 @@
233234

234235
def glog(n):
235236
if n < 1: # pragma: no cover
236-
raise ValueError(f"glog({n})")
237+
msg = f"glog({n})"
238+
raise ValueError(msg)
237239
return LOG_TABLE[n]
238240

239241

@@ -244,7 +246,8 @@ def gexp(n):
244246
class Polynomial:
245247
def __init__(self, num, shift):
246248
if not num: # pragma: no cover
247-
raise Exception(f"{len(num)}/{shift}")
249+
msg = f"{len(num)}/{shift}"
250+
raise ValueError(msg)
248251

249252
offset = 0
250253
for offset in range(len(num)):
@@ -296,10 +299,10 @@ class RSBlock(NamedTuple):
296299

297300
def rs_blocks(version, error_correction):
298301
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)
302+
msg = (
303+
f"bad rs block @ version: {version} / error_correction: {error_correction}"
302304
)
305+
raise ValueError(msg)
303306
offset = RS_BLOCK_OFFSET[error_correction]
304307
rs_block = RS_BLOCK_TABLE[(version - 1) * 4 + offset]
305308

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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import contextlib
2+
13
# Try to import png library.
24
PngWriter = None
35

4-
try:
5-
from png import Writer as PngWriter # type: ignore # noqa: F401
6-
except ImportError: # pragma: no cover
7-
pass
6+
with contextlib.suppress(ImportError):
7+
from png import Writer as PngWriter # noqa: F401

qrcode/console_scripts.py

Lines changed: 17 additions & 9 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:
@@ -143,20 +149,22 @@ def raise_error(msg: str) -> NoReturn:
143149

144150
def get_factory(module: str) -> type[BaseImage]:
145151
if "." not in module:
146-
raise ValueError("The image factory is not a full python path")
152+
msg = "The image factory is not a full python path"
153+
raise ValueError(msg)
147154
module, name = module.rsplit(".", 1)
148155
imp = __import__(module, {}, {}, [name])
149156
return getattr(imp, name)
150157

151158

152159
def get_drawer_help() -> str:
153160
help: dict[str, set] = {}
161+
154162
for alias, module in default_factories.items():
155163
try:
156164
image = get_factory(module)
157165
except ImportError: # pragma: no cover
158166
continue
159-
aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None)
167+
aliases: DrawerAliases | None = getattr(image, "drawer_aliases", None)
160168
if not aliases:
161169
continue
162170
factories = help.setdefault(commas(aliases), set())

0 commit comments

Comments
 (0)