from datetime import datetime, timedelta
import json
class KeyedSqliteThing(object):
[docs]
_id_key = 'id'
_db_required_props = []
_db_optional_props = []
def __init__(self, connector, id = None, auto_props = []):
self._id = id
self._connector = connector
self._conn = None
self._db_auto_props = auto_props
self._props = {}
self._in_db = False
if id is not None:
self._load()
def __repr__(self):
tname = type(self).__name__
return "{0}({1}, {2})".format(tname, self._id, self._props)
def __getattr__(self, name):
if name not in self._db_props:
raise AttributeError("No property '%s'" % (name))
return self._props.get(name, None)
def __setattr__(self, name, value):
if name in self._db_props:
self._props[name] = value
else:
super(KeyedSqliteThing, self).__setattr__(name, value)
def _get_connection(self):
if self._conn is None:
self._conn = self._connector()
return self._conn
def _exec(self, statement, arguments):
conn = self._get_connection()
cur = conn.cursor()
cur.execute(statement, arguments)
conn.commit()
return cur.lastrowid
def _fetchone(self, statement, arguments):
conn = self._get_connection()
cur = conn.cursor()
cur.execute(statement, arguments)
return cur.fetchone()
def _where_id(self):
return ' WHERE {0}=?'.format(self._id_key)
def _from_table_where_id(self):
return ' FROM {0}{1}'.format(self._db_table, self._where_id())
def _load(self):
props = self._db_props + self._db_auto_props
statement = 'SELECT ' + ', '.join(props) + self._from_table_where_id()
row = self._fetchone(statement, (self._id,))
if not row is None:
for i in range(len(props)):
if row[i] is not None:
self._props[props[i]] = row[i]
self._in_db = True
@property
def _db_props(self):
return self._db_required_props + self._db_optional_props
def _missing_props(self):
return [name for name in self._db_required_props if name not in self._props]
@property
def id(self):
return self._id
@property
def in_db(self):
return self._in_db
def delete(self):
[docs] if not self.in_db:
raise Exception( "Cannot remove %s '%s' - not in database!" % (type(self), self.self._id) )
self._exec("DELETE" + self._from_table_where_id(), (self._id,))
self._in_db = False
def save(self):
[docs] missing = self._missing_props()
if len(missing) > 0:
missing_str = ', '.join(missing)
raise Exception( "Cannot save %s '%s' - missing settings: '%s'." % (type(self), self._id, missing_str) )
props = self._props.keys()
values = list(self._props.values())
if self.in_db:
prep_statement = "UPDATE " + self._db_table + " SET " + '=?, '.join(props) + "=? " + self._where_id()
self._exec(prep_statement, values + [self._id])
else:
prep_statement = "INSERT INTO {0} ({1}, {2}) VALUES (?{3})".format(
self._db_table,
self._id_key,
', '.join(props),
',?' * len(props)
)
lastid = self._exec(prep_statement, [self._id] + values)
if self._id is None:
self._id = lastid
self._in_db = True
class AgedKeyedSqliteThing(KeyedSqliteThing):
[docs] def __init__(self, connector, birth_time_prop, id):
super(AgedKeyedSqliteThing, self).__init__(connector, id, [birth_time_prop])
self._birth_time_prop = birth_time_prop
def _get_time_property(self, name):
if name not in self._props:
return None
time_str = self._props[name]
dt = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
return dt
def _set_time_property(self, name, dt):
time_str = dt.strftime('%Y-%m-%d %H:%M:%S')
self.__setattr__(name, time_str)
@property
def age(self):
if not self.in_db:
return timedelta()
else:
birth = self._get_time_property(self._birth_time_prop)
age = datetime.utcnow() - birth
return age
class PendingSend(AgedKeyedSqliteThing):
[docs] @classmethod
def Unsent(cls, connector, max_retry = 5, max_results = 50):
[docs] conn = connector()
cur = conn.cursor()
args = (max_retry, max_results)
cur.execute("SELECT id FROM outbox WHERE retry_count<? AND sent_time is null ORDER BY request_time ASC LIMIT ?", args)
rows = cur.fetchall()
for row in rows:
id = row[0]
yield PendingSend(connector, id)
_db_table = 'outbox'
_db_required_props = ['toaddr', 'template_name', 'template_vars_json']
_db_optional_props = ['last_error', 'retry_count', 'sent_time']
def __init__(self, connector, id = None):
super(PendingSend, self).__init__(connector, 'request_time', id)
@property
def retry_count(self):
return self._props.get('retry_count', 0)
@property
def template_vars(self):
raw = self._props.get('template_vars_json', None)
if raw is not None:
vars = json.loads(raw)
return vars
return None
@template_vars.setter
def template_vars(self, value):
str = json.dumps(value)
self._props['template_vars_json'] = str
@property
def is_sent(self):
return self.sent_time is not None
@property
def sent_time(self):
return self._get_time_property('sent_time')
def mark_sent(self):
[docs] self._set_time_property('sent_time', datetime.now())
def retried(self):