Skip to content

Commit 126d388

Browse files
committed
Added a silent failure option to prevent Haystack from suppressing some failures.
This option defaults to ``True`` for compatibility & to prevent cases where lost connections can break reindexes/searches.
1 parent 021e453 commit 126d388

File tree

7 files changed

+115
-14
lines changed

7 files changed

+115
-14
lines changed

docs/multiple_index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ complete setup that accesses all backends might look like::
2525
'TIMEOUT': 60 * 5,
2626
'INCLUDE_SPELLING': True,
2727
'BATCH_SIZE': 100,
28+
'SILENTLY_FAIL': True,
2829
},
2930
'autocomplete': {
3031
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
@@ -33,15 +34,18 @@ complete setup that accesses all backends might look like::
3334
'POST_LIMIT': 128 * 1024 * 1024,
3435
'INCLUDE_SPELLING': True,
3536
'BATCH_SIZE': 100,
37+
'SILENTLY_FAIL': True,
3638
},
3739
'slave': {
3840
'ENGINE': 'xapian_backend.XapianEngine',
3941
'PATH': '/home/search/xapian_index',
4042
'INCLUDE_SPELLING': True,
4143
'BATCH_SIZE': 100,
44+
'SILENTLY_FAIL': True,
4245
},
4346
'db': {
4447
'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
48+
'SILENTLY_FAIL': True,
4549
}
4650
}
4751

haystack/backends/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def __init__(self, connection_alias, **connection_options):
6767
self.timeout = connection_options.get('TIMEOUT', 10)
6868
self.include_spelling = connection_options.get('INCLUDE_SPELLING', False)
6969
self.batch_size = connection_options.get('BATCH_SIZE', 1000)
70+
self.silently_fail = connection_options.get('SILENTLY_FAIL', True)
7071

