Skip to content

Commit 3a8ad97

Browse files
authored
Merge pull request #84 from aichaos/feature/sessions
Add official Redis driver for RiveScript Sessions
2 parents 4c3d82a + d158523 commit 3a8ad97

13 files changed

+247
-87
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
__pycache__
22
*.pyc
3+
build/
4+
dist/
5+
*.egg-info/

Changes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Revision history for the Python package RiveScript.
44

5+
## 1.14.5 - Feb 20 2017
6+
7+
- Bugfix when storing the user's `last_match` variable when a `%Previous` is
8+
active (it was storing a regexp object and not a string), to help third party
9+
session drivers (e.g. Redis) to work.
10+
511
## 1.14.4 - Dec 14 2016
612

713
- Fix the `last_match()` function so that it returns `None` when there was no

contrib/redis/LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Noah Petherbridge
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

contrib/redis/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Redis Sessions for RiveScript
2+
3+
This module installs support for using a [Redis cache](https://redis.io/) to
4+
store user variables for RiveScript.
5+
6+
```bash
7+
pip install rivescript-redis
8+
```
9+
10+
By default, RiveScript keeps user variables in an in-memory dictionary. This
11+
driver allows for using a Redis cache instead. All user variables will then be
12+
persisted to Redis automatically, which enables the bot to remember users after
13+
a reboot.
14+
15+
## Quick Start
16+
17+
```python
18+
from rivescript import RiveScript
19+
from rivescript_redis import RedisSessionManager
20+
21+
# Initialize RiveScript like normal but give it the RedisSessionManager.
22+
bot = RiveScript(
23+
session_manager=RedisSessionManager(
24+
# You can customize the key prefix: this is the default. Be sure to
25+
# include a separator like '/' at the end so the keys end up looking
26+
# like e.g. 'rivescript/username'
27+
prefix='rivescript/',
28+
29+
# All other options are passed directly through to redis.StrictRedis()
30+
host='localhost',
31+
port=6379,
32+
db=0,
33+
),
34+
)
35+
36+
bot.load_directory("eg/brain")
37+
bot.sort_replies()
38+
39+
# Get a reply. The user variables for 'alice' would be persisted in Redis
40+
# at the (default) key 'rivescript/alice'
41+
print(bot.reply("alice", "Hello robot!"))
42+
```
43+
44+
## Example
45+
46+
An example bot that uses this driver can be found in the
47+
[`eg/sessions`](https://github.com/aichaos/rivescript-python/tree/master/eg/sessions)
48+
directory of the `rivescript-python` project.
49+
50+
## See Also
51+
52+
* Documentation for [redis-py](https://redis-py.readthedocs.io/en/latest/),
53+
the Redis client module used by this driver.
54+
55+
## License
56+
57+
This module is licensed under the same terms as RiveScript itself (MIT).

contrib/redis/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
redis
2+
rivescript

contrib/redis/rivescript_redis.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# RiveScript-Python
2+
#
3+
# This code is released under the MIT License.
4+
# See the "LICENSE" file for more information.
5+
#
6+
# https://www.rivescript.com/
7+
8+
from __future__ import unicode_literals
9+
import json
10+
import redis
11+
from rivescript.sessions import SessionManager
12+
13+
__author__ = 'Noah Petherbridge'
14+
__copyright__ = 'Copyright 2017, Noah Petherbridge'
15+
__license__ = 'MIT'
16+
__status__ = 'Beta'
17+
__version__ = '0.1.0'
18+
19+
class RedisSessionManager(SessionManager):
20+
"""A Redis powered session manager for RiveScript."""
21+
22+
def __init__(self, prefix="rivescript/", *args, **kwargs):
23+
"""Initialize the Redis session driver.
24+
25+
Apart from the ``prefix`` parameter, all other options are passed
26+
directly to the underlying Redis constructor, ``redis.StrictRedis()``.
27+
See the documentation of redis-py for more information. Commonly used
28+
arguments are listed below for convenience.
29+
30+
Args:
31+
prefix (string): the key to prefix all the Redis keys with. The
32+
default is ``rivescript/``, so that for a username of ``alice``
33+
the key would be ``rivescript/alice``.
34+
host (string): Hostname of the Redis server.
35+
port (int): Port number of the Redis server.
36+
db (int): Database number in Redis.
37+
"""
38+
self.client = redis.StrictRedis(*args, **kwargs)
39+
self.prefix = prefix
40+
self.frozen = "frozen:" + prefix
41+
42+
def _key(self, username, frozen=False):
43+
"""Translate a username into a key for Redis."""
44+
if frozen:
45+
return self.frozen + username
46+
return self.prefix + username
47+
48+
def _get_user(self, username):
49+
"""Custom helper method to retrieve a user's data from Redis."""
50+
data = self.client.get(self._key(username))
51+
if data is None:
52+
return None
53+
return json.loads(data.decode())
54+
55+
# The below functions implement the RiveScript SessionManager.
56+
57+
def set(self, username, new_vars):
58+
data = self._get_user(username)
59+
if data is None:
60+
data = self.default_session()
61+
data.update(new_vars)
62+
self.client.set(self._key(username), json.dumps(data))
63+
64+
def get(self, username, key):
65+
data = self._get_user(username)
66+
if data is None:
67+
return None
68+
return data.get(key, "undefined")
69+
70+
def get_any(self, username):
71+
return self._get_user(username)
72+
73+
def get_all(self):
74+
users = self.client.keys(self.prefix + "*")
75+
result = dict()
76+
for user in users:
77+
username = users.replace(self.prefix, "")
78+
result[username] = self._get_user(username)
79+
return result
80+
81+
def reset(self, username):
82+
self.client.delete(self._key(username))
83+
84+
def reset_all(self):
85+
users = self.client.keys(self.prefix + "*")
86+
for user in users:
87+
self.c.delete(user)
88+
89+
def freeze(self, username):
90+
data = self._get_user(username)
91+
if data is not None:
92+
self.client.set(self._key(username, True), json.dumps(data))
93+
94+
def thaw(self, username, action="thaw"):
95+
data = self.client.get(self.key(username, True))
96+
if data is not None:
97+
data = json.loads(data.decode())
98+
if action == "thaw":
99+
self.reset(username)
100+
self.set(username, data)
101+
self.c.delete(self.key(username, True))
102+
elif action == "discard":
103+
self.c.delete(self.key(username, True))
104+
elif action == "keep":
105+
self.reset(username)
106+
self.set(username, data)
107+
else:
108+
raise ValueError("unsupported thaw action")

contrib/redis/setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[metadata]
2+
description-file = README.md

contrib/redis/setup.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# rivescript-python setup.py
2+
3+
import rivescript_redis
4+
from setuptools import setup
5+
6+
setup(
7+
name = 'rivescript_redis',
8+
version = rivescript_redis.__version__,
9+
description = 'Redis driver for RiveScript',
10+
long_description = 'Stores user variables for RiveScript in a Redis cache',
11+
author = 'Noah Petherbridge',
12+
author_email = '[email protected]',
13+
url = 'https://github.com/aichaos/rivescript-python',
14+
license = 'MIT',
15+
py_modules = ['rivescript_redis'],
16+
keywords = ['rivescript'],
17+
classifiers = [
18+
'License :: OSI Approved :: MIT License',
19+
'Programming Language :: Python',
20+
'Programming Language :: Python :: 2',
21+
'Programming Language :: Python :: 3',
22+
'Development Status :: 3 - Alpha',
23+
'Intended Audience :: Developers',
24+
'Topic :: Scientific/Engineering :: Artificial Intelligence',
25+
],
26+
install_requires = [ 'setuptools', 'redis', 'rivescript' ],
27+
)
28+
29+
# vim:expandtab

eg/sessions/redis_bot.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,25 @@
33
from __future__ import unicode_literals, print_function, absolute_import
44
from six.moves import input
55
import sys
6+
7+
# Manipulate sys.path to be able to import rivescript from this local git
8+
# repository.
9+
import os
10+
sys.path.append(os.path.join(
11+
os.path.dirname(__file__),
12+
"..", "..",
13+
))
14+
sys.path.append(os.path.join(
15+
os.path.dirname(__file__),
16+
"..", "..",
17+
"contrib", "redis",
18+
))
19+
620
from rivescript import RiveScript
7-
from redis_storage import RedisSessionStorage
21+
from rivescript_redis import RedisSessionManager
822

923
bot = RiveScript(
10-
session_manager=RedisSessionStorage(),
24+
session_manager=RedisSessionManager(),
1125
)
1226
bot.load_directory("../brain")
1327
bot.sort_replies()

eg/sessions/redis_storage.py

Lines changed: 0 additions & 82 deletions
This file was deleted.

eg/sessions/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
redis
1+
rivescript-redis

rivescript/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
__docformat__ = 'plaintext'
2121

2222
__all__ = ['rivescript']
23-
__version__ = '1.14.4'
23+
__version__ = '1.14.5'
2424

2525
from .rivescript import RiveScript
2626
from .exceptions import (

rivescript/brain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def _getreply(self, user, msg, context='normal', step=0, ignore_object_errors=Tr
246246
if match:
247247
self.say("Found a match!")
248248
matched = trig[1]
249-
matchedTrigger = subtrig
249+
matchedTrigger = user_side["trigger"]
250250
foundMatch = True
251251

252252
# Get the stars!

0 commit comments

Comments
 (0)