diff --git a/qrcode/main.py b/qrcode/main.py index 593c581b..ace23ca4 100644 --- a/qrcode/main.py +++ b/qrcode/main.py @@ -219,12 +219,16 @@ def best_fit(self, start=None): data.write(buffer) needed_bits = len(buffer) - # Create a thread-local copy of the table to avoid concurrent access issues (Python 3.13+) - self.version = bisect_left( + # Compute the version from a thread-local view of the limit table. + # Bisect first, then check overflow, *before* assigning to the setter + # so we raise the semantically correct DataOverflowError instead of + # the ValueError the version setter would raise for value 41. + new_version = bisect_left( util.BIT_LIMIT_TABLE[self.error_correction][:], needed_bits, start ) - if self.version == 41: + if new_version == 41: raise exceptions.DataOverflowError + self.version = new_version # Now check whether we need more bits for the mode sizes, recursing if # our guess was too low diff --git a/qrcode/util.py b/qrcode/util.py index 4187aa5e..ea73e44c 100644 --- a/qrcode/util.py +++ b/qrcode/util.py @@ -158,7 +158,7 @@ def mask_func(pattern): return lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0 if pattern == 7: # 111 return lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0 - raise TypeError("Bad mask pattern: " + pattern) # pragma: no cover + raise ValueError(f"Bad mask pattern: {pattern!r}") def mode_sizes_for_version(version): @@ -456,11 +456,22 @@ def write(self, buffer): for i in range(0, len(self.data), 2): chars = self.data[i : i + 2] if len(chars) > 1: - buffer.put( - ALPHA_NUM.find(chars[0]) * 45 + ALPHA_NUM.find(chars[1]), 11 - ) + a = ALPHA_NUM.find(chars[0]) + b = ALPHA_NUM.find(chars[1]) + if a < 0 or b < 0: + raise ValueError( + f"Character {chars!r} is not in the alphanumeric " + f"table; cannot encode in MODE_ALPHA_NUM" + ) + buffer.put(a * 45 + b, 11) else: - buffer.put(ALPHA_NUM.find(chars), 6) + a = ALPHA_NUM.find(chars) + if a < 0: + raise ValueError( + f"Character {chars!r} is not in the alphanumeric " + f"table; cannot encode in MODE_ALPHA_NUM" + ) + buffer.put(a, 6) else: # Iterating a bytestring in Python 3 returns an integer, # no need to ord().