Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase efficiency of mongoDB adapter #173

Merged
merged 5 commits into from
Jun 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions chatterbot/adapters/logic/base_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,7 @@ def has_storage_context(self):
"""
return self.context and self.context.storage

def get_available_statements(self, statement_list=None):
from chatterbot.conversation.utils import get_response_statements

if statement_list:
statement_list = get_response_statements(statement_list)

# Check if the list is empty
if not statement_list and self.has_storage_context:
all_statements = self.context.storage.filter()
statement_list = get_response_statements(all_statements)

return statement_list

def get(self, input_statement, statement_list=None):
def get(self, input_statement):
"""
This method should be overridden with one to select a match
based on the input statement.
Expand Down
4 changes: 2 additions & 2 deletions chatterbot/adapters/logic/closest_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ class ClosestMatchAdapter(BaseMatchAdapter):
of each statement.
"""

def get(self, input_statement, statement_list=None):
def get(self, input_statement):
"""
Takes a statement string and a list of statement strings.
Returns the closest matching statement from the list.
"""
statement_list = self.get_available_statements(statement_list)
statement_list = self.context.storage.get_response_statements()

if not statement_list:
if self.has_storage_context:
Expand Down
4 changes: 2 additions & 2 deletions chatterbot/adapters/logic/closest_meaning.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ def get_similarity(self, string1, string2):

return total_similarity

def get(self, input_statement, statement_list=None):
def get(self, input_statement):
"""
Takes a statement string and a list of statement strings.
Returns the closest matching statement from the list.
"""
statement_list = self.get_available_statements(statement_list)
statement_list = self.context.storage.get_response_statements()

if not statement_list:
if self.has_storage_context:
Expand Down
27 changes: 27 additions & 0 deletions chatterbot/adapters/storage/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,33 @@ def remove(self, statement_text):

self.statements.remove({'text': statement_text})

def get_response_statements(self):
"""
Return only statements that are in response to another statement.
A statement must exist which lists the closest matching statement in the
in_response_to field. Otherwise, the logic adapter may find a closest
matching statement that does not have a known response.
"""
response_query = self.statements.distinct('in_response_to.text')
statement_query = self.statements.find({
'text': {
'$in': response_query
}
})

statement_list = list(statement_query)

statement_objects = []

for statement in statement_list:
values = dict(statement)
statement_text = values['text']

del(values['text'])
statement_objects.append(Statement(statement_text, **values))

return statement_objects

def drop(self):
"""
Remove the database.
Expand Down
26 changes: 26 additions & 0 deletions chatterbot/adapters/storage/storage_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,32 @@ def drop(self):
"""
raise self.AdapterMethodNotImplementedError()

def get_response_statements(self):
"""
Return only statements that are in response to another statement.
A statement must exist which lists the closest matching statement in the
in_response_to field. Otherwise, the logic adapter may find a closest
matching statement that does not have a known response.

This method may be overridden by a child class to provide more a
efficient method to get these results.
"""
statement_list = self.filter()

responses = set()
to_remove = list()
for statement in statement_list:
for response in statement.in_response_to:
responses.add(response.text)
for statement in statement_list:
if statement.text not in responses:
to_remove.append(statement)

for statement in to_remove:
statement_list.remove(statement)

return statement_list

class EmptyDatabaseException(Exception):

def __init__(self, message="The database currently contains no entries. At least one entry is expected. You may need to train your chat bot to populate your database."):
Expand Down
20 changes: 0 additions & 20 deletions chatterbot/conversation/utils.py

This file was deleted.

20 changes: 0 additions & 20 deletions tests/conversation_tests/test_utils.py

This file was deleted.

41 changes: 26 additions & 15 deletions tests/logic_adapter_tests/test_closest_match.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
from unittest import TestCase
from mock import MagicMock, Mock
from chatterbot.adapters.logic import ClosestMatchAdapter
from chatterbot.adapters.storage import StorageAdapter
from chatterbot.conversation import Statement, Response


class MockContext(object):
def __init__(self):
self.storage = StorageAdapter()

self.storage.get_random = Mock(
side_effect=ClosestMatchAdapter.EmptyDatasetException()
)


class ClosestMatchAdapterTests(TestCase):

def setUp(self):
self.adapter = ClosestMatchAdapter()

# Add a mock storage adapter to the context
self.adapter.set_context(MockContext())

def test_no_choices(self):
possible_choices = []
self.adapter.context.storage.filter = MagicMock(return_value=[])

statement = Statement("What is your quest?")

with self.assertRaises(ClosestMatchAdapter.EmptyDatasetException):
self.adapter.get(statement, possible_choices)
self.adapter.get(statement)

def test_get_closest_statement(self):
"""
Expand All @@ -29,47 +44,43 @@ def test_get_closest_statement(self):
Statement("Yuck, black licorice jelly beans.", in_response_to=[Response("What is the meaning of life?")]),
Statement("I hear you are going on a quest?", in_response_to=[Response("Who do you love?")]),
]
self.adapter.context.storage.filter = MagicMock(return_value=possible_choices)

statement = Statement("What is your quest?")

confidence, match = self.adapter.get(statement, possible_choices)
confidence, match = self.adapter.get(statement)

self.assertEqual("What... is your quest?", match)

def test_confidence_exact_match(self):
possible_choices = [
Statement("What is your quest?", in_response_to=[Response("What is your quest?")])
]
self.adapter.context.storage.filter = MagicMock(return_value=possible_choices)