7172
def update(self, index, iterable):
7273
"""

haystack/backends/simple_backend.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,14 @@ class SimpleSearchBackend(BaseSearchBackend):
2929
def update(self, indexer, iterable, commit=True):
3030
if settings.DEBUG:
3131
logger.warning('update is not implemented in this backend')
32-
pass
3332

3433
def remove(self, obj, commit=True):
3534
if settings.DEBUG:
3635
logger.warning('remove is not implemented in this backend')
37-
pass
3836

3937
def clear(self, models=[], commit=True):
4038
if settings.DEBUG:
4139
logger.warning('clear is not implemented in this backend')
42-
pass
4340

4441
@log_query
4542
def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,

haystack/backends/solr_backend.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,18 @@ def update(self, index, iterable, commit=True):
5151
for obj in iterable:
5252
docs.append(index.full_prepare(obj))
5353
except UnicodeDecodeError:
54-
sys.stderr.write("Chunk failed.\n")
54+
if not self.silently_fail:
55+
raise
56+
57+
self.log.error("Chunk failed.\n")
5558

5659
if len(docs) > 0:
5760
try:
5861
self.conn.add(docs, commit=commit, boost=index.get_field_weights())
5962
except (IOError, SolrError), e:
63+
if not self.silently_fail:
64+
raise
65+
6066
self.log.error("Failed to add documents to Solr: %s", e)
6167

6268
def remove(self, obj_or_string, commit=True):
@@ -69,6 +75,9 @@ def remove(self, obj_or_string, commit=True):
6975
}
7076
self.conn.delete(**kwargs)
7177
except (IOError, SolrError), e:
78+
if not self.silently_fail:
79+
raise
80+
7281
self.log.error("Failed to remove document '%s' from Solr: %s", solr_id, e)
7382

7483
def clear(self, models=[], commit=True):
@@ -87,6 +96,9 @@ def clear(self, models=[], commit=True):
8796
# Run an optimize post-clear. http://wiki.apache.org/solr/FAQ#head-9aafb5d8dff5308e8ea4fcf4b71f19f029c4bb99
8897
self.conn.optimize()
8998
except (IOError, SolrError), e:
99+
if not self.silently_fail:
100+
raise
101+
90102
if len(models):
91103
self.log.error("Failed to clear Solr index of models '%s': %s", ','.join(models_to_delete), e)
92104
else:
@@ -175,6 +187,9 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
175187
try:
176188
raw_results = self.conn.search(query_string, **kwargs)
177189
except (IOError, SolrError), e:
190+
if not self.silently_fail:
191+
raise
192+
178193
self.log.error("Failed to query Solr using '%s': %s", query_string, e)
179194
raw_results = EmptyResults()
180195

@@ -230,6 +245,9 @@ def more_like_this(self, model_instance, additional_query_string=None,
230245
try:
231246
raw_results = self.conn.more_like_this(query, field_name, **params)
232247
except (IOError, SolrError), e:
248+
if not self.silently_fail:
249+
raise
250+
233251
self.log.error("Failed to fetch More Like This from Solr for document '%s': %s", query, e)
234252
raw_results = EmptyResults()
235253

haystack/backends/whoosh_backend.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,13 @@ def update(self, index, iterable, commit=True):
177177
for key in doc:
178178
doc[key] = self._from_python(doc[key])
179179

180-
writer.update_document(**doc)
180+
try:
181+
writer.update_document(**doc)
182+
except Exception, e:
183+
if not self.silently_fail:
184+
raise
185+
186+
self.log.error("Failed to add documents to Whoosh: %s", e)
181187

182188
if len(iterable) > 0:
183189
# For now, commit no matter what, as we run into locking issues otherwise.
@@ -194,23 +200,36 @@ def remove(self, obj_or_string, commit=True):
194200

195201
self.index = self.index.refresh()
196202
whoosh_id = get_identifier(obj_or_string)
197-
self.index.delete_by_query(q=self.parser.parse(u'%s:"%s"' % (ID, whoosh_id)))
203+
204+
try:
205+
self.index.delete_by_query(q=self.parser.parse(u'%s:"%s"' % (ID, whoosh_id)))
206+
except Exception, e:
207+
if not self.silently_fail:
208+
raise
209+
210+
self.log.error("Failed to remove document '%s' from Whoosh: %s", whoosh_id, e)
198211

199212
def clear(self, models=[], commit=True):
200213
if not self.setup_complete:
201214
self.setup()
202215

203216
self.index = self.index.refresh()
204217

205-
if not models:
206-
self.delete_index()
207-
else:
208-
models_to_delete = []
209-
210-
for model in models:
211-
models_to_delete.append(u"%s:%s.%s" % (DJANGO_CT, model._meta.app_label, model._meta.module_name))
218+
try:
219+
if not models:
220+
self.delete_index()
221+
else:
222+
models_to_delete = []
223+
224+
for model in models:
225+
models_to_delete.append(u"%s:%s.%s" % (DJANGO_CT, model._meta.app_label, model._meta.module_name))
226+
227+
self.index.delete_by_query(q=self.parser.parse(u" OR ".join(models_to_delete)))
228+
except Exception, e:
229+
if not self.silently_fail:
230+
raise
212231

213-
self.index.delete_by_query(q=self.parser.parse(u" OR ".join(models_to_delete)))
232+
self.log.error("Failed to remove document '%s' from Whoosh: %s", whoosh_id, e)
214233

215234
def delete_index(self):
216235
# Per the Whoosh mailing list, if wiping out everything from the index,
@@ -369,6 +388,9 @@ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None,
369388
try:
370389
raw_page = ResultsPage(raw_results, page_num, page_length)
371390
except ValueError:
391+
if not self.silently_fail:
392+
raise
393+
372394
return {
373395
'results': [],
374396
'hits': 0,
@@ -486,6 +508,9 @@ def more_like_this(self, model_instance, additional_query_string=None,
486508
try:
487509
raw_page = ResultsPage(raw_results, page_num, page_length)
488510
except ValueError:
511+
if not self.silently_fail:
512+
raise
513+
489514
return {
490515
'results': [],
491516
'hits': 0,

tests/solr_tests/tests/solr_backend.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,33 @@ def setUp(self):
173173
def tearDown(self):
174174
connections['default']._index = self.old_ui
175175
super(SolrSearchBackendTestCase, self).tearDown()
176+
177+
def test_non_silent(self):
178+
bad_sb = connections['default'].backend('bad', URL='http://omg.wtf.bbq:1000/solr', SILENTLY_FAIL=False)
179+
180+
try:
181+
bad_sb.update(self.smmi, self.sample_objs)
182+
self.fail()
183+
except:
184+
pass
185+
186+
try:
187+
bad_sb.remove('core.mockmodel.1')
188+
self.fail()
189+
except:
190+
pass
191+
192+
try:
193+
bad_sb.clear()
194+
self.fail()
195+
except:
196+
pass
197+
198+
try:
199+
bad_sb.search('foo')
200+
self.fail()
201+
except:
202+
pass
176203

177204
def test_update(self):
178205
self.sb.update(self.smmi, self.sample_objs)

tests/whoosh_tests/tests/whoosh_backend.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,35 @@ def whoosh_search(self, query):
127127
searcher = self.raw_whoosh.searcher()
128128
return searcher.search(self.parser.parse(query), limit=1000)
129129

130+
def test_non_silent(self):
131+
bad_sb = connections['default'].backend('bad', PATH='/tmp/bad_whoosh', SILENTLY_FAIL=False)
132+
bad_sb.use_file_storage = False
133+
bad_sb.storage = 'omg.wtf.bbq'
134+
135+
try:
136+
bad_sb.update(self.wmmi, self.sample_objs)
137+
self.fail()
138+
except:
139+
pass
140+
141+
try:
142+
bad_sb.remove('core.mockmodel.1')
143+
self.fail()
144+
except:
145+
pass
146+
147+
try:
148+
bad_sb.clear()
149+
self.fail()
150+
except:
151+
pass
152+
153+
try:
154+
bad_sb.search('foo')
155+
self.fail()
156+
except:
157+
pass
158+
130159
def test_update(self):
131160
self.sb.update(self.wmmi, self.sample_objs)
132161

0 commit comments

Comments
 (0)