from time import sleep from expiring_dict import ExpiringDict cache = ExpiringDict() # No TTL set, keys set via [] will not expire cache["abc"] = "persistent" cache.ttl("123", "expires", 1) # This will expire after 1 second print("abc" in cache) print("123" in cache) sleep(1.1) print("abc" in cache) print("123" not in cache) cache2 = ExpiringDict(1) cache2["abc"] = "expires" cache2["123"] = "also expires" print("abc" in cache2) print("123" in cache2) sleep(1.1) print("abc" not in cache2) print("123" not in cache2)
def test_set_ttl(): d = ExpiringDict(interval=0.005) d.ttl("key", "expire", 0.01) assert len(d) == 1 sleep(0.02) assert len(d) == 0
def test_set_ttl(): d = ExpiringDict() d.ttl("key", "expire", 1) assert len(d) == 1 sleep(1.1) assert len(d) == 0
class Server(object): def __init__(self, host=None, port=None, max_clients=None): if not host: host = os.getenv('HOST', '0.0.0.0') if not port: port = os.getenv('PORT', 31337) if not max_clients: max_clients = os.getenv('MAX_CLIENTS', 15) self._pool = Pool(int(max_clients)) self._server = StreamServer((host, int(port)), self.connection_handler, spawn=self._pool) self._protocol = DataHandler() self._kv = ExpiringDict() self._commands = self.get_commands() def get_commands(self): return { 'GET': self.get, 'SET': self.set, 'DEL': self.delete, 'KEYS': self.keys, 'FLUSHDB': self.flush, 'EXPIRE': self.expire, 'HGET': self.hget, 'HSET': self.hset, 'LSET': self.lset, 'RPUSH': self.rpush, 'LPUSH': self.lpush, 'LRANGE': self.lrange, 'LINDEX': self.lindex } def connection_handler(self, conn, address): logger.info('Connection received: %s:%s' % address) socket_file = conn.makefile('rwb') while True: try: data = self._protocol.handle_request(socket_file) except Disconnect: logger.info('Client went away: %s:%s' % address) break try: resp = self.get_response(data) except CommandError as exc: logger.exception('Command error') resp = Error(exc.args[0]) self._protocol.write_response(socket_file, resp) def run(self): self._server.serve_forever() def get_response(self, data): if not isinstance(data, list): try: data = data.split() except: raise CommandError('Request must be list or simple string.') if not data: raise CommandError('Missing command') command = data[0].upper() if command not in self._commands: raise CommandError('Unrecognized command: %s' % command) else: logger.debug('Received %s', command) try: response = self._commands[command](*data[1:]) except TypeError: raise CommandError( f'ERR wrong number of arguments for {command.lower()} command') return response def get(self, key): return self._kv.get(key) def set(self, key, value): self._kv[key] = value return '1' def delete(self, key): if key in self._kv: del self._kv[key] return 1 return 0 def keys(self, pattern): r = [] if pattern == '*': pattern = '\w*' for k in self._kv.keys(): if re.match(pattern, k): r.append(k) return r def flush(self): kvlen = len(self._kv) self._kv.clear() return str(kvlen) def expire(self, key, ttl): value = self._kv.get(key, None) if value: del self._kv[key] self._kv.ttl(key, value, float(ttl)) return 1 return 0 def hset(self, key, field, value): self._kv.setdefault(key, {}) self._kv[key][field] = value return 1 def hget(self, k, field): if self._kv.get(k): return self._kv[k].get(field) def lset(self, key, index, value): self._kv.setdefault(key, []) self._kv[key][int(index)] = value return 'OK' def rpush(self, key, *value): self._kv.setdefault(key, []) for v in value: self._kv[key].append(v) return len(self._kv[key]) def lpush(self, key, *value): self._kv.setdefault(key, []) for v in value: self._kv[key].insert(0, v) return len(self._kv[key]) def lrange(self, key, start, end): if isinstance(self._kv.get(key), list): if end == '-1': return self._kv[key][int(start):] return self._kv[key][int(start):int(end) + 1] return None def lindex(self, key, index): if isinstance(self._kv.get(key), list): try: return self._kv[key][int(index)] except IndexError: return None return None