def getset_handler(client, argv): ''' Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value. .. code:: GETSET key value ''' key, value = argv[1], argv[2] try: value = int(value) except ValueError: pass try: obj = get_object(client.db, key, type=RedisStringObject) orig_value = obj.get_bytes() obj.value = value except KeyError: orig_value = None client.db.key_space[key] = RedisStringObject(value) except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') return RedisStringObject(orig_value)
def incrbyfloat_handler(client, argv): ''' Increment the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if one of the following conditions occur: * The key contains a value of the wrong type (not a string). * The current key content or the specified increment are not parsable as a double precision floating point number. If the command is successful the new incremented value is stored as the new value of the key (replacing the old one), and returned to the caller as a string. Both the value already contained in the string key and the increment argument can be optionally provided in exponential notation, however the value computed after the increment is stored consistently in the same format, that is, an integer number followed (if needed) by a dot, and a variable number of digits representing the decimal part of the number. Trailing zeroes are always removed. The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation. .. code:: INCRBYFLOAT key increment :return: the value of key after the increment. :rtype: bytes ''' key, increment = argv[1], argv[2] try: increment = Decimal(increment.decode()) except InvalidOperation: abort(message='value is not a valid float') try: obj = get_object(client.db, key, type=RedisStringObject) value = obj.get_integer() except KeyError: value = 0 obj = RedisStringObject() except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') try: value = obj.get_decimal() except InvalidOperation: abort(message='value is not a valid float') value += increment obj.value = value client.db.key_space[key] = obj return obj
def append_handler(client, argv): ''' If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case. .. code:: APPEND key value :return: the length of the string after the append operation. :rtype: int ''' key, value = argv[1], argv[2] try: obj = get_object(client.db, key, type=RedisStringObject) except KeyError: length = len(value) try: value = int(value) except ValueError: pass client.db.key_space[key] = RedisStringObject(value) return length except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') obj.value = obj.get_bytes() + value return len(obj.value)
def decrby_handler(client, argv): ''' Decrements the number stored at key by decrement. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers. See INCR for extra information on increment/decrement operations. .. code:: DECRBY key decrement :return: the value of key after the decrement :rtype: int ''' key, decrement = argv[1], argv[2] try: decrement = int(decrement) except ValueError: abort(message='value is not an integer or out of range') try: obj = get_object(client.db, key, type=RedisStringObject) value = obj.get_integer() except KeyError: value = 0 obj = RedisStringObject(value) except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') try: value = obj.get_integer() except ValueError: abort(message='value is not an integer or out of range') value -= decrement obj.value = value # client.db.key_space[key] = obj return obj
def incr_handler(client, argv): ''' Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers. Note: this is a string operation because Redis does not have a dedicated integer type. The string stored at the key is interpreted as a base-10 64 bit signed integer to execute the operation. Redis stores integers in their integer representation, so for string values that actually hold an integer, there is no overhead for storing the string representation of the integer. .. code:: INCR key :return: the value of key after the increment :rtype: int ''' key = argv[1] try: obj = get_object(client.db, key, type=RedisStringObject) value = obj.get_integer() except KeyError: value = 0 obj = RedisStringObject(value) except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') try: value = obj.get_integer() except ValueError: abort(message='value is not an integer or out of range') value += 1 obj.value = value client.db.key_space[key] = obj return obj
def setrange_handler(client, argv): ''' Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset. Note that the maximum offset that you can set is 229 -1 (536870911), as Redis Strings are limited to 512 megabytes. If you need to grow beyond this size, you can use multiple keys. .. code:: SETRANGE key offset value :return: the length of the string after it was modified by the command. :rtype: int ''' key, offset, value = argv[1], argv[2], argv[3] try: offset = int(offset) except ValueError: abort(message='value is not an integer or out of range') try: obj = get_object(client.db, key, RedisStringObject) except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') except KeyError: obj = RedisStringObject() stor_value = obj.get_bytes() if len(stor_value) < offset: stor_value += bytes(offset - len(stor_value)) + value else: stor_value = stor_value[0:offset + 1] + value obj.value = stor_value client.db.key_space[key] = obj return len(stor_value)
def mset_handler(client, argv): ''' Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values. MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. .. code:: MSET key value [key value ...] ''' if len(argv) & 1 == 0: abort(message='wrong number of arguments for MSET') for key, value in group_iter(argv[1:], n=2): client.db.key_space[key] = RedisStringObject(value=value) return True
def setnx_handler(client, argv): ''' Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if N ot e X ists". .. code:: SETNX key value :return: 1 if key was set, otherwise 0 :rtype: int ''' key, value = argv[1], argv[2] try: get_object(client.db, key) return 0 except KeyError: client.db.key_space[key] = RedisStringObject(value) return 1
def setex_handler(client, argv): ''' Set key to hold the string value and set key to timeout after a given number of seconds. This command is equivalent to executing the following commands: .. code:: SETEX key seconds value ''' key, seconds, value = argv[1], argv[2], argv[3] try: seconds = int(seconds) if seconds <= 0: abort(message='invalid expire time in SETEX') except ValueError: abort(message='value is not an integer or out of range') client.db.key_space[key] = RedisStringObject(value, expire_time=time.time() + seconds) return True
def msetnx_handler(client, argv): ''' Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists. Because of this semantic MSETNX can be used in order to set different keys representing different fields of an unique logic object in a way that ensures that either all the fields or none at all are set. MSETNX is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. .. code:: MSETNX key value [key value ...] :return: 1 if the all the keys were set. 0 if no key was set (at least one key already existed). :rtype: int ''' if len(argv) & 1 == 0: abort(message='wrong number of arguments for MSET') all_set = True for key, value in group_iter(argv[1:], n=2): try: get_object(client.db, key) all_set = False break except KeyError: pass if all_set: for key, value in group_iter(argv[1:], n=2): client.db.key_space[key] = RedisStringObject(value=value) return int(all_set)
def bitop_handler(client, argv): ''' Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. The BITOP command supports four bitwise operations: AND, OR, XOR and NOT, thus the valid forms to call the command are: * BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN * BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN * BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN * BITOP NOT destkey srckey As you can see NOT is special as it only takes an input key, because it performs inversion of bits so it only makes sense as an unary operator. The result of the operation is always stored at destkey. .. code:: BITOP operation destkey key [key ...] :return: The size of the string stored in the destination key, that is equal to the size of the longest input string. :rtype: int ''' operation, destkey, keys = argv[1].upper(), argv[2], argv[3:] print(operation, destkey, keys) if operation not in (b'AND', b'OR', b'XOR', b'NOT'): abort(message='Don\'t know what to do for "bitop"') if operation == b'NOT': if len(keys) > 1: abort(message='BITOP NOT must be called with a single source key.') try: obj = get_object(client.db, keys[0], RedisStringObject) except KeyError: return 0 except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') ba = bitarray.bitarray() ba.frombytes(obj.get_bytes()) ba.invert() client.db.key_space[destkey] = RedisStringObject(ba.tobytes()) return len(client.db.key_space[destkey].value) if operation == b'AND': oper_func = lambda a, b: a & b elif operation == b'OR': oper_func = lambda a, b: a | b elif operation == b'XOR': oper_func = lambda a, b: a ^ b dest_ba = bitarray.bitarray() try: obj = get_object(client.db, keys[0], RedisStringObject) dest_ba.frombytes(obj.get_bytes()) except KeyError: pass except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') for key in keys[1:]: try: obj = get_object(client.db, key, RedisStringObject) src_ba = bitarray.bitarray() src_ba.frombytes(obj.get_bytes()) if len(src_ba) > len(dest_ba): dest_ba = bitarray.bitarray('0' * (len(src_ba) - len(dest_ba))) + dest_ba elif len(dest_ba) > len(src_ba): src_ba = bitarray.bitarray('0' * (len(dest_ba) - len(src_ba))) + src_ba except KeyError: src_ba = bitarray.bitarray('0' * len(dest_ba)) except TypeError: abort(errtype='WRONGTYPE', message='Operation against a key holding the wrong kind of value') dest_ba = oper_func(dest_ba, src_ba) client.db.key_space[destkey] = RedisStringObject(dest_ba.tobytes()) return len(client.db.key_space[destkey].get_bytes())
def set_handler(client, argv): ''' Set the string value of a key .. code:: SET key value [EX seconds] [PX milliseconds] [NX|XX] :param EX: Set the specified expire time, in seconds. :param PX: Set the specified expire time, in milliseconds. :param NX: Only set the key if it does not already exist. :param XX: Only set the key if it already exist. ''' key, value = argv[1], argv[2] expire_time = None nx = False xx = False cur_index = 3 while cur_index < len(argv): argname = argv[cur_index].lower() if argname == b'ex': if cur_index == len(argv) - 1: abort(message='syntax error') if expire_time is None: expire_time = time.time() cur_index += 1 try: expire_time += int(argv[cur_index]) except: abort(message='syntax error') elif argname == b'px': if cur_index == len(argv) - 1: abort(message='syntax error') if expire_time is None: expire_time = time.time() cur_index += 1 try: expire_time += int(argv[cur_index]) / 1000.0 except: abort(message='syntax error') elif argname == b'nx': nx = True elif argname == b'xx': xx = True else: abort(message='syntax error') cur_index += 1 if nx and xx: abort(message='syntax error') try: get_object(client.db, key) if nx: return None except KeyError: if xx: return None try: value = int(value) except ValueError: pass client.db.key_space[key] = RedisStringObject(value, expire_time=expire_time) return True