From 96b9b73a16ecb68c156c142cd1a7651bb74ac3c9 Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Tue, 18 Aug 2026 19:39:37 -0400 Subject: [PATCH] Add --mask-pattern option to the qr command line script The QRCode class already accepts a mask_pattern argument, but there was no way to set it from the qr script, so the command always relied on the automatically chosen best pattern. Expose it as --mask-pattern so callers can pin a specific pattern (0-7), and surface an out-of-range value as a normal parser error instead of a traceback. Closes #335. --- CHANGES.rst | 2 ++ doc/qr.1 | 10 +++++++++- qrcode/console_scripts.py | 18 ++++++++++++++---- qrcode/tests/test_script.py | 20 ++++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 26e6b6aa..f635bb0b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -42,6 +42,7 @@ WIP 8.x - **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`_) +- **Added** ``--mask-pattern`` option to the ``qr`` command line script to select a specific mask pattern instead of the automatically chosen one. (ChrisJr404 in `#433`_) - **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`_) - **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`_) - **Fixed** an issue where an `` NoReturn: help="The error correction level to use. Choices are L (7%), " "M (15%, default), Q (25%), and H (30%).", ) + parser.add_option( + "--mask-pattern", + type=int, + help="The mask pattern (0-7) to use. By default the pattern that " + "scores best under the standard penalty rules is chosen automatically.", + ) parser.add_option( "--ascii", help="Print as ascii even if stdout is piped.", action="store_true" ) @@ -103,10 +109,14 @@ def raise_error(msg: str) -> NoReturn: else: image_factory = None - qr = qrcode.QRCode( - error_correction=error_correction[opts.error_correction], - image_factory=image_factory, - ) + try: + qr = qrcode.QRCode( + error_correction=error_correction[opts.error_correction], + mask_pattern=opts.mask_pattern, + image_factory=image_factory, + ) + except (TypeError, ValueError) as e: + raise_error(str(e)) if args: data = args[0] diff --git a/qrcode/tests/test_script.py b/qrcode/tests/test_script.py index ab4d2903..8100d0d7 100644 --- a/qrcode/tests/test_script.py +++ b/qrcode/tests/test_script.py @@ -57,6 +57,26 @@ def test_optimize(): main(["testtext", "--optimize", "0"]) +@mock.patch("os.isatty", return_value=True) +@mock.patch("qrcode.QRCode") +def test_mask_pattern(mock_qrcode, mock_isatty): + main(["testtext", "--mask-pattern", "1"]) + assert mock_qrcode.call_args.kwargs["mask_pattern"] == 1 + + +@mock.patch("os.isatty", return_value=True) +@mock.patch("qrcode.QRCode") +def test_mask_pattern_default(mock_qrcode, mock_isatty): + main(["testtext"]) + assert mock_qrcode.call_args.kwargs["mask_pattern"] is None + + +def test_mask_pattern_out_of_range(capsys): + with pytest.raises(SystemExit): + main(["testtext", "--mask-pattern", "8"]) + assert "Mask pattern should be in range(8)" in capsys.readouterr()[1] + + def test_factory(): main(["testtext", "--factory", "svg"])