statement = Statement("What is your quest?")

confidence, match = self.adapter.get(
statement, possible_choices
)
confidence, match = self.adapter.get(statement)

self.assertEqual(confidence, 1)

def test_confidence_half_match(self):
possible_choices = [
Statement("xxyy", in_response_to=[Response("xxyy")])
]
self.adapter.context.storage.filter = MagicMock(return_value=possible_choices)

statement = Statement("wwxx")

confidence, match = self.adapter.get(
statement, possible_choices
)
confidence, match = self.adapter.get(statement)

self.assertEqual(confidence, 0.5)

def test_confidence_no_match(self):
possible_choices = [
Statement("xxx", in_response_to=[Response("xxx")])
]
self.adapter.context.storage.filter = MagicMock(return_value=possible_choices)

statement = Statement("yyy")

confidence, match = self.adapter.get(
statement, possible_choices
)
confidence, match = self.adapter.get(statement)

self.assertEqual(confidence, 0)
23 changes: 19 additions & 4 deletions tests/logic_adapter_tests/test_closest_meaning.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
from unittest import TestCase
from mock import MagicMock, Mock
from chatterbot.adapters.logic import ClosestMeaningAdapter
from chatterbot.adapters.storage import StorageAdapter
from chatterbot.conversation import Statement, Response


class MockContext(object):
def __init__(self):
self.storage = StorageAdapter()

self.storage.get_random = Mock(
side_effect=ClosestMeaningAdapter.EmptyDatasetException()
)


class ClosestMeaningAdapterTests(TestCase):

def setUp(self):
self.adapter = ClosestMeaningAdapter()

# Add a mock storage adapter to the context
self.adapter.set_context(MockContext())

def test_no_choices(self):
possible_choices = []
self.adapter.context.storage.filter = MagicMock(return_value=[])
statement = Statement("Hello")

with self.assertRaises(ClosestMeaningAdapter.EmptyDatasetException):
self.adapter.get(statement, possible_choices)
self.adapter.get(statement)

def test_get_closest_statement(self):
"""
Expand All @@ -26,9 +40,10 @@ def test_get_closest_statement(self):
Statement("This is a beautiful swamp.", in_response_to=[Response("This is a beautiful swamp.")]),
Statement("It smells like swamp.", in_response_to=[Response("It smells like swamp.")])
]
statement = Statement("This is a lovely swamp.")
self.adapter.context.storage.filter = MagicMock(return_value=possible_choices)

confidence, match = self.adapter.get(statement, possible_choices)
statement = Statement("This is a lovely swamp.")
confidence, match = self.adapter.get(statement)

self.assertEqual("This is a lovely bog.", match)

1 change: 1 addition & 0 deletions tests/logic_adapter_tests/test_logic_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest import TestCase
from chatterbot.adapters.logic import LogicAdapter


class LogicAdapterTestCase(TestCase):
"""
This test case is for the LogicAdapter base class.
Expand Down
21 changes: 21 additions & 0 deletions tests/storage_adapter_tests/test_jsondb_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,27 @@ def test_remove_response(self):

self.assertEqual(results, [])

def test_get_response_statements(self):
"""
Test that we are able to get a list of only statements
that are known to be in response to another statement.
"""
statement_list = [
Statement("What... is your quest?"),
Statement("This is a phone."),
Statement("A what?", in_response_to=[Response("This is a phone.")]),
Statement("A phone.", in_response_to=[Response("A what?")])
]

for statement in statement_list:
self.adapter.update(statement)

responses = self.adapter.get_response_statements()

self.assertEqual(len(responses), 2)
self.assertIn("This is a phone.", responses)
self.assertIn("A what?", responses)


class JsonDatabaseAdapterFilterTestCase(JsonAdapterTestCase):

Expand Down
21 changes: 21 additions & 0 deletions tests/storage_adapter_tests/test_mongo_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,27 @@ def test_remove_response(self):

self.assertEqual(results, [])

def test_get_response_statements(self):
"""
Test that we are able to get a list of only statements
that are known to be in response to another statement.
"""
statement_list = [
Statement("What... is your quest?"),
Statement("This is a phone."),
Statement("A what?", in_response_to=[Response("This is a phone.")]),
Statement("A phone.", in_response_to=[Response("A what?")])
]

for statement in statement_list:
self.adapter.update(statement)

responses = self.adapter.get_response_statements()

self.assertEqual(len(responses), 2)
self.assertIn("This is a phone.", responses)
self.assertIn("A what?", responses)


class MongoAdapterFilterTestCase(MongoAdapterTestCase):

Expand Down
5 changes: 5 additions & 0 deletions tests/storage_adapter_tests/test_storage_adapter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from unittest import TestCase
from chatterbot.adapters.storage import StorageAdapter
from chatterbot.conversation import Statement, Response

class StorageAdapterTestCase(TestCase):
"""
Expand Down Expand Up @@ -37,6 +38,10 @@ def test_get_random(self):
with self.assertRaises(StorageAdapter.AdapterMethodNotImplementedError):
self.adapter.get_random()

def test_get_response_statements(self):
with self.assertRaises(StorageAdapter.AdapterMethodNotImplementedError):
self.adapter.get_response_statements()

def test_drop(self):
with self.assertRaises(StorageAdapter.AdapterMethodNotImplementedError):
self.adapter.drop()