def _incr_decr(self, command, key, value, default, time): """ Function which increments and decrements. :param key: Key's name :type key: six.string_types :param value: Number to be (de|in)cremented :type value: int :param default: Default value if key does not exist. :type default: int :param time: Time in seconds to expire key. :type time: int :return: Actual value of the key on server :rtype: int """ time = time if time >= 0 else self.MAXIMUM_EXPIRE_TIME self._send(struct.pack(self.HEADER_STRUCT + self.COMMANDS[command]['struct'] % len(key), self.MAGIC['request'], self.COMMANDS[command]['command'], len(key), 20, 0, 0, len(key) + 20, 0, 0, value, default, time, str_to_bytes(key))) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status not in (self.STATUS['success'], self.STATUS['server_disconnected']): raise MemcachedException('Code: %d Message: %s' % (status, extra_content), status) if status == self.STATUS['server_disconnected']: return 0 return struct.unpack('!Q', extra_content)[0]
def delete(self, key, cas=0): """ Delete a key/value from server. If key existed and was deleted, return True. :param key: Key's name to be deleted :type key: six.string_types :param cas: If set, only delete the key if its CAS value matches. :type cas: int :return: True in case o success and False in case of failure. :rtype: bool """ logger.debug('Deleting key %s', key) self._send(struct.pack(self.HEADER_STRUCT + self.COMMANDS['delete']['struct'] % len(key), self.MAGIC['request'], self.COMMANDS['delete']['command'], len(key), 0, 0, 0, len(key), 0, cas, str_to_bytes(key))) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['server_disconnected']: return False if status != self.STATUS['success'] and status not in (self.STATUS['key_not_found'], self.STATUS['key_exists']): raise MemcachedException('Code: %d message: %s' % (status, extra_content), status) logger.debug('Key deleted %s', key) return status != self.STATUS['key_exists']
def flush_all(self, time): """ Send a command to server flush|delete all keys. :param time: Time to wait until flush in seconds. :type time: int :return: True in case of success, False in case of failure :rtype: bool """ logger.info('Flushing memcached') self._send( struct.pack(self.HEADER_STRUCT + self.COMMANDS['flush']['struct'], self.MAGIC['request'], self.COMMANDS['flush']['command'], 0, 4, 0, 0, 4, 0, 0, time)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status not in (self.STATUS['success'], self.STATUS['server_disconnected']): raise MemcachedException( 'Code: %d message: %s' % (status, extra_content), status) logger.debug('Memcached flushed') return True
def delete(self, key): """ Delete a key/value from server. If key does not exist, it returns True. :param key: Key's name to be deleted :type key: basestring :return: True in case o success and False in case of failure. :rtype: bool """ logger.info('Deletting key %s' % key) self.connection.send( struct.pack( self.HEADER_STRUCT + self.COMMANDS['delete']['struct'] % len(key), self.MAGIC['request'], self.COMMANDS['delete']['command'], len(key), 0, 0, 0, len(key), 0, 0, key)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status != self.STATUS['success'] and status != self.STATUS[ 'key_not_found']: raise MemcachedException('Code: %d message: %s' % (status, extra_content)) logger.debug('Key deleted %s' % key) return True
def get(self, key): """ Get a key from server. :param key: Key's name :type key: basestring :return: Returns a key data from server. :rtype: object """ logger.info('Getting key %s' % key) self.connection.send( struct.pack( self.HEADER_STRUCT + self.COMMANDS['get']['struct'] % (len(key)), self.MAGIC['request'], self.COMMANDS['get']['command'], len(key), 0, 0, 0, len(key), 0, 0, key)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() logger.debug('Value Length: %d. Body length: %d. Data type: %d' % (extlen, bodylen, datatype)) if status != self.STATUS['success']: if status == self.STATUS['key_not_found']: logger.debug('Key not found. Message: %s' % extra_content) return None raise MemcachedException('Code: %d Message: %s' % (status, extra_content)) flags, value = struct.unpack('!L%ds' % (bodylen - 4, ), extra_content) return self.deserialize(value, flags)
def _send_authentication(self): if not self._username or not self._password: return False logger.debug('Authenticating as %s', self._username) self._send( struct.pack(self.HEADER_STRUCT, self.MAGIC['request'], self.COMMANDS['auth_negotiation']['command'], 0, 0, 0, 0, 0, 0, 0)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['server_disconnected']: return False if status == self.STATUS['unknown_command']: logger.debug('Server does not requires authentication.') self.authenticated = True return True methods = extra_content if b'PLAIN' not in methods: raise AuthenticationNotSupported( 'This module only supports ' 'PLAIN auth for now.', status) method = b'PLAIN' auth = '\x00%s\x00%s' % (self._username, self._password) if isinstance(auth, text_type): auth = auth.encode() self._send( struct.pack( self.HEADER_STRUCT + self.COMMANDS['auth_request']['struct'] % (len(method), len(auth)), self.MAGIC['request'], self.COMMANDS['auth_request']['command'], len(method), 0, 0, 0, len(method) + len(auth), 0, 0, method, auth)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['server_disconnected']: return False if status == self.STATUS['auth_error']: raise InvalidCredentials("Incorrect username or password", status) if status != self.STATUS['success']: raise MemcachedException( 'Code: %d Message: %s' % (status, extra_content), status) logger.debug('Auth OK. Code: %d Message: %s', status, extra_content) self.authenticated = True return True
def _set_add_replace(self, command, key, value, time, cas=0, compress_level=-1): """ Function to set/add/replace commands. :param key: Key's name :type key: six.string_types :param value: A value to be stored on server. :type value: object :param time: Time in seconds that your key will expire. :type time: int :param cas: The CAS value that must be matched for this operation to complete, or 0 for no CAS. :type cas: int :param compress_level: How much to compress. 0 = no compression, 1 = fastest, 9 = slowest but best, -1 = default compression level. :type compress_level: int :return: True in case of success and False in case of failure :rtype: bool """ time = time if time >= 0 else self.MAXIMUM_EXPIRE_TIME logger.debug('Setting/adding/replacing key %s.', key) flags, value = self.serialize(value, compress_level=compress_level) logger.debug('Value bytes %s.', len(value)) if isinstance(value, text_type): value = value.encode('utf8') keybytes = str_to_bytes(key) self._send( struct.pack( self.HEADER_STRUCT + self.COMMANDS[command]['struct'] % (len(keybytes), len(value)), self.MAGIC['request'], self.COMMANDS[command]['command'], len(keybytes), 8, 0, 0, len(keybytes) + len(value) + 8, 0, cas, flags, time, keybytes, value)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status != self.STATUS['success']: if status == self.STATUS['key_exists']: return False elif status == self.STATUS['key_not_found']: return False elif status == self.STATUS['server_disconnected']: return False raise MemcachedException( 'Code: %d Message: %s' % (status, extra_content), status) return True
def get_multi(self, keys): """ Get multiple keys from server. Since keys are converted to b'' when six.PY3 the keys need to be decoded back into string . e.g key='test' is read as b'test' and then decoded back to 'test' This encode/decode does not work when key is already a six.binary_type hence this function remembers which keys were originally sent as str so that it only decoded those keys back to string which were sent as string :param keys: A list of keys to from server. :type keys: Collection :return: A dict with all requested keys. :rtype: dict """ # pipeline N-1 getkq requests, followed by a regular getk to uncork the # server n = len(keys) if n == 0: return {} msg = b'' for i, key in enumerate(keys): keybytes = str_to_bytes(key) command = self.COMMANDS['getk' if i == n - 1 else 'getkq'] msg += struct.pack( self.HEADER_STRUCT + command['struct'] % (len(keybytes), ), self.MAGIC['request'], command['command'], len(keybytes), 0, 0, 0, len(keybytes), 0, 0, keybytes) self._send(msg) d = {} opcode = -1 while opcode != self.COMMANDS['getk']['command']: (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['success']: flags, key, value = struct.unpack( '!L%ds%ds' % (keylen, bodylen - keylen - 4), extra_content) d[key] = self.deserialize(value, flags), cas elif status == self.STATUS['server_disconnected']: break elif status != self.STATUS['key_not_found']: raise MemcachedException( 'Code: %d Message: %s' % (status, extra_content), status) ret = {} for key in keys: keybytes = str_to_bytes(key) if keybytes in d: ret[key] = d[keybytes] return ret
def authenticate(self, username, password): """ Authenticate user on server. :param username: Username used to be authenticated. :type username: basestring :param password: Password used to be authenticated. :type password: basestring :return: True if successful. :raises: InvalidCredentials, AuthenticationNotSupported, MemcachedException :rtype: bool """ logger.info('Authenticating as %s' % username) self.connection.send( struct.pack(self.HEADER_STRUCT, self.MAGIC['request'], self.COMMANDS['auth_negotiation']['command'], 0, 0, 0, 0, 0, 0, 0)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['unknown_command']: logger.debug('Server does not requires authentication.') return True methods = extra_content if not 'PLAIN' in methods: raise AuthenticationNotSupported('This module only supports ' 'PLAIN auth for now.') method = 'PLAIN' auth = '\x00%s\x00%s' % (username, password) self.connection.send( struct.pack( self.HEADER_STRUCT + self.COMMANDS['auth_request']['struct'] % (len(method), len(auth)), self.MAGIC['request'], self.COMMANDS['auth_request']['command'], len(method), 0, 0, 0, len(method) + len(auth), 0, 0, method, auth)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['auth_error']: raise InvalidCredentials("Incorrect username or password") if status != self.STATUS['success']: raise MemcachedException('Code: %d Message: %s' % (status, extra_content)) logger.debug('Auth OK. Code: %d Message: %s' % (status, extra_content)) self.authenticated = True return True
def get_multi(self, keys): """ Get multiple keys from server. :param keys: A list of keys to from server. :type keys: list :return: A dict with all requested keys. :rtype: dict """ # pipeline N-1 getkq requests, followed by a regular getk to uncork the # server keys, last = keys[:-1], keys[-1] msg = ''.join([ struct.pack(self.HEADER_STRUCT + self.COMMANDS['getkq']['struct'] % (len(key)), self.MAGIC['request'], self.COMMANDS['getkq']['command'], len(key), 0, 0, 0, len(key), 0, 0, key) for key in keys]) msg += struct.pack(self.HEADER_STRUCT + self.COMMANDS['getk']['struct'] % (len(last)), self.MAGIC['request'], self.COMMANDS['getk']['command'], len(last), 0, 0, 0, len(last), 0, 0, last) self._send(msg) d = {} opcode = -1 while opcode != self.COMMANDS['getk']['command']: (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['success']: flags, key, value = struct.unpack('!L%ds%ds' % (keylen, bodylen - keylen - 4), extra_content) d[key] = self.deserialize(value, flags), cas elif status == self.STATUS['server_disconnected']: break elif status != self.STATUS['key_not_found']: raise MemcachedException('Code: %d Message: %s' % (status, extra_content)) return d
def _set_add_replace(self, command, key, value, time, cas=0): """ Function to set/add/replace commands. :param key: Key's name :type key: basestring :param value: A value to be stored on server. :type value: object :param time: Time in seconds that your key will expire. :type time: int :param cas: The CAS value that must be matched for this operation to complete, or 0 for no CAS. :type cas: int :return: True in case of success and False in case of failure :rtype: bool """ logger.info('Setting/adding/replacing key %s.' % key) flags, value = self.serialize(value) logger.info('Value bytes %d.' % len(value)) self._send( struct.pack( self.HEADER_STRUCT + self.COMMANDS[command]['struct'] % (len(key), len(value)), self.MAGIC['request'], self.COMMANDS[command]['command'], len(key), 8, 0, 0, len(key) + len(value) + 8, 0, cas, flags, time, key, value)) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status != self.STATUS['success']: if status == self.STATUS['key_exists']: return False elif status == self.STATUS['key_not_found']: return False elif status == self.STATUS['server_disconnected']: return False raise MemcachedException('Code: %d Message: %s' % (status, extra_content)) return True
def get(self, key): """ Get a key and its CAS value from server. If the value isn't cached, return (None, None). :param key: Key's name :type key: six.string_types :return: Returns (value, cas). :rtype: object """ logger.debug('Getting key %s', key) keybytes = str_to_bytes(key) data = struct.pack( self.HEADER_STRUCT + self.COMMANDS['get']['struct'] % (len(keybytes), ), self.MAGIC['request'], self.COMMANDS['get']['command'], len(keybytes), 0, 0, 0, len(keybytes), 0, 0, keybytes) self._send(data) (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() logger.debug('Value Length: %d. Body length: %d. Data type: %d', extlen, bodylen, datatype) if status != self.STATUS['success']: if status == self.STATUS['key_not_found']: logger.debug('Key not found. Message: %s', extra_content) return None, None if status == self.STATUS['server_disconnected']: return None, None raise MemcachedException( 'Code: %d Message: %s' % (status, extra_content), status) flags, value = struct.unpack('!L%ds' % (bodylen - 4, ), extra_content) return self.deserialize(value, flags), cas
def get_multi(self, keys): """ Get multiple keys from server. Since keys are converted to b'' when six.PY3 the keys need to be decoded back into string . e.g key='test' is read as b'test' and then decoded back to 'test' This encode/decode does not work when key is already a six.binary_type hence this function remembers which keys were originally sent as str so that it only decoded those keys back to string which were sent as string :param keys: A list of keys to from server. :type keys: list :return: A dict with all requested keys. :rtype: dict """ # pipeline N-1 getkq requests, followed by a regular getk to uncork the # server o_keys = keys keys, last = keys[:-1], str_to_bytes(keys[-1]) if six.PY2: msg = '' else: msg = b'' msg = msg.join([ struct.pack(self.HEADER_STRUCT + self.COMMANDS['getkq']['struct'] % (len(key)), self.MAGIC['request'], self.COMMANDS['getkq']['command'], len(key), 0, 0, 0, len(key), 0, 0, str_to_bytes(key)) for key in keys]) msg += struct.pack(self.HEADER_STRUCT + self.COMMANDS['getk']['struct'] % (len(last)), self.MAGIC['request'], self.COMMANDS['getk']['command'], len(last), 0, 0, 0, len(last), 0, 0, last) self._send(msg) d = {} opcode = -1 while opcode != self.COMMANDS['getk']['command']: (magic, opcode, keylen, extlen, datatype, status, bodylen, opaque, cas, extra_content) = self._get_response() if status == self.STATUS['success']: flags, key, value = struct.unpack('!L%ds%ds' % (keylen, bodylen - keylen - 4), extra_content) if six.PY2: d[key] = self.deserialize(value, flags), cas else: try: decoded_key = key.decode() except UnicodeDecodeError: d[key] = self.deserialize(value, flags), cas else: if decoded_key in o_keys: d[decoded_key] = self.deserialize(value, flags), cas else: d[key] = self.deserialize(value, flags), cas elif status == self.STATUS['server_disconnected']: break elif status != self.STATUS['key_not_found']: raise MemcachedException('Code: %d Message: %s' % (status, extra_content), status) return d