|
|
@ -4,42 +4,27 @@ from __future__ import absolute_import, unicode_literals
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.sql import select, func
|
|
|
|
|
|
|
|
|
|
|
|
from . import Base
|
|
|
|
from . import Base
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SQLite3Cache(Base):
|
|
|
|
class SQLite3Cache(Base):
|
|
|
|
"""Implements a shared cache using SQLite3. Works across multiple processes
|
|
|
|
"""Implements cache using SQLAlchemy Core.
|
|
|
|
and threads, concurrent writes are not supported.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JSON is used to serialize python primitives in a safe way.
|
|
|
|
JSON is used for safe serialization of python primitives.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
serialize = True
|
|
|
|
serialize = True
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, connection, threshold=1024, timeout=-1):
|
|
|
|
def __init__(self, db, threshold=1024, timeout=-1):
|
|
|
|
super(SQLite3Cache, self).__init__(threshold, timeout)
|
|
|
|
super(SQLite3Cache, self).__init__(threshold, timeout)
|
|
|
|
self.connection = connection
|
|
|
|
self.db = db
|
|
|
|
self.connection.execute(
|
|
|
|
|
|
|
|
'CREATE TABLE IF NOT EXISTS cache ('
|
|
|
|
def _get(self, ns, key):
|
|
|
|
' key TEXT PRIMARY KEY,'
|
|
|
|
rv = self.db.engine.execute(
|
|
|
|
' value BLOB,'
|
|
|
|
select([self.db.cache.c.value]).where(
|
|
|
|
' time FLOAT)')
|
|
|
|
self.db.cache.c.key == ns + b'-' + key)).fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
# drop trigger, in case threshold has changed
|
|
|
|
|
|
|
|
self.connection.execute('DROP TRIGGER IF EXISTS sweeper')
|
|
|
|
|
|
|
|
self.connection.execute([
|
|
|
|
|
|
|
|
'CREATE TRIGGER sweeper AFTER INSERT ON cache',
|
|
|
|
|
|
|
|
'BEGIN',
|
|
|
|
|
|
|
|
' DELETE FROM cache WHERE key NOT IN ('
|
|
|
|
|
|
|
|
' SELECT key FROM cache',
|
|
|
|
|
|
|
|
' ORDER BY time DESC LIMIT {0}'.format(threshold),
|
|
|
|
|
|
|
|
' );',
|
|
|
|
|
|
|
|
'END'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get(self, ns, key, default=None):
|
|
|
|
|
|
|
|
rv = self.connection.execute(
|
|
|
|
|
|
|
|
'SELECT value FROM cache WHERE key = ?',
|
|
|
|
|
|
|
|
(ns + b'-' + key, )).fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if rv is None:
|
|
|
|
if rv is None:
|
|
|
|
raise KeyError
|
|
|
|
raise KeyError
|
|
|
@ -47,11 +32,36 @@ class SQLite3Cache(Base):
|
|
|
|
return rv[0]
|
|
|
|
return rv[0]
|
|
|
|
|
|
|
|
|
|
|
|
def _set(self, ns, key, value):
|
|
|
|
def _set(self, ns, key, value):
|
|
|
|
with self.connection.transaction as con:
|
|
|
|
with self.db.transaction:
|
|
|
|
con.execute(
|
|
|
|
cnt = self.db.engine.execute(
|
|
|
|
'INSERT OR REPLACE INTO cache (key, value, time) VALUES (?, ?, ?)',
|
|
|
|
select([func.count(self.db.cache)])).fetchone()[0]
|
|
|
|
(ns + b'-' + key, value, time.time()))
|
|
|
|
|
|
|
|
|
|
|
|
if cnt + 1 > self.threshold:
|
|
|
|
|
|
|
|
self.db.engine.execute(
|
|
|
|
|
|
|
|
self.db.cache.delete().where(
|
|
|
|
|
|
|
|
self.db.cache.c.key.in_(select(
|
|
|
|
|
|
|
|
[self.db.cache.c.key])
|
|
|
|
|
|
|
|
.order_by(self.db.cache.c.time)
|
|
|
|
|
|
|
|
.limit(1))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
self._get(ns, key)
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
|
|
insert = True
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
insert = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if insert:
|
|
|
|
|
|
|
|
stmt = self.db.cache.insert().values(
|
|
|
|
|
|
|
|
key=ns + b'-' + key, value=value, time=time.time())
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
stmt = self.db.cache.update().values(
|
|
|
|
|
|
|
|
value=value, time=time.time()).where(
|
|
|
|
|
|
|
|
self.db.cache.c.key == ns + b'-' + key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.db.engine.execute(stmt)
|
|
|
|
|
|
|
|
|
|
|
|
def _delete(self, ns, key):
|
|
|
|
def _delete(self, ns, key):
|
|
|
|
with self.connection.transaction as con:
|
|
|
|
with self.db.transaction:
|
|
|
|
con.execute('DELETE FROM cache WHERE key = ?', (ns + b'-' + key, ))
|
|
|
|
self.db.engine.execute(
|
|
|
|
|
|
|
|
self.db.cache.delete(self.db.cache.c.key == ns + b'-' + key))
|
|
|
|