PNG  IHDR;IDATxܻn0K )(pA 7LeG{ §㻢|ذaÆ 6lذaÆ 6lذaÆ 6lom$^yذag5bÆ 6lذaÆ 6lذa{ 6lذaÆ `}HFkm,mӪôô! x|'ܢ˟;E:9&ᶒ}{v]n&6 h_tڠ͵-ҫZ;Z$.Pkž)!o>}leQfJTu іچ\X=8Rن4`Vwl>nG^is"ms$ui?wbs[m6K4O.4%/bC%t Mז -lG6mrz2s%9s@-k9=)kB5\+͂Zsٲ Rn~GRC wIcIn7jJhۛNCS|j08yiHKֶۛkɈ+;SzL/F*\Ԕ#"5m2[S=gnaPeғL lذaÆ 6l^ḵaÆ 6lذaÆ 6lذa; _ذaÆ 6lذaÆ 6lذaÆ RIENDB` """Redis database engine.""" import time import logging import datetime import functools try: import redis _has_redis = True except ImportError: redis = None _has_redis = False from pyzor.engines.common import * VERSION = "1" NAMESPACE = "pyzord.digest_v%s" % VERSION def encode_date(date): """Convert the date to Unix Timestamp""" if date is None: return 0 return int(time.mktime(date.timetuple())) def decode_date(stamp): """Return a datetime object from a Unix Timestamp.""" stamp = int(stamp) if stamp == 0: return None return datetime.datetime.fromtimestamp(stamp) def safe_call(f): """Decorator that wraps a method for handling database operations.""" def wrapped_f(self, *args, **kwargs): # This only logs the error and raise the usual Error for consistency, # the redis library takes care of reconnecting and everything else. try: return f(self, *args, **kwargs) except redis.exceptions.RedisError as e: self.log.error("Redis error while calling %s: %s", f.__name__, e) raise DatabaseError("Database temporarily unavailable.") return wrapped_f class RedisDBHandle(BaseEngine): absolute_source = False handles_one_step = True log = logging.getLogger("pyzord") def __init__(self, fn, mode, max_age=None): self.max_age = max_age # The 'fn' is host,port,password,db. We ignore mode. # We store the authentication details so that we can reconnect if # necessary. self._dsn = fn fn = fn.split(",") self.host = fn[0] or "localhost" self.port = fn[1] or "6379" self.passwd = fn[2] or None self.db_name = fn[3] or "0" self.db = self._get_new_connection() self._check_version() @staticmethod def _encode_record(r): return {"r_count": r.r_count, "r_entered": encode_date(r.r_entered), "r_updated": encode_date(r.r_updated), "wl_count": r.wl_count, "wl_entered": encode_date(r.wl_entered), "wl_updated": encode_date(r.wl_updated) } @staticmethod def _decode_record(r): if not r: return Record() return Record(r_count=int(r.get(b"r_count", 0)), r_entered=decode_date(r.get(b"r_entered", 0)), r_updated=decode_date(r.get(b"r_updated", 0)), wl_count=int(r.get(b"wl_count", 0)), wl_entered=decode_date(r.get(b"wl_entered", 0)), wl_updated=decode_date(r.get(b"wl_updated", 0))) def __iter__(self): for key in self.db.keys(self._real_key("*")): yield key.rsplit(".", 1)[-1] def _iteritems(self): for key in self: try: yield key, self[key] except Exception as ex: self.log.warning("Invalid record %s: %s", key, ex) def iteritems(self): return self._iteritems() def items(self): return list(self._iteritems()) @staticmethod def _real_key(key): return "%s.%s" % (NAMESPACE, key) @safe_call def _get_new_connection(self): if "/" in self.host: return redis.StrictRedis(unix_socket_path=self.host, db=int(self.db_name), password=self.passwd) return redis.StrictRedis(host=self.host, port=int(self.port), db=int(self.db_name), password=self.passwd) @safe_call def __getitem__(self, key): return self._decode_record(self.db.hgetall(self._real_key(key))) @safe_call def __setitem__(self, key, value): real_key = self._real_key(key) self.db.hmset(real_key, self._encode_record(value)) if self.max_age is not None: self.db.expire(real_key, self.max_age) @safe_call def __delitem__(self, key): self.db.delete(self._real_key(key)) @safe_call def report(self, keys): now = int(time.time()) for key in keys: real_key = self._real_key(key) self.db.hincrby(real_key, "r_count") self.db.hsetnx(real_key, "r_entered", now) self.db.hset(real_key, "r_updated", now) if self.max_age: self.db.expire(real_key, self.max_age) @safe_call def whitelist(self, keys): now = int(time.time()) for key in keys: real_key = self._real_key(key) self.db.hincrby(real_key, "wl_count") self.db.hsetnx(real_key, "wl_entered", now) self.db.hset(real_key, "wl_updated", now) if self.max_age: self.db.expire(real_key, self.max_age) @classmethod def get_prefork_connections(cls, fn, mode, max_age=None): """Yields a number of database connections suitable for a Pyzor pre-fork server. """ while True: yield functools.partial(cls, fn, mode, max_age=max_age) def _check_version(self): """Check if there are deprecated records and warn the user.""" old_keys = len(self.db.keys("pyzord.digest.*")) if old_keys: cmd = ("pyzor-migrate --delete --se=redis_v0 --sd=%s " "--de=redis --dd=%s" % (self._dsn, self._dsn)) self.log.critical("You have %s records in the deprecated version " "of the redis engine.", old_keys) self.log.critical("Please migrate the records with: %r", cmd) class ThreadedRedisDBHandle(RedisDBHandle): def __init__(self, fn, mode, max_age=None, bound=None): RedisDBHandle.__init__(self, fn, mode, max_age=max_age) if not _has_redis: handle = DBHandle(single_threaded=None, multi_threaded=None, multi_processing=None, prefork=None) else: handle = DBHandle(single_threaded=RedisDBHandle, multi_threaded=ThreadedRedisDBHandle, multi_processing=None, prefork=RedisDBHandle)