Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ Change log
WIP
===

- Added ``GappedCircleModuleDrawer`` (PIL) to render QR code modules as non-contiguous circles. (BenwestGate in `#373`_)
- 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`_)
- **Added** ``GappedCircleModuleDrawer`` (PIL) to render QR code modules as non-contiguous circles. (BenwestGate in `#373`_)
- **Added** ability to execute as a Python module: ``python -m qrcode --output qrcode.png "hello world"`` (stefansjs in `#400`_)
- **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`_)
- **Removed** backwards-compatible imports for PIL drawers in ``qrcode.image.styles.moduledrawers``; users are required to import drawers directly from ``qrcode.image.styles.moduledrawers.pil``."
- Improved test coveraged (akx in `#315`_)
- 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`_)
- Migrate pyproject.toml to PEP 621-compliant [project] metadata format. (hroncok in `#399`_)
- Allow execution as a Python module. (stefansjs in `#400`_)

::

python -m qrcode --output qrcode.png "hello world"
- Implement Ruff rules and perform comprehensive code cleanup.

.. _#315: https://github.com/lincolnloop/python-qrcode/pull/315
.. _#349: https://github.com/lincolnloop/python-qrcode/pull/349
Expand Down
109 changes: 76 additions & 33 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,31 @@ name = "qrcode"
version = "8.2"
description = "QR Code image generator"
authors = [
{ name = "Lincoln Loop", email = "[email protected]" },
{ name = "Lincoln Loop", email = "[email protected]" },
]
license = { text = "BSD-3-Clause" }
dynamic = [ "readme" ]
dynamic = ["readme"]
keywords = ["qr", "denso-wave", "IEC18004"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"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",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"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",
]
requires-python = "~=3.9"
dependencies = [
"colorama; sys_platform == 'win32'",
"deprecation",
"colorama; sys_platform == 'win32'",
"deprecation",
]


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

[tool.poetry]
packages = [{include = "qrcode"}]
packages = [{ include = "qrcode" }]
readme = ["README.rst", "CHANGES.rst"]

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

[tool.poetry.group.dev.dependencies]
pytest = {version = "*"}
pytest-cov = {version = "*"}
tox = {version = "*"}
ruff = {version = "*"}
pypng = {version = "*"}
pillow = {version = ">=9.1.0"}
pytest = { version = "*" }
pytest-cov = { version = "*" }
tox = { version = "*" }
ruff = { version = "*" }
pypng = { version = "*" }
pillow = { version = ">=9.1.0" }
docutils = "^0.21.2"
zest-releaser = {extras = ["recommended"], version = "^9.2.0"}
zest-releaser = { extras = ["recommended"], version = "^9.2.0" }

[tool.zest-releaser]
less-zeros = "yes"
version-levels = 2
tag-format = "v{version}"
tag-message = "Version {version}"
tag-signing = "yes"
date-format =" %%-d %%B %%Y"
date-format = " %%-d %%B %%Y"
prereleaser.middle = [
"qrcode.release.update_manpage"
"qrcode.release.update_manpage"
]

[tool.coverage.run]
Expand All @@ -88,10 +88,53 @@ parallel = true

[tool.coverage.report]
exclude_lines = [
"@abc.abstractmethod",
"@overload",
"if (typing\\.)?TYPE_CHECKING:",
"pragma: no cover",
"raise NotImplementedError"
"@abc.abstractmethod",
"@overload",
"if (typing\\.)?TYPE_CHECKING:",
"pragma: no cover",
"raise NotImplementedError"
]
skip_covered = true

