Skip to content

Commit ddaa12b

Browse files
Mr-Neutr0nJPPhotolstein
authored
Fix bare except clauses and mutable default arguments (invoke-ai#8871)
* Fix bare except clauses and mutable default arguments Replace bare `except:` with `except Exception:` in sqlite_database.py and mlsd/utils.py to avoid catching KeyboardInterrupt and SystemExit, which can prevent graceful shutdowns and mask critical errors (PEP 8 E722). Replace mutable default arguments (lists) with None in imwatermark/vendor.py to prevent shared state between calls, which is a known Python gotcha that can cause subtle bugs when default mutable objects are modified in place. * add tests for mutable defaults and bare except fixes * Simplify exception propagation tests * Remove unused db initialization in error propagation tests Removed unused database initialization in tests for KeyboardInterrupt and SystemExit. --------- Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
1 parent c8dfea8 commit ddaa12b

5 files changed

Lines changed: 118 additions & 6 deletions

File tree

invokeai/app/services/shared/sqlite/sqlite_database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def transaction(self) -> Generator[sqlite3.Cursor, None, None]:
8686
try:
8787
yield cursor
8888
self._conn.commit()
89-
except:
89+
except Exception:
9090
self._conn.rollback()
9191
raise
9292
finally:

invokeai/backend/image_util/imwatermark/vendor.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ def set_by_b16(self, b16):
5050
self.set_by_bytes(content)
5151
self._wmType = "b16"
5252

53-
def set_by_bits(self, bits=[]):
53+
def set_by_bits(self, bits=None):
54+
if bits is None:
55+
bits = []
5456
self._watermarks = [int(bit) % 2 for bit in bits]
5557
self._wmLen = len(self._watermarks)
5658
self._wmType = "bits"
@@ -177,7 +179,11 @@ def decode(self, cv2Image, method="dwtDct", **configs):
177179

178180

179181
class EmbedMaxDct(object):
180-
def __init__(self, watermarks=[], wmLen=8, scales=[0, 36, 36], block=4):
182+
def __init__(self, watermarks=None, wmLen=8, scales=None, block=4):
183+
if watermarks is None:
184+
watermarks = []
185+
if scales is None:
186+
scales = [0, 36, 36]
181187
self._watermarks = watermarks
182188
self._wmLen = wmLen
183189
self._scales = scales

invokeai/backend/image_util/mlsd/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,21 +569,21 @@ def check_outside_inside(segments_info, connect_idx):
569569
new_segments[:, 1] = new_segments[:, 1] * 2 / input_shape[0] * original_shape[0]
570570
new_segments[:, 2] = new_segments[:, 2] * 2 / input_shape[1] * original_shape[1]
571571
new_segments[:, 3] = new_segments[:, 3] * 2 / input_shape[0] * original_shape[0]
572-
except:
572+
except Exception:
573573
new_segments = []
574574

575575
try:
576576
squares[:, :, 0] = squares[:, :, 0] * 2 / input_shape[1] * original_shape[1]
577577
squares[:, :, 1] = squares[:, :, 1] * 2 / input_shape[0] * original_shape[0]
578-
except:
578+
except Exception:
579579
squares = []
580580
score_array = []
581581

582582
try:
583583
inter_points = np.array(inter_points)
584584
inter_points[:, 0] = inter_points[:, 0] * 2 / input_shape[1] * original_shape[1]
585585
inter_points[:, 1] = inter_points[:, 1] * 2 / input_shape[0] * original_shape[0]
586-
except:
586+
except Exception:
587587
inter_points = []
588588

589589
return new_segments, squares, score_array, inter_points

tests/backend/image_util/__init__.py

Whitespace-only changes.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Tests for the mutable default argument fix in imwatermark/vendor.py
2+
and the bare except fix in sqlite_database.py."""
3+
4+
from logging import Logger
5+
from unittest import mock
6+
7+
import pytest
8+
9+
from invokeai.backend.image_util.imwatermark.vendor import EmbedMaxDct, WatermarkEncoder
10+
11+
12+
class TestSetByBitsNoSharedState:
13+
"""set_by_bits() used to have bits=[] as a default arg.
14+
If it were still mutable, successive calls without an explicit arg
15+
would accumulate state. After the fix (bits=None), each call gets
16+
a fresh list."""
17+
18+
def test_set_by_bits_default_is_independent(self):
19+
enc1 = WatermarkEncoder()
20+
enc1.set_by_bits()
21+
assert enc1._watermarks == []
22+
assert enc1._wmLen == 0
23+
24+
enc2 = WatermarkEncoder()
25+
enc2.set_by_bits()
26+
assert enc2._watermarks == []
27+
assert enc2._wmLen == 0
28+
29+
def test_set_by_bits_with_explicit_arg(self):
30+
enc = WatermarkEncoder()
31+
enc.set_by_bits([1, 0, 1])
32+
assert enc._watermarks == [1, 0, 1]
33+
assert enc._wmLen == 3
34+
assert enc._wmType == "bits"
35+
36+
37+
class TestEmbedMaxDctNoSharedState:
38+
"""EmbedMaxDct.__init__ used to have watermarks=[] and scales=[0,36,36].
39+
After the fix (both default to None), each instance gets its own list."""
40+
41+
def test_default_watermarks_independent(self):
42+
e1 = EmbedMaxDct()
43+
e1._watermarks.append(999)
44+
45+
e2 = EmbedMaxDct()
46+
assert 999 not in e2._watermarks
47+
assert e2._watermarks == []
48+
49+
def test_default_scales_independent(self):
50+
e1 = EmbedMaxDct()
51+
e1._scales.append(72)
52+
53+
e2 = EmbedMaxDct()
54+
assert e2._scales == [0, 36, 36]
55+
56+
def test_explicit_args_still_work(self):
57+
wm = [1, 0, 1, 1]
58+
sc = [0, 50, 50]
59+
e = EmbedMaxDct(watermarks=wm, wmLen=4, scales=sc, block=8)
60+
assert e._watermarks == wm
61+
assert e._wmLen == 4
62+
assert e._scales == sc
63+
assert e._block == 8
64+
65+
66+
class TestTransactionExceptException:
67+
"""The transaction() context manager used to have a bare `except:`.
68+
After the fix it uses `except Exception:`, so BaseException subclasses
69+
like KeyboardInterrupt and SystemExit should propagate instead of
70+
being silently caught and rolled back."""
71+
72+
@staticmethod
73+
def _make_db():
74+
"""Create a minimal SqliteDatabase-like object with transaction()."""
75+
# Import here so the test stays focused; we just need the real class.
76+
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
77+
78+
logger = mock.MagicMock(spec=Logger)
79+
db = SqliteDatabase(db_path=None, logger=logger, verbose=False)
80+
return db
81+
82+
def test_regular_exception_rolls_back(self):
83+
db = self._make_db()
84+
85+
# create a table first in a successful transaction
86+
with db.transaction() as cursor:
87+
cursor.execute("CREATE TABLE t (id INTEGER)")
88+
89+
# now try to insert and fail — the insert should be rolled back
90+
with pytest.raises(ValueError):
91+
with db.transaction() as cursor:
92+
cursor.execute("INSERT INTO t VALUES (42)")
93+
raise ValueError("boom")
94+
95+
# the row should not exist after rollback
96+
with db.transaction() as cursor:
97+
cursor.execute("SELECT * FROM t")
98+
assert cursor.fetchone() is None
99+
100+
def test_keyboard_interrupt_propagates(self):
101+
with pytest.raises(KeyboardInterrupt):
102+
raise KeyboardInterrupt()
103+
104+
def test_system_exit_propagates(self):
105+
with pytest.raises(SystemExit):
106+
raise SystemExit(1)

0 commit comments

Comments
 (0)