Skip to content

Commit 398f010

Browse files
committed
Merge pull request aichaos#16 from FujiMakoto/exceptions
RiveScript exceptions
2 parents a954048 + 2f46e50 commit 398f010

3 files changed

Lines changed: 104 additions & 29 deletions

File tree

rivescript/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@
4141
__all__ = ['rivescript']
4242
__version__ = '1.07'
4343

44-
from .rivescript import RiveScript
44+
from .rivescript import RiveScript, RiveScriptError, NoMatchError, NoReplyError,\
45+
ObjectError, DeepRecursionError, NoDefaultRandomTopicError, RepliesNotSortedError

rivescript/python.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ def load(self, name, code):
6767

6868
try:
6969
exec(source)
70-
#self._objects[name] = RSOBJ
70+
# self._objects[name] = RSOBJ
7171
except Exception as e:
7272
print("Failed to load code from object", name)
7373
print("The error given was: ", e)
7474

7575
def call(self, rs, name, user, fields):
7676
"""Invoke a previously loaded object."""
7777
# Call the dynamic method.
78-
if not name in self._objects:
78+
if name not in self._objects:
7979
return '[ERR: Object Not Found]'
8080
func = self._objects[name]
8181
reply = ''
@@ -84,6 +84,9 @@ def call(self, rs, name, user, fields):
8484
if reply is None:
8585
reply = ''
8686
except Exception as e:
87-
print("Error executing Python object:", e)
88-
reply = '[ERR: Error when executing Python object]'
87+
raise PythonObjectError("Error executing Python object: " + str(e))
8988
return str(reply)
89+
90+
91+
class PythonObjectError(Exception):
92+
pass

rivescript/rivescript.py

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ class RE(object):
7676
rs_version = 2.0
7777

7878
# Exportable constants.
79-
RS_ERR_MATCH = "ERR: No Reply Matched"
80-
RS_ERR_REPLY = "ERR: No Reply Found"
79+
RS_ERR_MATCH = "[ERR: No reply matched]"
80+
RS_ERR_REPLY = "[ERR: No reply found]"
81+
RS_ERR_DEEP_RECURSION = "[ERR: Deep recursion detected]"
82+
RS_ERR_OBJECT = "[ERR: Error when executing Python object]"
83+
RS_ERR_OBJECT_HANDLER = "[ERR: No Object Handler]"
84+
RS_ERR_OBJECT_MISSING = "[ERR: Object Not Found]"
8185

8286

8387
class RiveScript(object):
@@ -1511,7 +1515,7 @@ def current_user(self):
15111515
# Reply Fetching Methods #
15121516
############################################################################
15131517

1514-
def reply(self, user, msg):
1518+
def reply(self, user, msg, errors_as_replies=True):
15151519
"""Fetch a reply from the RiveScript brain."""
15161520
self._say("Get reply to [" + user + "] " + msg)
15171521

@@ -1525,20 +1529,30 @@ def reply(self, user, msg):
15251529

15261530
# If the BEGIN block exists, consult it first.
15271531
if "__begin__" in self._topics:
1528-
begin = self._getreply(user, 'request', context='begin')
1532+
begin = self._getreply(user, 'request', context='begin', ignore_object_errors=errors_as_replies)
15291533

15301534
# Okay to continue?
15311535
if '{ok}' in begin:
1532-
reply = self._getreply(user, msg)
1536+
try:
1537+
reply = self._getreply(user, msg, ignore_object_errors=errors_as_replies)
1538+
except RiveScriptError as e:
1539+
if not errors_as_replies:
1540+
raise
1541+
reply = e.error_message
15331542
begin = begin.replace('{ok}', reply)
15341543

15351544
reply = begin
15361545

15371546
# Run more tag substitutions.
1538-
reply = self._process_tags(user, msg, reply)
1547+
reply = self._process_tags(user, msg, reply, ignore_object_errors=errors_as_replies)
15391548
else:
15401549
# Just continue then.
1541-
reply = self._getreply(user, msg)
1550+
try:
1551+
reply = self._getreply(user, msg, ignore_object_errors=errors_as_replies)
1552+
except RiveScriptError as e:
1553+
if not errors_as_replies:
1554+
raise
1555+
reply = e.error_message
15421556

15431557
# Save their reply history.
15441558
oldInput = self._users[user]['__history__']['input'][:8]
@@ -1580,10 +1594,10 @@ def _format_message(self, msg, botreply=False):
15801594

15811595
return msg
15821596

1583-
def _getreply(self, user, msg, context='normal', step=0):
1597+
def _getreply(self, user, msg, context='normal', step=0, ignore_object_errors=True):
15841598
# Needed to sort replies?
15851599
if 'topics' not in self._sorted:
1586-
raise Exception("You forgot to call sort_replies()!")
1600+
raise RepliesNotSortedError("You must call sort_replies() once you are done loading RiveScript documents")
15871601

15881602
# Initialize the user's profile?
15891603
if user not in self._users:
@@ -1602,7 +1616,7 @@ def _getreply(self, user, msg, context='normal', step=0):
16021616

16031617
# Avoid deep recursion.
16041618
if step > self._depth:
1605-
return "ERR: Deep Recursion Detected"
1619+
raise DeepRecursionError
16061620

16071621
# Are we in the BEGIN statement?
16081622
if context == 'begin':
@@ -1627,7 +1641,7 @@ def _getreply(self, user, msg, context='normal', step=0):
16271641
if topic not in self._topics:
16281642
# This was handled before, which would mean topic=random and
16291643
# it doesn't exist. Serious issue!
1630-
return "[ERR: No default topic 'random' was found!]"
1644+
raise NoDefaultRandomTopicError("no default topic 'random' was found")
16311645

16321646
# Create a pointer for the matched data when we find it.
16331647
matched = None
@@ -1743,9 +1757,10 @@ def _getreply(self, user, msg, context='normal', step=0):
17431757
# See if there are any hard redirects.
17441758
if matched["redirect"]:
17451759
self._say("Redirecting us to " + matched["redirect"])
1746-
redirect = self._process_tags(user, msg, matched["redirect"], stars, thatstars, step)
1760+
redirect = self._process_tags(user, msg, matched["redirect"], stars, thatstars, step,
1761+
ignore_object_errors)
17471762
self._say("Pretend user said: " + redirect)
1748-
reply = self._getreply(user, redirect, step=(step + 1))
1763+
reply = self._getreply(user, redirect, step=(step + 1), ignore_object_errors=ignore_object_errors)
17491764
break
17501765

17511766
# Check the conditionals.
@@ -1761,8 +1776,8 @@ def _getreply(self, user, msg, context='normal', step=0):
17611776
self._say("Left: " + left + "; eq: " + eq + "; right: " + right + " => " + potreply)
17621777

17631778
# Process tags all around.
1764-
left = self._process_tags(user, msg, left, stars, thatstars, step)
1765-
right = self._process_tags(user, msg, right, stars, thatstars, step)
1779+
left = self._process_tags(user, msg, left, stars, thatstars, step, ignore_object_errors)
1780+
right = self._process_tags(user, msg, right, stars, thatstars, step, ignore_object_errors)
17661781

17671782
# Defaults?
17681783
if len(left) == 0:
@@ -1828,9 +1843,9 @@ def _getreply(self, user, msg, context='normal', step=0):
18281843

18291844
# Still no reply?
18301845
if not foundMatch:
1831-
reply = RS_ERR_MATCH
1846+
raise NoMatchError
18321847
elif len(reply) == 0:
1833-
reply = RS_ERR_REPLY
1848+
raise NoReplyError
18341849

18351850
self._say("Reply: " + reply)
18361851

@@ -1851,7 +1866,7 @@ def _getreply(self, user, msg, context='normal', step=0):
18511866
reply = reply.replace('<set {key}={value}>'.format(key=match[0], value=match[1]), '')
18521867
else:
18531868
# Process more tags if not in BEGIN.
1854-
reply = self._process_tags(user, msg, reply, stars, thatstars, step)
1869+
reply = self._process_tags(user, msg, reply, stars, thatstars, step, ignore_object_errors)
18551870

18561871
return reply
18571872

@@ -1860,9 +1875,9 @@ def _substitute(self, msg, kind):
18601875

18611876
# Safety checking.
18621877
if 'lists' not in self._sorted:
1863-
raise Exception("You forgot to call sort_replies()!")
1878+
raise RepliesNotSortedError("You must call sort_replies() once you are done loading RiveScript documents")
18641879
if kind not in self._sorted["lists"]:
1865-
raise Exception("You forgot to call sort_replies()!")
1880+
raise RepliesNotSortedError("You must call sort_replies() once you are done loading RiveScript documents")
18661881

18671882
# Get the substitution map.
18681883
subs = None
@@ -2008,7 +2023,7 @@ def _precompile_regexp(self, trigger):
20082023

20092024
self._regexc["trigger"][trigger] = self._reply_regexp(None, trigger)
20102025

2011-
def _process_tags(self, user, msg, reply, st=[], bst=[], depth=0):
2026+
def _process_tags(self, user, msg, reply, st=[], bst=[], depth=0, ignore_object_errors=True):
20122027
"""Post process tags in a message."""
20132028
stars = ['']
20142029
stars.extend(st)
@@ -2197,11 +2212,21 @@ def _process_tags(self, user, msg, reply, st=[], bst=[], depth=0):
21972212
lang = self._objlangs[obj]
21982213
if lang in self._handlers:
21992214
# We do.
2200-
output = self._handlers[lang].call(self, obj, user, args)
2215+
try:
2216+
output = self._handlers[lang].call(self, obj, user, args)
2217+
except python.PythonObjectError as e:
2218+
self._warn(str(e))
2219+
if not ignore_object_errors:
2220+
raise ObjectError(str(e))
2221+
output = RS_ERR_OBJECT
22012222
else:
2202-
output = '[ERR: No Object Handler]'
2223+
if not ignore_object_errors:
2224+
raise ObjectError(RS_ERR_OBJECT_HANDLER)
2225+
output = RS_ERR_OBJECT_HANDLER
22032226
else:
2204-
output = '[ERR: Object Not Found]'
2227+
if not ignore_object_errors:
2228+
raise ObjectError(RS_ERR_OBJECT_MISSING)
2229+
output = RS_ERR_OBJECT_MISSING
22052230

22062231
reply = reply.replace('<call>{match}</call>'.format(match=match), output)
22072232

@@ -2431,6 +2456,52 @@ def _dump(self):
24312456
print("=== Syntax Tree ===")
24322457
pp.pprint(self._syntax)
24332458

2459+
2460+
################################################################################
2461+
# Exception Classes #
2462+
################################################################################
2463+
2464+
class RiveScriptError(Exception):
2465+
"""RiveScript base exception class"""
2466+
def __init__(self, error_message=None):
2467+
super(RiveScriptError, self).__init__(error_message)
2468+
self.error_message = error_message
2469+
2470+
2471+
class NoMatchError(RiveScriptError):
2472+
"""No reply could be matched"""
2473+
def __init__(self):
2474+
super(NoMatchError, self).__init__(RS_ERR_MATCH)
2475+
2476+
2477+
class NoReplyError(RiveScriptError):
2478+
"""No reply could be found"""
2479+
def __init__(self):
2480+
super(NoReplyError, self).__init__(RS_ERR_REPLY)
2481+
2482+
2483+
class ObjectError(RiveScriptError):
2484+
"""An error occurred when executing a Python object"""
2485+
def __init__(self, error_message=RS_ERR_OBJECT):
2486+
super(ObjectError, self).__init__(error_message)
2487+
2488+
2489+
class DeepRecursionError(RiveScriptError):
2490+
"""Prevented an infinite loop / deep recursion, unable to retrieve a reply for this message"""
2491+
def __init__(self):
2492+
super(DeepRecursionError, self).__init__(RS_ERR_DEEP_RECURSION)
2493+
2494+
2495+
class NoDefaultRandomTopicError(Exception):
2496+
"""No default topic 'random' could be found, critical error"""
2497+
pass
2498+
2499+
2500+
class RepliesNotSortedError(Exception):
2501+
"""sort_replies() was not called after the RiveScript documents were loaded, critical error"""
2502+
pass
2503+
2504+
24342505
################################################################################
24352506
# Interactive Mode #
24362507
################################################################################

0 commit comments

Comments
 (0)