Skip to content

Commit 657e1c4

Browse files
committed
feat: Add spellcheck, dict operation commands
1 parent c20eac6 commit 657e1c4

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

redisearch/client.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ class Client(object):
109109
DEL_CMD = 'FT.DEL'
110110
AGGREGATE_CMD = 'FT.AGGREGATE'
111111
CURSOR_CMD = 'FT.CURSOR'
112+
SPELLCHECK_CMD = 'FT.SPELLCHECK'
113+
DICT_ADD_CMD = 'FT.DICTADD'
114+
DICT_DEL_CMD = 'FT.DICTDEL'
115+
DICT_DUMP_CMD = 'FT.DICTDUMP'
112116

113117

114118
NOOFFSETS = 'NOOFFSETS'
@@ -383,3 +387,100 @@ def aggregate(self, query):
383387

384388
res = AggregateResult(rows, cursor, schema)
385389
return res
390+
391+
def spellcheck(self, query, distance=None, include=None, exclude=None):
392+
"""
393+
Issue a spellcheck query
394+
395+
### Parameters
396+
397+
**query**: search query.
398+
**distance***: the maximal Levenshtein distance for spelling suggestions (default: 1, max: 4).
399+
**include**: specifies an inclusion custom dictionary.
400+
**exclude**: specifies an exclusion custom dictionary.
401+
"""
402+
cmd = [self.SPELLCHECK_CMD, self.index_name, query]
403+
if distance:
404+
cmd.extend(['DISTANCE', distance])
405+
406+
if include:
407+
cmd.extend(['TERMS', 'INCLUDE', include])
408+
409+
if exclude:
410+
cmd.extend(['TERMS', 'EXCLUDE', exclude])
411+
412+
raw = self.redis.execute_command(*cmd)
413+
414+
corrections = {}
415+
if raw == 0:
416+
return corrections
417+
418+
for _correction in raw:
419+
if isinstance(_correction, long) and _correction == 0:
420+
continue
421+
422+
if len(_correction) != 3:
423+
continue
424+
if not _correction[2]:
425+
continue
426+
if not _correction[2][0]:
427+
continue
428+
429+
# For spellcheck output
430+
# 1) 1) "TERM"
431+
# 2) "{term1}"
432+
# 3) 1) 1) "{score1}"
433+
# 2) "{suggestion1}"
434+
# 2) 1) "{score2}"
435+
# 2) "{suggestion2}"
436+
#
437+
# Following dictionary will be made
438+
# corrections = {
439+
# '{term1}': [
440+
# {'score': '{score1}', 'suggestion': '{suggestion1}'},
441+
# {'score': '{score2}', 'suggestion': '{suggestion2}'}
442+
# ]
443+
# }
444+
corrections[_correction[1]] = [
445+
{'score': _item[0], 'suggestion':_item[1]}
446+
for _item in _correction[2]
447+
]
448+
449+
return corrections
450+
451+
def dict_add(self, name, *terms):
452+
"""Adds terms to a dictionary.
453+
454+
### Parameters
455+
456+
- **name**: Dictionary name.
457+
- **terms**: List of items for adding to the dictionary.
458+
"""
459+
cmd = [self.DICT_ADD_CMD, name]
460+
cmd.extend(terms)
461+
raw = self.redis.execute_command(*cmd)
462+
return raw
463+
464+
def dict_del(self, name, *terms):
465+
"""Deletes terms from a dictionary.
466+
467+
### Parameters
468+
469+
- **name**: Dictionary name.
470+
- **terms**: List of items for removing from the dictionary.
471+
"""
472+
cmd = [self.DICT_DEL_CMD, name]
473+
cmd.extend(terms)
474+
raw = self.redis.execute_command(*cmd)
475+
return raw
476+
477+
def dict_dump(self, name):
478+
"""Dumps all terms in the given dictionary.
479+
480+
### Parameters
481+
482+
- **name**: Dictionary name.
483+
"""
484+
cmd = [self.DICT_DUMP_CMD, name]
485+
raw = self.redis.execute_command(*cmd)
486+
return raw

test/test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,41 @@ def testAlterSchemaAdd(self):
534534
res = client.search(q)
535535
self.assertEqual(1, res.total)
536536

537+
def testSpellCheck(self):
538+
client = self.getCleanClient('idx')
539+
client.create_index((TextField('f1'), TextField('f2')))
540+
541+
client.add_document('doc1', f1='some valid content', f2='this is sample text')
542+
client.add_document('doc2', f1='very important', f2='lorem ipsum')
543+
544+
for i in self.retry_with_reload():
545+
res = client.spellcheck('impornant')
546+
self.assertEqual('important', res['impornant'][0]['suggestion'])
547+
548+
res = client.spellcheck('contnt')
549+
self.assertEqual('content', res['contnt'][0]['suggestion'])
550+
551+
def testDictOps(self):
552+
client = self.getCleanClient('idx')
553+
client.create_index((TextField('f1'), TextField('f2')))
554+
555+
for i in self.retry_with_reload():
556+
# Add three items
557+
res = client.dict_add('custom_dict', 'item1', 'item2', 'item3')
558+
self.assertEqual(3, res)
559+
560+
# Remove one item
561+
res = client.dict_del('custom_dict', 'item2')
562+
self.assertEqual(1, res)
563+
564+
# Dump dict and inspect content
565+
res = client.dict_dump('custom_dict')
566+
self.assertEqual(['item1', 'item3'], res)
567+
568+
# Remove rest of the items before reload
569+
client.dict_del('custom_dict', *res)
570+
571+
537572
if __name__ == '__main__':
538573

539574
unittest.main()

0 commit comments

Comments
 (0)