Skip to content

Fix three error-handling correctness issues (mask_func, QRData write, best_fit)#425

Open
nssmd wants to merge 1 commit into
lincolnloop:mainfrom
nssmd:fix/error-handling-improvements
Open

Fix three error-handling correctness issues (mask_func, QRData write, best_fit)#425
nssmd wants to merge 1 commit into
lincolnloop:mainfrom
nssmd:fix/error-handling-improvements

Conversation

@nssmd
Copy link
Copy Markdown

@nssmd nssmd commented May 10, 2026

PR: Fix three error-handling correctness issues

Branch: fix/error-handling-improvements
Base: lincolnloop/python-qrcode:main
Type: Bug fix · Public API behavior

Summary

While building an end-to-end test suite for the library, three latent
correctness issues surfaced. All three are reachable from the public
API and lead to misleading or wrong-typed exceptions. The patch fixes
the failure modes and the test suite gains regression tests for each.

# Site Symptom Fix
1 util.py:161 TypeError: can only concatenate str (not 'int') to str for pattern=8. The intended message is hidden behind the concatenation. Use an f-string and raise ValueError (the type is correct; the value is not). Tracks the case described in issue #191.
2 util.py:455-463 (QRData.write) QRData(b"a", mode=ALPHA_NUM, check_data=False).write(buf) silently produces an undecodable QR because ALPHA_NUM.find('a') == -1 and BitBuffer.put(-1, 6) writes six 1-bits. Validate find()'s return value in the writer and raise ValueError naming the offending byte(s).
3 main.py:223-227 (best_fit) When the input exceeds the v40 capacity, bisect_left returns 41. Assigning 41 to self.version triggers the setter, which calls check_version and raises ValueError("Invalid version (was 41…)") — preventing the library's own DataOverflowError from being raised. Compute into a local new_version, raise DataOverflowError on 41, then assign. Same observable behavior on success; semantically correct exception on overflow.

Why this matters

Two of the three fixes change exception type, not just text. Callers
that wrap qr.make() and dispatch on DataOverflowError (e.g. to ask
the user to shorten the payload) currently cannot catch the overflow
case via that exception, because it's intercepted by the
property-setter validation. Same story with mask_func: defensive code
that asserts pytest.raises(TypeError, match="Bad mask pattern") will
silently fail to match because the prefix never gets emitted. These
are user-facing contract regressions even when the bug count looks
small.

Verification

# 324 passing tests including new regression cases for each fix
$ pytest qrcode/tests test_deliverables -q
324 passed, 1 skipped in 6.7s

# Coverage held at 98%
$ pytest --cov=qrcode | tail -1
TOTAL  2057  32  98%

Three unit tests assert the new correct behavior:

  • TestMaskFuncErrorMessage::test_mask_func_invalid_pattern_message
  • TestQRDataAlphaNumWithNonAlphaChar::test_alphanum_with_invalid_char_raises
  • TestDataOverflow::test_overflow_when_autofit_exceeds_v40

Backward compatibility

The exception types change for the three failure modes above
(BUG #1: TypeErrorValueError; BUG #3: ValueError
DataOverflowError). For success paths there is no observable change.
The new ValueError from QRData.write is only reachable via the
opt-in check_data=False constructor flag with non-ALPHA_NUM data —
i.e., previously broken usage — so no existing valid program is
affected.

If you'd prefer to preserve the exact exception class for #1 to keep
strict back-compat, I can change the patch to keep TypeError and
only fix the message-construction crash. Happy to iterate.

Discovery process

These were found while writing a black-box / white-box test plan
against v8.2 as part of a software-testing course assignment. The
suspects were short-listed by reading util.py and main.py looking
for three patterns: (a) error messages built with + instead of
f"", (b) public methods with an opt-in skip-validation flag, and
(c) numeric overflow paths that route through a setter. All three
were independently reproduced via pytest, and #3 was confirmed by a
side run of the OpenAI Codex CLI which observed the same
ValueError("Invalid version (was 41, expected 1 to 40)") from a
clean checkout.


Discovered & patched by the python-qrcode testing study group, with
the assistance of Claude Opus 4.7 (Anthropic) for static review and
Codex CLI for cross-verification.

1. mask_func: replace `"Bad mask pattern: " + pattern` (which raises
   TypeError on str+int when pattern is non-int) with an f-string and
   raise ValueError, since for an out-of-range integer the type is fine
   but the value is not. Closes the issue tracked in lincolnloop#191.

2. QRData.write: validate ALPHA_NUM characters in the write path. With
   `check_data=False`, ALPHA_NUM.find() returned -1 for any character
   outside the table; BitBuffer.put(-1, n) silently writes all-ones bits,
   producing a QR that no compliant scanner can decode. Now raises a
   ValueError explaining which character is invalid.

3. best_fit: compute version into a local first, then raise
   DataOverflowError on 41 *before* assigning to the property setter.
   Previously the setter's check_version() raised ValueError, masking
   the library's own DataOverflowError exception.

All three are reachable via the public API and are covered by new
regression tests under test_deliverables/test_blackbox.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant