morss/morss/caching.py

136 lines
3.7 KiB
Python
Raw Normal View History

2021-09-11 11:20:34 +00:00
# This file is part of morss
#
# Copyright (C) 2013-2020 pictuga <contact@pictuga.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import threading
2021-09-11 19:39:11 +00:00
import time
2021-09-11 11:20:34 +00:00
from collections import OrderedDict
CACHE_SIZE = int(os.getenv('CACHE_SIZE', 1000)) # max number of items in cache (default: 1k items)
CACHE_LIFESPAN = int(os.getenv('CACHE_LIFESPAN', 60)) # how often to auto-clear the cache (default: 1min)
class BaseCache:
""" Subclasses must behave like a dict """
def trim(self):
pass
def autotrim(self, delay=CACHE_LIFESPAN):
# trim the cache every so often
self.trim()
t = threading.Timer(delay, self.autotrim)
t.daemon = True
t.start()
def __contains__(self, url):
try:
self[url]
except KeyError:
return False
else:
return True
class CappedDict(OrderedDict, BaseCache):
def trim(self):
if CACHE_SIZE >= 0:
for i in range( max( len(self) - CACHE_SIZE , 0 )):
self.popitem(False)
def __setitem__(self, key, data):
# https://docs.python.org/2/library/collections.html#ordereddict-examples-and-recipes
if key in self:
del self[key]
OrderedDict.__setitem__(self, key, data)
2021-09-11 19:36:14 +00:00
try:
import redis # isort:skip
except ImportError:
pass
class RedisCacheHandler(BaseCache):
def __init__(self, host='localhost', port=6379, db=0, password=None):
self.r = redis.Redis(host=host, port=port, db=db, password=password)
def __getitem__(self, key):
return self.r.get(key)
def __setitem__(self, key, data):
self.r.set(key, data)
2021-11-07 17:15:20 +00:00
try:
import diskcache # isort:skip
except ImportError:
pass
class DiskCacheHandler(BaseCache):
def __init__(self, directory=None, **kwargs):
self.cache = diskcache.Cache(directory=directory, eviction_policy='least-frequently-used', **kwargs)
2021-11-08 20:57:43 +00:00
def __del__(self):
self.cache.close()
2021-11-07 17:15:20 +00:00
def trim(self):
self.cache.cull()
def __getitem__(self, key):
2021-11-08 20:57:43 +00:00
return self.cache[key]
2021-11-07 17:15:20 +00:00
def __setitem__(self, key, data):
self.cache.set(key, data)
2021-09-11 11:20:34 +00:00
if 'CACHE' in os.environ:
if os.environ['CACHE'] == 'mysql':
default_cache = MySQLCacheHandler(
user = os.getenv('MYSQL_USER'),
password = os.getenv('MYSQL_PWD'),
database = os.getenv('MYSQL_DB'),
host = os.getenv('MYSQL_HOST', 'localhost')
)
elif os.environ['CACHE'] == 'sqlite':
2021-11-07 17:14:18 +00:00
default_cache = SQLiteCache(
os.getenv('SQLITE_PATH', ':memory:')
)
2021-09-11 11:20:34 +00:00
2021-09-11 19:36:14 +00:00
elif os.environ['CACHE'] == 'redis':
default_cache = RedisCacheHandler(
host = os.getenv('REDIS_HOST', 'localhost'),
port = int(os.getenv('REDIS_PORT', 6379)),
db = int(os.getenv('REDIS_DB', 0)),
password = os.getenv('REDIS_PWD', None)
)
2021-11-07 17:15:20 +00:00
elif os.environ['CACHE'] == 'diskcache':
default_cache = DiskCacheHandler(
2021-12-23 11:02:24 +00:00
directory = os.getenv('DISKCACHE_DIR', '/tmp/morss-diskcache'),
2021-11-08 20:57:43 +00:00
size_limit = CACHE_SIZE # in Bytes
2021-11-07 17:15:20 +00:00
)
2021-09-11 11:20:34 +00:00
else:
default_cache = CappedDict()