Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 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
38 changes: 31 additions & 7 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,43 @@
Change log
==========

Deprecation Warnings
====================

Removed in v9.0:
----------------

- Importing a PIL drawer from ``qrcode.image.styles.moduledrawers`` has been deprecated.
Update your code to import directly from the ``pil`` module instead:

.. code-block:: python

from qrcode.image.styles.moduledrawers import SquareModuleDrawer # Old
from qrcode.image.styles.moduledrawers.pil import SquareModuleDrawer # New

- Calling ``QRCode.make_image`` or ``StyledPilImage`` with the arguments ``embeded_image``
or ``embeded_image_path`` have been deprecated due to typographical errors. Update
your code to use the correct arguments ``embedded_image`` and ``embededd_image_path``:

.. code-block:: python

qr = QRCode()
qr.make_image(embeded_image=..., embeded_image_path=...) # Old
qr.make_image(embedded_image=..., embedded_image_path=...) # New

StyledPilImage(embeded_image=..., embeded_image_path=...) # Old
StyledPilImage(embedded_image=..., embedded_image_path=...) # New

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`_)
- 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
115 changes: 82 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 = "info@lincolnloop.com" },
{ name = "Lincoln Loop", email = "info@lincolnloop.com" },
]
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,59 @@ 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)
"EM101", # Exception must not use a string literal, assign to variable first
"EM102", # Exception must not use an f-string literal, assign to variable first "ERA001", # Found commented-out code
"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
"SIM105", # Use contextlib.suppress(ImportError) instead of try-except-pass
"SLF001", # private-member-access
"TRY003", # Avoid specifying long messages outside the exception class

# 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" = [
"F401", # Unused import
"PLC0415", # Import not at top of a file
"PT011", # pytest.raises is too broad
"S101", # Use of 'assert' detected
"S603", # `subprocess` call: check for execution of untrusted input
]
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
8 changes: 4 additions & 4 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 @@ -244,7 +245,7 @@ def gexp(n):
class Polynomial:
def __init__(self, num, shift):
if not num: # pragma: no cover
raise Exception(f"{len(num)}/{shift}")
raise ValueError(f"{len(num)}/{shift}")

offset = 0
for offset in range(len(num)):
Expand Down Expand Up @@ -296,9 +297,8 @@ 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)
raise ValueError(
f"bad rs block @ version: {version} / error_correction: {error_correction}"
)
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
4 changes: 2 additions & 2 deletions qrcode/compat/png.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
PngWriter = None

try:
from png import Writer as PngWriter # type: ignore # noqa: F401
except ImportError: # pragma: no cover
from png import Writer as PngWriter # noqa: F401
except ImportError:
pass
23 changes: 15 additions & 8 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 Down Expand Up @@ -151,12 +157,13 @@ def get_factory(module: str) -> type[BaseImage]:

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
5 changes: 5 additions & 0 deletions qrcode/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from importlib.util import find_spec

# QR error correct levels
ERROR_CORRECT_L = 1
ERROR_CORRECT_M = 0
ERROR_CORRECT_Q = 3
ERROR_CORRECT_H = 2

# Constant whether the PIL library is installed.
PIL_AVAILABLE = find_spec("PIL") is not None
Loading