[tool.ruff]
target-version = "py39"
exclude = ["migrations"]
lint.select = ["ALL"]
lint.ignore = [
# Safe to ignore
"A001", # Variable is shadowing a Python builtin
"A002", # Function argument is shadowing a Python builtin
"ANN", # Missing Type Annotation
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`"
"ARG001", # Unused function argument (request, ...)
"ARG002", # Unused method argument (*args, **kwargs)
"ARG005", # Unused lambda argument
"D", # Missing or badly formatted docstrings
"E501", # Line too long (>88)
"ERA001", # Found commented-out code
"FBT", # Flake Boolean Trap (don't use arg=True in functions)
"N999", # Invalid module name
"PLR091", # Too many statements/branches/arguments
"PLR2004", # Magic value used in comparison, consider replacing <int> with a constant variable
"RUF012", # Mutable class attributes https://github.com/astral-sh/ruff/issues/5243
"SLF001", # private-member-access

# Should be fixed later
"C901", # Too complex
"N802", # Function name should be lowercase
"N803", # Argument name should be lowercase
"N806", # Variable should be lowercase
"PERF401", # Use `list.extend` to create a transformed list
"S101", # Use of 'assert' detected

# Required for `ruff format` to work correctly
"COM812", # Checks for the absence of trailing commas
Comment thread
bartTC marked this conversation as resolved.
"ISC001", # Checks for implicitly concatenated strings on a single line
]

[tool.ruff.lint.extend-per-file-ignores]
"qrcode/tests/*.py" = [
"S101", # Use of 'assert' detected
"S603", # `subprocess` call: check for execution of untrusted input
"PT011", # pytest.raises is too broad
]
2 changes: 1 addition & 1 deletion qrcode/LUT.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

# Result. Usage: input: ecCount, output: Polynomial.num
# e.g. rsPoly = base.Polynomial(LUT.rsPoly_LUT[ecCount], 0)
rsPoly_LUT = {
rsPoly_LUT = { # noqa: N816
7: [1, 127, 122, 154, 164, 11, 68, 117],
10: [1, 216, 194, 159, 111, 199, 94, 95, 113, 157, 193],
13: [1, 137, 73, 227, 17, 177, 17, 52, 13, 46, 43, 83, 132, 120],
Expand Down
21 changes: 15 additions & 6 deletions qrcode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
from qrcode.main import QRCode
from qrcode.main import make # noqa
from qrcode.constants import ( # noqa
from qrcode import image
from qrcode.constants import (
ERROR_CORRECT_H,
ERROR_CORRECT_L,
ERROR_CORRECT_M,
ERROR_CORRECT_Q,
ERROR_CORRECT_H,
)

from qrcode import image # noqa
from qrcode.main import QRCode, make

__all__ = [
"ERROR_CORRECT_H",
"ERROR_CORRECT_L",
"ERROR_CORRECT_M",
"ERROR_CORRECT_Q",
"QRCode",
"image",
"make",
"run_example",
]


def run_example(data="http://www.lincolnloop.com", *args, **kwargs):
Expand Down
13 changes: 8 additions & 5 deletions qrcode/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import NamedTuple

from qrcode import constants

EXP_TABLE = list(range(256))
Expand Down Expand Up @@ -233,7 +234,8 @@

def glog(n):
if n < 1: # pragma: no cover
raise ValueError(f"glog({n})")
msg = f"glog({n})"
raise ValueError(msg)
Comment thread
bartTC marked this conversation as resolved.
Outdated
return LOG_TABLE[n]


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

offset = 0
for offset in range(len(num)):
Expand Down Expand Up @@ -296,10 +299,10 @@ class RSBlock(NamedTuple):

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

Expand Down
4 changes: 2 additions & 2 deletions qrcode/compat/etree.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
try:
import lxml.etree as ET # type: ignore # noqa: F401
import lxml.etree as ET # noqa: N812
except ImportError:
import xml.etree.ElementTree as ET # type: ignore # noqa: F401
import xml.etree.ElementTree as ET # noqa: F401
8 changes: 4 additions & 4 deletions qrcode/compat/png.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import contextlib

# Try to import png library.
PngWriter = None

try:
from png import Writer as PngWriter # type: ignore # noqa: F401
except ImportError: # pragma: no cover
pass
with contextlib.suppress(ImportError):
Comment thread
bartTC marked this conversation as resolved.
Outdated
from png import Writer as PngWriter # noqa: F401
26 changes: 17 additions & 9 deletions qrcode/console_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@
a pipe to a file an image is written. The default image format is PNG.
"""

from __future__ import annotations

import optparse
import os
import sys
from typing import NoReturn, Optional
from collections.abc import Iterable
from importlib import metadata
from pathlib import Path
from typing import TYPE_CHECKING, NoReturn

import qrcode
from qrcode.image.base import BaseImage, DrawerAliases

if TYPE_CHECKING:
from collections.abc import Iterable

from qrcode.image.base import BaseImage, DrawerAliases

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

colorama.init()

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

parser.add_option(
"--factory",
Expand Down Expand Up @@ -114,15 +120,15 @@ def raise_error(msg: str) -> NoReturn:

if opts.output:
img = qr.make_image()
with open(opts.output, "wb") as out:
with Path(opts.output).open("wb") as out:
Comment thread
bartTC marked this conversation as resolved.
img.save(out)
else:
if image_factory is None and (os.isatty(sys.stdout.fileno()) or opts.ascii):
qr.print_ascii(tty=not opts.ascii)
return

kwargs = {}
aliases: Optional[DrawerAliases] = getattr(
aliases: DrawerAliases | None = getattr(
qr.image_factory, "drawer_aliases", None
)
if opts.factory_drawer:
Expand All @@ -143,20 +149,22 @@ def raise_error(msg: str) -> NoReturn:

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


def get_drawer_help() -> str:
help: dict[str, set] = {}

for alias, module in default_factories.items():
try:
image = get_factory(module)
except ImportError: # pragma: no cover
continue
aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None)
aliases: DrawerAliases | None = getattr(image, "drawer_aliases", None)
if not aliases:
continue
factories = help.setdefault(commas(aliases), set())
Expand Down
Loading