Source code for score.kvcache.backend

# Copyright © 2015 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.

from abc import ABCMeta, abstractmethod
from score.kvcache import NotFound
import pickle
import time
import sqlite3


[docs]class Backend(metaclass=ABCMeta):
[docs] @abstractmethod def store(self, container, key, value, expire=None): """ Stores a value for given container name and key with given expire. """ pass
[docs] @abstractmethod def retrieve(self, container, key): """ Retrieves a value for given container and key and raises an :class:`NotFound <score.kvcache.NotFound>` if the value is invalid. """ return None
[docs] @abstractmethod def invalidate(self, container, key): """ Invalidates a value for given container and key. """ return None
[docs]class VariableCache(Backend): """ This backend implementation caches values in a variable. Note, that the expire is restricted to the process's lifetime. """ def __init__(self): self.cache = {} def retrieve(self, container, key): try: return self.cache[container][key] except KeyError: raise NotFound() def store(self, container, key, value, expire=None): if container not in self.cache: self.cache[container] = {} self.cache[container][key] = value def invalidate(self, container, key): del self.cache[container][key]
[docs]class FileCache(Backend): """ This backend implementation caches values in a sqlite3_ database with given path. """ def __init__(self, path): self.path = path def retrieve(self, container, key): try: cursor = sqlite3.connect(self.path).cursor() cursor.execute('SELECT value ' 'FROM kvcache WHERE container = ? AND key = ? AND ' 'expire > ?', (container, key, time.time())) result = cursor.fetchone() except sqlite3.OperationalError: raise NotFound() if not result: raise NotFound() return pickle.loads(result[0]) def store(self, container, key, value, expire=None): if expire is None: expire = 10**6 conn = sqlite3.connect(self.path) cursor = conn.cursor() try: cursor.execute('INSERT OR REPLACE INTO kvcache (container, key, ' 'value, expire) VALUES (?, ?, ?, ?)', (container, key, pickle.dumps(value), time.time() + expire)) conn.commit() except sqlite3.OperationalError: cursor.execute('CREATE TABLE IF NOT EXISTS kvcache (' 'container VARCHAR(255), ' 'key VARCHAR(255), ' 'value BLOB, ' 'expire INTEGER, ' 'PRIMARY KEY (container, key))') self.store(container, key, value, expire) def invalidate(self, container, key): self.store(container, key, None, time.time())
[docs]class SQLAlchemyCache(Backend): """ This backend implementation caches values in a database supported by sqlalchemy_. .. _sqlalchemy: http://docs.sqlalchemy.org/en/latest/ """ def __init__(self, **confdict): from sqlalchemy import Column, String, Integer, Binary from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from score.db import engine_from_config engine = engine_from_config(confdict) self.Session = sessionmaker(bind=engine) Base = declarative_base() Base.metadata.bind = engine self.kvcache_cls = type('KvCache', (Base,), { '__tablename__': 'kvcache', 'container': Column(String(256), nullable=False, primary_key=True), 'key': Column(String(256), nullable=False, primary_key=True), 'value': Column(Binary), 'expire': Column(Integer, nullable=False), }) Base.metadata.create_all() def store(self, container, key, value, expire=None): try: key = str(key) if expire is None: expire = 10**6 session = self.Session() cls = self.kvcache_cls entry = session.query(cls).\ filter(cls.container == container).\ filter(cls.key == key).\ first() if not entry: entry = cls(container=container, key=key, value=pickle.dumps(value), expire=time.time() + expire) session.add(entry) return entry.value = pickle.dumps(value) entry.expire = time.time() + expire except: session.rollback() session = None raise finally: if session: session.commit() def retrieve(self, container, key): try: key = str(key) session = self.Session() cls = self.kvcache_cls entry = session.query(cls).\ filter(cls.container == container).\ filter(cls.key == key).\ filter(cls.expire > time.time()).\ first() if not entry: raise NotFound() return pickle.loads(entry.value) except: session.rollback() session = None raise finally: if session: session.commit() def invalidate(self, container, key): self.store(container, key, None)