forked from platypush/platypush
- Support for Platypush main configuration db, where plugins and backends can store their data
- Support for permanent cross-process storage of session variables through SQLite db - Support for db.select with table+filter instead of raw SQL query
This commit is contained in:
parent
7114d8bcaa
commit
2fda066e39
3 changed files with 97 additions and 17 deletions
|
@ -67,6 +67,11 @@ class Config(object):
|
||||||
self._config['workdir'] = self._workdir_location
|
self._config['workdir'] = self._workdir_location
|
||||||
os.makedirs(self._config['workdir'], exist_ok=True)
|
os.makedirs(self._config['workdir'], exist_ok=True)
|
||||||
|
|
||||||
|
self._config['db'] = self._config.get('main_db', {
|
||||||
|
'engine': 'sqlite:///' + os.path.join(
|
||||||
|
os.environ['HOME'], '.local', 'share', 'platypush', 'main.db')
|
||||||
|
})
|
||||||
|
|
||||||
logging_config = {
|
logging_config = {
|
||||||
'level': logging.INFO,
|
'level': logging.INFO,
|
||||||
'stream': sys.stdout,
|
'stream': sys.stdout,
|
||||||
|
|
|
@ -36,6 +36,14 @@ class DbPlugin(Plugin):
|
||||||
else:
|
else:
|
||||||
return self.engine
|
return self.engine
|
||||||
|
|
||||||
|
def _build_condition(self, table, column, value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = "'{}'".format(value)
|
||||||
|
elif not isinstance(value, int) and not isinstance(value, 'float'):
|
||||||
|
value = "'{}'".format(str(value))
|
||||||
|
|
||||||
|
return eval('table.c.{}=={}'.format(column, value))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def execute(self, statement, engine=None, *args, **kwargs):
|
def execute(self, statement, engine=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -64,19 +72,23 @@ class DbPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def select(self, query, engine=None, *args, **kwargs):
|
def select(self, query=None, table=None, filter=None, engine=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns rows (as a list of hashes) given a query.
|
Returns rows (as a list of hashes) given a query.
|
||||||
|
|
||||||
:param query: SQL to be executed
|
:param query: SQL to be executed
|
||||||
:type query: str
|
:type query: str
|
||||||
|
:param filter: Query WHERE filter expressed as a dictionary. This approach is preferred over specifying raw SQL in ``query`` as the latter approach may be prone to SQL injection, unless you need to build some complex SQL logic.
|
||||||
|
:type filter: dict
|
||||||
|
:param table: If you specified a filter instead of a raw query, you'll have to specify the target table
|
||||||
|
:type table: str
|
||||||
:param engine: Engine to be used (default: default class engine)
|
:param engine: Engine to be used (default: default class engine)
|
||||||
:type engine: str
|
:type engine: str
|
||||||
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
:param args: Extra arguments that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
:param kwargs: Extra kwargs that will be passed to ``sqlalchemy.create_engine`` (see http://docs.sqlalchemy.org/en/latest/core/engines.html)
|
||||||
:returns: List of hashes representing the result rows.
|
:returns: List of hashes representing the result rows.
|
||||||
|
|
||||||
Example:
|
Examples:
|
||||||
|
|
||||||
Request::
|
Request::
|
||||||
|
|
||||||
|
@ -90,25 +102,46 @@ class DbPlugin(Plugin):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"target": "your_host",
|
||||||
|
"action": "db.select",
|
||||||
|
"args": {
|
||||||
|
"engine": "sqlite:///:memory:",
|
||||||
|
"table": "table",
|
||||||
|
"filter": {"id": 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Response::
|
Response::
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": foo
|
"name": foo
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": bar
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
engine = self._get_engine(engine, *args, **kwargs)
|
db = self._get_engine(engine, *args, **kwargs)
|
||||||
|
|
||||||
with engine.connect() as connection:
|
if table:
|
||||||
|
metadata = MetaData()
|
||||||
|
table = Table(table, metadata, autoload=True, autoload_with=db)
|
||||||
|
query = table.select()
|
||||||
|
|
||||||
|
if filter:
|
||||||
|
for (k,v) in filter.items():
|
||||||
|
query = query.where(self._build_condition(table, k, v))
|
||||||
|
|
||||||
|
if query is None:
|
||||||
|
raise RuntimeError('You need to specify either "query", or "table" and "filter"')
|
||||||
|
|
||||||
|
with db.connect() as connection:
|
||||||
result = connection.execute(query)
|
result = connection.execute(query)
|
||||||
|
|
||||||
columns = result.keys()
|
columns = result.keys()
|
||||||
rows = [
|
rows = [
|
||||||
{ columns[i]: row[i] for i in range(0, len(columns)) }
|
{ columns[i]: row[i] for i in range(0, len(columns)) }
|
||||||
|
@ -235,7 +268,7 @@ class DbPlugin(Plugin):
|
||||||
update = table.update()
|
update = table.update()
|
||||||
|
|
||||||
for (k,v) in key.items():
|
for (k,v) in key.items():
|
||||||
update = update.where(eval('table.c.{}=={}'.format(k, v)))
|
update = update.where(self._build_condition(table, k, v))
|
||||||
|
|
||||||
update = update.values(**values)
|
update = update.values(**values)
|
||||||
engine.execute(update)
|
engine.execute(update)
|
||||||
|
@ -282,7 +315,7 @@ class DbPlugin(Plugin):
|
||||||
delete = table.delete()
|
delete = table.delete()
|
||||||
|
|
||||||
for (k,v) in record.items():
|
for (k,v) in record.items():
|
||||||
delete = delete.where(eval('table.c.{}=={}'.format(k, v)))
|
delete = delete.where(self._build_condition(table, k, v))
|
||||||
|
|
||||||
engine.execute(delete)
|
engine.execute(delete)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from platypush.config import Config
|
||||||
|
from platypush.context import get_plugin
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,9 +9,27 @@ class VariablePlugin(Plugin):
|
||||||
accessed across your tasks.
|
accessed across your tasks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_variable_table_name = 'variable'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._variables = {}
|
self.db_plugin = get_plugin('db')
|
||||||
|
|
||||||
|
db = Config.get('db')
|
||||||
|
self.db_config = {
|
||||||
|
'engine': db.get('engine'),
|
||||||
|
'args': db.get('args', []),
|
||||||
|
'kwargs': db.get('kwargs', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
self._create_tables()
|
||||||
|
# self._variables = {}
|
||||||
|
|
||||||
|
def _create_tables(self):
|
||||||
|
self.db_plugin.execute("""CREATE TABLE IF NOT EXISTS {}(
|
||||||
|
name varchar(255) not null primary key,
|
||||||
|
value text
|
||||||
|
)""".format(self._variable_table_name))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get(self, name, default_value=None):
|
def get(self, name, default_value=None):
|
||||||
|
@ -24,7 +44,13 @@ class VariablePlugin(Plugin):
|
||||||
:returns: A map in the format ``{"<name>":"<value>"}``
|
:returns: A map in the format ``{"<name>":"<value>"}``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return {name: self._variables.get(name, default_value)}
|
rows = self.db_plugin.select(table=self._variable_table_name,
|
||||||
|
filter={'name': name},
|
||||||
|
engine=self.db_config['engine'],
|
||||||
|
*self.db_config['args'],
|
||||||
|
**self.db_config['kwargs']).output
|
||||||
|
|
||||||
|
return {name: rows[0]['value'] if rows else None}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set(self, **kwargs):
|
def set(self, **kwargs):
|
||||||
|
@ -34,10 +60,19 @@ class VariablePlugin(Plugin):
|
||||||
:param kwargs: Key-value list of variables to set (e.g. ``foo='bar', answer=42``)
|
:param kwargs: Key-value list of variables to set (e.g. ``foo='bar', answer=42``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for (name, value) in kwargs.items():
|
records = [ { 'name': k, 'value': v }
|
||||||
self._variables[name] = value
|
for (k,v) in kwargs.items() ]
|
||||||
|
|
||||||
|
self.db_plugin.insert(table=self._variable_table_name,
|
||||||
|
records=records, key_columns=['name'],
|
||||||
|
engine=self.db_config['engine'],
|
||||||
|
on_duplicate_update=True,
|
||||||
|
*self.db_config['args'],
|
||||||
|
**self.db_config['kwargs'])
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def unset(self, name):
|
def unset(self, name):
|
||||||
"""
|
"""
|
||||||
|
@ -46,8 +81,15 @@ class VariablePlugin(Plugin):
|
||||||
:param name: Name of the variable to remove
|
:param name: Name of the variable to remove
|
||||||
:type name: str
|
:type name: str
|
||||||
"""
|
"""
|
||||||
if name in self._variables:
|
|
||||||
del self._variables[name]
|
records = [ { 'name': name } ]
|
||||||
|
|
||||||
|
self.db_plugin.delete(table=self._variable_table_name,
|
||||||
|
records=records, engine=self.db_config['engine'],
|
||||||
|
*self.db_config['args'],
|
||||||
|
**self.db_config['kwargs'])
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue