Skip to content

Commit c62c077

Browse files
committed
Initial commit.
0 parents  commit c62c077

File tree

5 files changed

+145
-0
lines changed

5 files changed

+145
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.pyc
2+
/.idea
3+
/atlassian-ide-plugin.xml
4+
/.settings
5+
/config.py
6+
/.venv

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## SQL storage plugin for errbot
2+
3+
4+
### About
5+
[Errbot](http://errbot.io) is a python chatbot, this storage plugin allows you to use it with SQL databases as a persistent storage.
6+
By using [SQLAlchemy](sqlalchemy.org), it has the support for Firebird, Microsoft SQL Server, MySQL, Oracle, PostgreSQL, SQLite, Sybase, IBM DB2, Amazon Redshift, exasol, Sybase SQL Anywhere, MonetDB.
7+
8+
### Installation
9+
10+
1. Install the support for the database you want to use. See [SQLalchemy doc](http://docs.sqlalchemy.org/en/latest/dialects/)
11+
2. Then you need to add this section to your config.py, following this example:
12+
```python
13+
BOT_EXTRA_STORAGE_PLUGINS_DIR='/home/gbin/err-storage'
14+
STORAGE = 'SQL'
15+
STORAGE_CONFIG = {
16+
'data_url': 'postgresql://scott:tiger@localhost/test',
17+
}
18+
```
19+
20+
3. Start your bot in text mode: `errbot -T` to give it a shot.
21+
22+
If you want to migrate from the local storage to SQL, you should be able to backup your data (with STORAGE commented)
23+
then restore it back with STORAGE uncommented.

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
jsonpickle
2+
sqlalchemy

sql.plug

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[Core]
2+
Name = SQL
3+
Module = sql
4+
5+
[Documentation]
6+
Description = This is the errbot storage plugin for SQL. It is compatible with Firebird, Microsoft SQL Server, MySQL, Oracle, PostgreSQL, SQLite, Sybase, IBM DB2, Amazon Redshift, exasol, Sybase SQL Anywhere, MonetDB.

sql.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
2+
3+
import logging
4+
from jsonpickle import encode, decode
5+
from typing import Any
6+
from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey, create_engine, select
7+
from sqlalchemy.orm import mapper, sessionmaker
8+
from sqlalchemy.orm.exc import NoResultFound
9+
from errbot.storage.base import StorageBase, StoragePluginBase
10+
11+
log = logging.getLogger('errbot.storage.sql')
12+
13+
DATA_URL_ENTRY = 'data_url'
14+
15+
16+
class KV(object):
17+
"""This is a basic key/value. Pickling in JSON."""
18+
def __init__(self, key:str, value:Any):
19+
self._key = key
20+
self._value = encode(value)
21+
22+
@property
23+
def key(self) -> str:
24+
return self._key
25+
26+
@property
27+
def value(self) -> Any:
28+
return decode(self._value)
29+
30+
31+
class SQLStorage(StorageBase):
32+
def __init__(self, session, clazz):
33+
self.session = session
34+
self.clazz = clazz
35+
36+
def get(self, key: str) -> Any:
37+
try:
38+
return self.session.query(self.clazz).filter(self.clazz._key == key).one().value
39+
except NoResultFound:
40+
raise KeyError("%s doesn't exists." % key)
41+
42+
def remove(self, key: str):
43+
try:
44+
self.session.query(self.clazz).filter(self.clazz._key == key).delete()
45+
self.session.commit()
46+
except NoResultFound:
47+
raise KeyError("%s doesn't exists." % key)
48+
49+
def set(self, key: str, value: Any) -> None:
50+
self.session.add(self.clazz(key,value))
51+
self.session.commit()
52+
53+
def len(self):
54+
return self.session.query(self.clazz).count()
55+
56+
def keys(self):
57+
return (kv.key for kv in self.session.query(self.clazz).all())
58+
59+
def close(self) -> None:
60+
self.session.commit()
61+
62+
63+
class SQLPlugin(StoragePluginBase):
64+
def __init__(self, bot_config):
65+
super().__init__(bot_config)
66+
config = self._storage_config
67+
if DATA_URL_ENTRY not in config:
68+
raise Exception('You need to specify an connection URL for the database in data_url in you config.py for example:\n'
69+
'STORAGE_CONFIG={\n'
70+
'"data_url": "postgresql://scott:tiger@localhost/mydatabase/",\n'
71+
'}')
72+
73+
# Hack around the multithreading issue in memory only sqlite.
74+
# This mode is useful for testing.
75+
if config[DATA_URL_ENTRY] == 'sqlite://':
76+
from sqlalchemy.pool import StaticPool
77+
self._engine = create_engine('sqlite://',
78+
connect_args={'check_same_thread':False},
79+
poolclass=StaticPool,
80+
echo=bot_config.BOT_LOG_LEVEL == logging.DEBUG)
81+
else:
82+
self._engine = create_engine(config[DATA_URL_ENTRY], echo=bot_config.BOT_LOG_LEVEL == logging.DEBUG)
83+
self._metadata = MetaData()
84+
self._sessionmaker = sessionmaker()
85+
self._sessionmaker.configure(bind=self._engine)
86+
87+
88+
def open(self, namespace: str) -> StorageBase:
89+
90+
# Create a table with the given namespace
91+
table = Table(namespace, self._metadata,
92+
Column('key', String(), primary_key=True),
93+
Column('value', String()),
94+
)
95+
96+
class NewKV(KV):
97+
pass
98+
99+
mapper(NewKV, table, properties={
100+
'_key': table.c.key,
101+
'_value': table.c.value,
102+
})
103+
104+
# ensure that the table for this namespace exists
105+
self._metadata.create_all(self._engine)
106+
107+
# create an autonomous session for it.
108+
return SQLStorage(self._sessionmaker(), NewKV)

0 commit comments

Comments
 (0)