Initial commit of all pymemcache files
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# compiled code.
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
# dist
|
||||
# build
|
||||
eggs
|
||||
# parts
|
||||
# bin
|
||||
# var
|
||||
# sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# Swap files.
|
||||
*.swp
|
||||
0
pymemcache/__init__.py
Normal file
0
pymemcache/__init__.py
Normal file
714
pymemcache/client.py
Normal file
714
pymemcache/client.py
Normal file
@@ -0,0 +1,714 @@
|
||||
"""
|
||||
A comprehensive, fast, pure-Python memcached client library.
|
||||
|
||||
Basic Usage:
|
||||
------------
|
||||
|
||||
from pymemcache.client import Client
|
||||
|
||||
client = Client(('localhost', 11211))
|
||||
client.set('some_key', 'some_value', noreply=True)
|
||||
result = client.get('some_key')
|
||||
|
||||
|
||||
Serialization:
|
||||
--------------
|
||||
|
||||
import json
|
||||
from pymemcache.client import Client
|
||||
|
||||
def json_serializer(key, value):
|
||||
if type(value) == str:
|
||||
return value, 1
|
||||
return json.dumps(value), 2
|
||||
|
||||
def json_deserializer(key, value, flags):
|
||||
if flags == 1:
|
||||
return value
|
||||
if flags == 2:
|
||||
return json.loads(value)
|
||||
raise Exception("Unknown serialization format")
|
||||
|
||||
client = Client(('localhost', 11211), serializer=json_serializer,
|
||||
deserializer=json_deserializer)
|
||||
client.set('key', {'a':'b', 'c':'d'}, noreply=True)
|
||||
result = client.get('key')
|
||||
|
||||
|
||||
Best Practices:
|
||||
---------------
|
||||
|
||||
- Always set the connect_timeout and timeout arguments in the constructor to
|
||||
avoid blocking your process when memcached is slow. Consider setting them
|
||||
to small values like 0.05 (50ms) or less.
|
||||
- Use the "noreply" flag whenever possible for a significant performance
|
||||
boost.
|
||||
- Use get_many and gets_many whenever possible, as they result in less
|
||||
round trip times for fetching multiple keys.
|
||||
- Use the "ignore_exc" flag to treat memcache/network errors as cache misses
|
||||
on calls to the get* methods.
|
||||
|
||||
|
||||
Not Implemented:
|
||||
----------------
|
||||
|
||||
The following features are not implemented by this library:
|
||||
|
||||
- Retries: It generally isn't worth retrying failed memcached calls. Use the
|
||||
ignore_exc flag to treat failures as cache misses.
|
||||
- Pooling: coming soon?
|
||||
- Clustering: coming soon?
|
||||
- Key/value validation: it's relatively expensive to validate keys and values
|
||||
on the client side, and memcached already does so on the server side.
|
||||
- Unix sockets: coming soon?
|
||||
- Binary protocol: coming soon?
|
||||
"""
|
||||
|
||||
__author__ = "Charles Gordon"
|
||||
|
||||
|
||||
import socket
|
||||
|
||||
|
||||
RECV_SIZE = 1024
|
||||
VALID_STORE_RESULTS = {
|
||||
'set': ('STORED',),
|
||||
'add': ('STORED', 'NOT_STORED'),
|
||||
'replace': ('STORED', 'NOT_STORED'),
|
||||
'append': ('STORED', 'NOT_STORED'),
|
||||
'prepend': ('STORED', 'NOT_STORED'),
|
||||
'cas': ('STORED', 'EXISTS', 'NOT_FOUND'),
|
||||
}
|
||||
|
||||
|
||||
class MemcacheError(Exception):
|
||||
"Base exception class"
|
||||
pass
|
||||
|
||||
|
||||
class MemcacheUnknownCommandError(MemcacheError):
|
||||
"""Raised when memcached fails to parse a request, likely due to a bug in
|
||||
this library or a version mismatch with memcached."""
|
||||
pass
|
||||
|
||||
|
||||
class MemcacheClientError(MemcacheError):
|
||||
"""Raised when memcached fails to parse the arguments to a request, likely
|
||||
due to a malformed key and/or value, a bug in this library, or a version
|
||||
mismatch with memcached."""
|
||||
pass
|
||||
|
||||
|
||||
class MemcacheServerError(MemcacheError):
|
||||
"""Raised when memcached reports a failure while processing a request,
|
||||
likely due to a bug or transient issue in memcached."""
|
||||
pass
|
||||
|
||||
|
||||
class MemcacheUnknownError(MemcacheError):
|
||||
"""Raised when this library receives a response from memcached that it
|
||||
cannot parse, likely due to a bug in this library or a version mismatch
|
||||
with memcached."""
|
||||
pass
|
||||
|
||||
|
||||
class MemcacheUnexpectedCloseError(MemcacheError):
|
||||
"Raised when the connection with memcached closes unexpectedly."
|
||||
pass
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""
|
||||
A client for a single memcached server.
|
||||
|
||||
Keys and Values:
|
||||
----------------
|
||||
|
||||
Keys must have a __str__() method which should return a str with no more
|
||||
than 250 ASCII characters and no whitespace or control characters. Unicode
|
||||
strings must be encoded (as UTF-8, for example) unless they consist only
|
||||
of ASCII characters that are neither whitespace nor control characters.
|
||||
|
||||
Values must have a __str__() method and a __len__() method (unless
|
||||
serialization is being used, see below). The __str__() method can return
|
||||
any str object, and the __len__() method must return the length of the
|
||||
str returned. For instance, passing a list won't work, because the str
|
||||
returned by list.__str__() is not the same length as the value returned
|
||||
by list.__len__(). As with keys, unicode values must be encoded if they
|
||||
contain characters not in the ASCII subset.
|
||||
|
||||
Serialization and Deserialization:
|
||||
----------------------------------
|
||||
|
||||
The constructor takes two optional functions, one for "serialization" of
|
||||
values, and one for "deserialization". The serialization function takes
|
||||
two arguments, a key and a value, and returns a tuple of two elements, the
|
||||
serialized value, and an integer in the range 0-65535 (the "flags"). The
|
||||
deserialization function takes three parameters, a key, value and flags
|
||||
and returns the deserialized value.
|
||||
|
||||
Here is an example using JSON for non-str values:
|
||||
|
||||
def serialize_json(key, value):
|
||||
if type(value) == str:
|
||||
return value, 1
|
||||
return json.dumps(value), 2
|
||||
|
||||
def deserialize_json(key, value, flags):
|
||||
if flags == 1:
|
||||
return value
|
||||
if flags == 2:
|
||||
return json.loads(value)
|
||||
raise Exception("Unknown flags for value: {}".format(flags))
|
||||
|
||||
Error Handling:
|
||||
---------------
|
||||
|
||||
All of the methods in this class that talk to memcached can throw one of
|
||||
the following exceptions:
|
||||
|
||||
* MemcacheUnknownCommandError
|
||||
* MemcacheClientError
|
||||
* MemcacheServerError
|
||||
* MemcacheUnknownError
|
||||
* MemcacheUnexpectedCloseError
|
||||
* socket.timeout
|
||||
* socket.error
|
||||
|
||||
Instances of this class maintain a persistent connection to memcached
|
||||
which is terminated when any of these exceptions are raised. The next
|
||||
call to a method on the object will result in a new connection being made
|
||||
to memcached.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
server,
|
||||
serializer=None,
|
||||
deserializer=None,
|
||||
connect_timeout=None,
|
||||
timeout=None,
|
||||
no_delay=False,
|
||||
ignore_exc=False):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
Args:
|
||||
server: tuple(hostname, port)
|
||||
serializer: optional function, see notes in the class docs.
|
||||
deserializer: optional function, see notes in the class docs.
|
||||
connect_timeout: optional float, seconds to wait for a connection to
|
||||
the memcached server. Defaults to "forever" (uses the underlying
|
||||
default socket timeout, which can be very long).
|
||||
timeout: optional float, seconds to wait for send or recv calls on
|
||||
the socket connected to memcached. Defaults to "forever" (uses the
|
||||
underlying default socket timeout, which can be very long).
|
||||
no_delay: optional bool, set the TCP_NODELAY flag, which may help
|
||||
with performance in some cases. Defaults to False.
|
||||
ignore_exc: optional bool, True to cause the "get", "gets",
|
||||
"get_many" and "gets_many" calls to treat any errors as cache
|
||||
misses. Defaults to False.
|
||||
|
||||
Notes:
|
||||
The constructor does not make a connection to memcached. The first
|
||||
call to a method on the object will do that.
|
||||
"""
|
||||
self.server = server
|
||||
self.serializer = serializer
|
||||
self.deserializer = deserializer
|
||||
self.connect_timeout = connect_timeout
|
||||
self.timeout = timeout
|
||||
self.no_delay = no_delay
|
||||
self.ignore_exc = ignore_exc
|
||||
self.sock = None
|
||||
self.buf = ''
|
||||
|
||||
def _connect(self):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(self.connect_timeout)
|
||||
sock.connect(self.server)
|
||||
sock.settimeout(self.timeout)
|
||||
if self.no_delay:
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
self.sock = sock
|
||||
|
||||
def close(self):
|
||||
"""Close the connetion to memcached, if it is open. The next call to a
|
||||
method that requires a connection will re-open it."""
|
||||
if self.sock is not None:
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.sock = None
|
||||
self.buf = ''
|
||||
|
||||
def set(self, key, value, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "set" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' on success, or raises an Exception on error (see
|
||||
class documentation).
|
||||
"""
|
||||
return self._store_cmd('set', key, expire, noreply, value)
|
||||
|
||||
def add(self, key, value, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "add" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' if the value was stored, 'NOT_STORED' if the key
|
||||
already existed, or an Exception on error (see class docs).
|
||||
"""
|
||||
return self._store_cmd('add', key, expire, noreply, value)
|
||||
|
||||
def replace(self, key, value, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "replace" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' if the value was stored, 'NOT_STORED' if the key
|
||||
didn't already exist or an Exception on error (see class docs).
|
||||
"""
|
||||
return self._store_cmd('replace', key, expire, noreply, value)
|
||||
|
||||
def append(self, key, value, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "append" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' on success, or raises an Exception on error (see
|
||||
the class docs).
|
||||
"""
|
||||
return self._store_cmd('append', key, expire, noreply, value)
|
||||
|
||||
def prepend(self, key, value, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "prepend" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' on success, or raises an Exception on error (see
|
||||
the class docs).
|
||||
"""
|
||||
return self._store_cmd('prepend', key, expire, noreply, value)
|
||||
|
||||
def cas(self, key, value, cas, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "cas" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: str, see class docs for details.
|
||||
cas: int or str that only contains the characters '0'-'9'.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'STORED' if the value was stored, 'EXISTS' if the key
|
||||
already existed with a different cas, 'NOT_FOUND' if the key didn't
|
||||
exist or raises an Exception on error (see the class docs).
|
||||
"""
|
||||
return self._store_cmd('cas', key, expire, noreply, value, cas)
|
||||
|
||||
def get(self, key):
|
||||
"""
|
||||
The memcached "get" command, but only for one key, as a convenience.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
|
||||
Returns:
|
||||
The value for the key, or None if the key wasn't found, or raises
|
||||
an Exception on error (see class docs).
|
||||
"""
|
||||
return self._fetch_cmd('get', [key], False).get(key, None)
|
||||
|
||||
def get_many(self, keys):
|
||||
"""
|
||||
The memcached "get" command.
|
||||
|
||||
Args:
|
||||
keys: list(str), see class docs for details.
|
||||
|
||||
Returns:
|
||||
A dict in which the keys are elements of the "keys" argument list
|
||||
and the values are values from the cache. The dict may contain all,
|
||||
some or none of the given keys. An exception is raised on errors (see
|
||||
the class docs for details).
|
||||
"""
|
||||
return self._fetch_cmd('get', keys, False)
|
||||
|
||||
def gets(self, key):
|
||||
"""
|
||||
The memcached "gets" command for one key, as a convenience.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
|
||||
Returns:
|
||||
A tuple of (key, cas), or (None, None) if the key was not found.
|
||||
Raises an Exception on errors (see class docs for details).
|
||||
"""
|
||||
return self._fetch_cmd('gets', [key], True).get(key, (None, None))
|
||||
|
||||
def gets_many(self, keys):
|
||||
"""
|
||||
The memcached "gets" command.
|
||||
|
||||
Args:
|
||||
keys: list(str), see class docs for details.
|
||||
|
||||
Returns:
|
||||
A dict in which the keys are elements of the "keys" argument list and
|
||||
the values are tuples of (value, cas) from the cache. The dict may
|
||||
contain all, some or none of the given keys. An exception is raised
|
||||
on errors (see the class docs for details).
|
||||
"""
|
||||
return self._fetch_cmd('gets', keys, True)
|
||||
|
||||
def delete(self, key, noreply=False):
|
||||
"""
|
||||
The memcached "delete" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
|
||||
Returns:
|
||||
The string 'DELTED' if the key existed, and was deleted, 'NOT_FOUND'
|
||||
if the string did not exist, or raises an Exception on error (see the
|
||||
class docs for details).
|
||||
"""
|
||||
cmd = 'delete {}{}\r\n'.format(key, ' noreply' if noreply else '')
|
||||
return self._misc_cmd(cmd, 'delete', noreply)
|
||||
|
||||
def incr(self, key, value, noreply=False):
|
||||
"""
|
||||
The memcached "incr" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: int, the amount by which to increment the value.
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'NOT_FOUND', or an integer which is the value of the key
|
||||
after incrementing. Raises an Exception on errors (see the class docs
|
||||
for details).
|
||||
"""
|
||||
cmd = "incr {} {}{}\r\n".format(
|
||||
key,
|
||||
str(value),
|
||||
' noreply' if noreply else '')
|
||||
result = self._misc_cmd(cmd, 'incr', noreply)
|
||||
if noreply:
|
||||
return None
|
||||
if result == 'NOT_FOUND':
|
||||
return result
|
||||
return int(result)
|
||||
|
||||
def decr(self, key, value, noreply=False):
|
||||
"""
|
||||
The memcached "decr" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
value: int, the amount by which to increment the value.
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'NOT_FOUND', or an integer which is the value of the key
|
||||
after decrementing. Raises an Exception on errors (see the class
|
||||
docs for details).
|
||||
"""
|
||||
cmd = "decr {} {}{}\r\n".format(
|
||||
key,
|
||||
str(value),
|
||||
' noreply' if noreply else '')
|
||||
result = self._misc_cmd(cmd, 'decr', noreply)
|
||||
if noreply:
|
||||
return None
|
||||
if result == 'NOT_FOUND':
|
||||
return result
|
||||
return int(result)
|
||||
|
||||
def touch(self, key, expire=0, noreply=False):
|
||||
"""
|
||||
The memcached "touch" command.
|
||||
|
||||
Args:
|
||||
key: str, see class docs for details.
|
||||
expire: optional int, number of seconds until the item is expired
|
||||
from the cache, or zero for no expiry (the default).
|
||||
noreply: optional bool, False to wait for the reply (the default).
|
||||
|
||||
Returns:
|
||||
The string 'OK' if the value was stored or raises an Exception on
|
||||
error (see the class docs).
|
||||
"""
|
||||
cmd = "touch {} {}{}\r\n".format(
|
||||
key,
|
||||
expire,
|
||||
' noreply' if noreply else '')
|
||||
return self._misc_cmd(cmd, 'touch', noreply)
|
||||
|
||||
def stats(self):
|
||||
# TODO(charles)
|
||||
pass
|
||||
|
||||
def flush_all(self, delay=0, noreply=False):
|
||||
"""
|
||||
The memcached "flush_all" command.
|
||||
|
||||
Args:
|
||||
delay: optional int, the number of seconds to wait before flushing,
|
||||
or zero to flush immediately (the default).
|
||||
noreply: optional bool, False to wait for the response (the default).
|
||||
|
||||
Returns:
|
||||
The string 'OK' on success, or raises an Exception on error (see the
|
||||
class docs).
|
||||
"""
|
||||
cmd = "flush_all {}{}\r\n".format(delay, ' noreply' if noreply else '')
|
||||
return self._misc_cmd(cmd, 'flush_all', noreply)
|
||||
|
||||
def quit(self):
|
||||
"""
|
||||
The memcached "quit" command.
|
||||
|
||||
This will close the connection with memcached. Calling any other
|
||||
method on this object will re-open the connection, so this object can
|
||||
be re-used after quit.
|
||||
"""
|
||||
cmd = "quit\r\n"
|
||||
self._misc_cmd(cmd, 'quit', True)
|
||||
self.close()
|
||||
|
||||
def _raise_errors(self, line, name):
|
||||
if line.startswith('ERROR'):
|
||||
raise MemcacheUnknownCommandError(name)
|
||||
|
||||
if line.startswith('CLIENT_ERROR'):
|
||||
error = line[line.find(' ') + 1:]
|
||||
raise MemcacheClientError(error)
|
||||
|
||||
if line.startswith('SERVER_ERROR'):
|
||||
error = line[line.find(' ') + 1:]
|
||||
raise MemcacheServerError(error)
|
||||
|
||||
def _fetch_cmd(self, name, keys, expect_cas):
|
||||
if not self.sock:
|
||||
self._connect()
|
||||
|
||||
cmd = '{} {}\r\n'.format(name, ' '.join(keys))
|
||||
try:
|
||||
self.sock.sendall(cmd)
|
||||
|
||||
result = {}
|
||||
while True:
|
||||
self.buf, line = _readline(self.sock, self.buf)
|
||||
self._raise_errors(line, name)
|
||||
|
||||
if line == 'END':
|
||||
return result
|
||||
elif line.startswith('VALUE'):
|
||||
if expect_cas:
|
||||
_, key, flags, size, cas = line.split()
|
||||
else:
|
||||
_, key, flags, size = line.split()
|
||||
|
||||
self.buf, value = _readvalue(self.sock,
|
||||
self.buf,
|
||||
int(size))
|
||||
|
||||
if self.deserializer:
|
||||
value = self.deserializer(value, int(flags))
|
||||
|
||||
if expect_cas:
|
||||
result[key] = (value, cas)
|
||||
else:
|
||||
result[key] = value
|
||||
else:
|
||||
raise MemcacheUnknownError(line[:32])
|
||||
except Exception:
|
||||
self.close()
|
||||
if self.ignore_exc:
|
||||
return {}
|
||||
raise
|
||||
|
||||
def _store_cmd(self, name, key, expire, noreply, data, cas=None):
|
||||
if not self.sock:
|
||||
self._connect()
|
||||
|
||||
if self.serializer:
|
||||
data, flags = self.serializer(data)
|
||||
else:
|
||||
flags = 0
|
||||
|
||||
if cas is not None and noreply:
|
||||
extra = ' {} noreply'.format(cas)
|
||||
elif cas is not None and not noreply:
|
||||
extra = ' {}'.format(cas)
|
||||
elif cas is None and noreply:
|
||||
extra = ' noreply'
|
||||
else:
|
||||
extra = ''
|
||||
|
||||
cmd = '{} {} {} {} {}{}\r\n{}\r\n'.format(
|
||||
name,
|
||||
key,
|
||||
flags,
|
||||
expire,
|
||||
len(data),
|
||||
extra,
|
||||
data)
|
||||
|
||||
try:
|
||||
self.sock.sendall(cmd)
|
||||
|
||||
if noreply:
|
||||
return
|
||||
|
||||
self.buf, line = _readline(self.sock, self.buf)
|
||||
self._raise_errors(line, name)
|
||||
|
||||
if line in VALID_STORE_RESULTS[name]:
|
||||
return line
|
||||
else:
|
||||
raise MemcacheUnknownError(line[:32])
|
||||
except Exception:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _misc_cmd(self, cmd, cmd_name, noreply):
|
||||
if not self.sock:
|
||||
self._connect()
|
||||
|
||||
try:
|
||||
self.sock.sendall(cmd)
|
||||
|
||||
if noreply:
|
||||
return
|
||||
|
||||
_, line = _readline(self.sock, '')
|
||||
self._raise_errors(line, cmd_name)
|
||||
|
||||
return line
|
||||
except Exception:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
|
||||
def _readline(sock, buf):
|
||||
"""Read line of text from the socket.
|
||||
|
||||
Read a line of text (delimited by "\r\n") from the socket, and
|
||||
return that line along with any trailing characters read from the
|
||||
socket.
|
||||
|
||||
Args:
|
||||
sock: Socket object, should be connected.
|
||||
buf: String, zero or more characters, returned from an earlier
|
||||
call to _readline or _readvalue (pass an empty string on the
|
||||
first call).
|
||||
|
||||
Returns:
|
||||
A tuple of (buf, line) where line is the full line read from the
|
||||
socket (minus the "\r\n" characters) and buf is any trailing
|
||||
characters read after the "\r\n" was found (which may be an empty
|
||||
string).
|
||||
|
||||
"""
|
||||
chunks = []
|
||||
last_char = ''
|
||||
|
||||
while True:
|
||||
idx = buf.find('\r\n')
|
||||
# We're reading in chunks, so "\r\n" could appear in one chunk,
|
||||
# or across the boundary of two chunks, so we check for both
|
||||
# cases.
|
||||
if idx != -1:
|
||||
before, sep, after = buf.partition("\r\n")
|
||||
chunks.append(before)
|
||||
return after, ''.join(chunks)
|
||||
elif last_char == '\r' and buf[0] == '\n':
|
||||
# Strip the last character from the last chunk.
|
||||
chunks[-1] = chunks[-1][:-1]
|
||||
return buf[1:], ''.join(chunks)
|
||||
|
||||
if buf:
|
||||
chunks.append(buf)
|
||||
last_char = buf[-1]
|
||||
|
||||
buf = sock.recv(RECV_SIZE)
|
||||
if not buf:
|
||||
raise MemcacheUnexpectedCloseError()
|
||||
|
||||
|
||||
def _readvalue(sock, buf, size):
|
||||
"""Read specified amount of bytes from the socket.
|
||||
|
||||
Read size bytes, followed by the "\r\n" characters, from the socket,
|
||||
and return those bytes and any trailing bytes read after the "\r\n".
|
||||
|
||||
Args:
|
||||
sock: Socket object, should be connected.
|
||||
buf: String, zero or more characters, returned from an earlier
|
||||
call to _readline or _readvalue (pass an empty string on the
|
||||
first call).
|
||||
size: Integer, number of bytes to read from the socket.
|
||||
|
||||
Returns:
|
||||
A tuple of (buf, value) where value is the bytes read from the
|
||||
socket (there will be exactly size bytes) and buf is trailing
|
||||
characters read after the "\r\n" following the bytes (but not
|
||||
including the \r\n).
|
||||
|
||||
"""
|
||||
chunks = []
|
||||
rlen = size + 2
|
||||
while rlen - len(buf) > 0:
|
||||
if buf:
|
||||
rlen -= len(buf)
|
||||
chunks.append(buf)
|
||||
buf = sock.recv(RECV_SIZE)
|
||||
if not buf:
|
||||
raise MemcacheUnexpectedCloseError()
|
||||
|
||||
chunks.append(buf[:rlen - 2])
|
||||
return buf[rlen:], ''.join(chunks)
|
||||
0
pymemcache/test/__init__.py
Normal file
0
pymemcache/test/__init__.py
Normal file
82
pymemcache/test/benchmark.py
Normal file
82
pymemcache/test/benchmark.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import argparse
|
||||
import time
|
||||
|
||||
|
||||
def test_client(name, client, size, count):
|
||||
client.flush_all()
|
||||
|
||||
value = 'X' * size
|
||||
|
||||
start = time.time()
|
||||
|
||||
for i in xrange(count):
|
||||
client.set(str(i), value)
|
||||
|
||||
for i in xrange(count):
|
||||
client.get(str(i))
|
||||
|
||||
duration = time.time() - start
|
||||
print "{}: {}".format(name, duration)
|
||||
|
||||
|
||||
def test_pylibmc(host, port, size, count):
|
||||
try:
|
||||
import pylibmc
|
||||
except Exception:
|
||||
print "Could not import pylibmc, skipping test..."
|
||||
return
|
||||
|
||||
client = pylibmc.Client(['{}:{}'.format(host, port)])
|
||||
client.behaviors = {"tcp_nodelay": True}
|
||||
test_client('pylibmc', client, size, count)
|
||||
|
||||
|
||||
def test_memcache(host, port, size, count):
|
||||
try:
|
||||
import memcache
|
||||
except Exception:
|
||||
print "Could not import pymemcache.client, skipping test..."
|
||||
return
|
||||
|
||||
client = memcache.Client(['{}:{}'.format(host, port)])
|
||||
test_client('memcache', client, size, count)
|
||||
|
||||
|
||||
def test_pymemcache(host, port, size, count):
|
||||
try:
|
||||
import pymemcache.client
|
||||
except Exception:
|
||||
print "Could not import pymemcache.client, skipping test..."
|
||||
return
|
||||
|
||||
client = pymemcache.client.Client((host, port))
|
||||
test_client('pymemcache', client, size, count)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-s', '--server',
|
||||
metavar='HOST',
|
||||
required=True)
|
||||
parser.add_argument('-p', '--port',
|
||||
metavar='PORT',
|
||||
type=int,
|
||||
required=True)
|
||||
parser.add_argument('-z', '--size',
|
||||
metavar='SIZE',
|
||||
default=1024,
|
||||
type=int)
|
||||
parser.add_argument('-c', '--count',
|
||||
metavar='COUNT',
|
||||
default=10000,
|
||||
type=int)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
test_pylibmc(args.server, args.port, args.size, args.count)
|
||||
test_memcache(args.server, args.port, args.size, args.count)
|
||||
test_pymemcache(args.server, args.port, args.size, args.count)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
204
pymemcache/test/integration.py
Normal file
204
pymemcache/test/integration.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from pymemcache.client import Client, MemcacheClientError
|
||||
from nose import tools
|
||||
|
||||
|
||||
def get_set_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
client.set('key', 'value')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
client.set('key2', 'value2', noreply=True)
|
||||
result = client.get('key2')
|
||||
tools.assert_equal(result, 'value2')
|
||||
|
||||
result = client.get_many(['key', 'key2'])
|
||||
tools.assert_equal(result, {'key': 'value', 'key2': 'value2'})
|
||||
|
||||
|
||||
def add_replace_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.add('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
result = client.add('key', 'value2')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
result = client.replace('key1', 'value1')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
result = client.get('key1')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
result = client.replace('key', 'value2')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value2')
|
||||
|
||||
|
||||
def append_prepend_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.append('key', 'value')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
result = client.set('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.append('key', 'after')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'valueafter')
|
||||
|
||||
result = client.prepend('key1', 'value')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
result = client.get('key1')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
result = client.prepend('key', 'before')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'beforevalueafter')
|
||||
|
||||
|
||||
def cas_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.cas('key', 'value', '1')
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
result = client.set('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
result = client.cas('key', 'value', '1')
|
||||
tools.assert_equal(result, 'EXISTS')
|
||||
|
||||
result, cas = client.gets('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
result = client.cas('key', 'value1', cas)
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
result = client.cas('key', 'value2', cas)
|
||||
tools.assert_equal(result, 'EXISTS')
|
||||
|
||||
|
||||
def gets_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.gets('key')
|
||||
tools.assert_equal(result, (None, None))
|
||||
|
||||
result = client.set('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.gets('key')
|
||||
tools.assert_equal(result[0], 'value')
|
||||
|
||||
|
||||
def delete_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.delete('key')
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, None)
|
||||
result = client.set('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.delete('key')
|
||||
tools.assert_equal(result, 'DELETED')
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
|
||||
def incr_decr_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
result = client.incr('key', 1)
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
result = client.set('key', '0')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
result = client.incr('key', 1)
|
||||
tools.assert_equal(result, 1)
|
||||
|
||||
def _bad_int():
|
||||
client.incr('key', 'foobar')
|
||||
|
||||
tools.assert_raises(MemcacheClientError, _bad_int)
|
||||
|
||||
result = client.decr('key1', 1)
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
result = client.decr('key', 1)
|
||||
tools.assert_equal(result, 0)
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, '0')
|
||||
|
||||
|
||||
def misc_test(host, port):
|
||||
client = Client((host, port))
|
||||
client.flush_all()
|
||||
|
||||
|
||||
def test_serialization_deserialization(host, port):
|
||||
def _ser(value):
|
||||
return json.dumps(value), 1
|
||||
|
||||
def _des(value, flags):
|
||||
if flags == 1:
|
||||
return json.loads(value)
|
||||
return value
|
||||
|
||||
client = Client((host, port), serializer=_ser, deserializer=_des)
|
||||
client.flush_all()
|
||||
|
||||
value = {'a': 'b', 'c': ['d']}
|
||||
client.set('key', value)
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, value)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-s', '--server',
|
||||
metavar='HOST',
|
||||
required=True)
|
||||
parser.add_argument('-p', '--port',
|
||||
metavar='PORT',
|
||||
type=int,
|
||||
required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
get_set_test(args.server, args.port)
|
||||
add_replace_test(args.server, args.port)
|
||||
append_prepend_test(args.server, args.port)
|
||||
cas_test(args.server, args.port)
|
||||
gets_test(args.server, args.port)
|
||||
delete_test(args.server, args.port)
|
||||
incr_decr_test(args.server, args.port)
|
||||
misc_test(args.server, args.port)
|
||||
test_serialization_deserialization(args.server, args.port)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
369
pymemcache/test/test_client.py
Normal file
369
pymemcache/test/test_client.py
Normal file
@@ -0,0 +1,369 @@
|
||||
import collections
|
||||
import json
|
||||
|
||||
from nose import tools
|
||||
from pymemcache.client import Client, MemcacheUnknownCommandError
|
||||
from pymemcache.client import MemcacheClientError, MemcacheServerError
|
||||
from pymemcache.client import MemcacheUnknownError
|
||||
|
||||
|
||||
class MockSocket(object):
|
||||
def __init__(self, recv_bufs):
|
||||
self.recv_bufs = collections.deque(recv_bufs)
|
||||
self.send_bufs = []
|
||||
self.closed = False
|
||||
|
||||
def sendall(self, value):
|
||||
self.send_bufs.append(value)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
def recv(self, size):
|
||||
value = self.recv_bufs.popleft()
|
||||
if isinstance(value, Exception):
|
||||
raise value
|
||||
return value
|
||||
|
||||
|
||||
def test_set_success():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
result = client.set('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
tools.assert_equal(client.sock.closed, False)
|
||||
tools.assert_equal(len(client.sock.send_bufs), 1)
|
||||
|
||||
|
||||
def test_set_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['ERROR\r\n'])
|
||||
|
||||
def _set():
|
||||
client.set('key', 'value')
|
||||
|
||||
tools.assert_raises(MemcacheUnknownCommandError, _set)
|
||||
|
||||
|
||||
def test_set_client_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['CLIENT_ERROR some message\r\n'])
|
||||
|
||||
def _set():
|
||||
client.set('key', 'value')
|
||||
|
||||
tools.assert_raises(MemcacheClientError, _set)
|
||||
|
||||
|
||||
def test_set_server_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['SERVER_ERROR some message\r\n'])
|
||||
|
||||
def _set():
|
||||
client.set('key', 'value')
|
||||
|
||||
tools.assert_raises(MemcacheServerError, _set)
|
||||
|
||||
|
||||
def test_set_unknown_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['foobarbaz\r\n'])
|
||||
|
||||
def _set():
|
||||
client.set('key', 'value')
|
||||
|
||||
tools.assert_raises(MemcacheUnknownError, _set)
|
||||
|
||||
|
||||
def test_set_noreply():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([])
|
||||
result = client.set('key', 'value', noreply=True)
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
|
||||
def test_set_exception():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([Exception('fail')])
|
||||
|
||||
def _set():
|
||||
client.set('key', 'value')
|
||||
|
||||
tools.assert_raises(Exception, _set)
|
||||
tools.assert_equal(client.sock, None)
|
||||
tools.assert_equal(client.buf, '')
|
||||
|
||||
|
||||
def test_add_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r', '\n'])
|
||||
result = client.add('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
|
||||
def test_add_not_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_', 'STOR', 'ED', '\r\n'])
|
||||
result = client.add('key', 'value')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
|
||||
|
||||
def test_replace_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
result = client.replace('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
|
||||
def test_replace_not_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_STORED\r\n'])
|
||||
result = client.replace('key', 'value')
|
||||
tools.assert_equal(result, 'NOT_STORED')
|
||||
|
||||
|
||||
def test_append_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
result = client.append('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
|
||||
def test_prepend_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
result = client.prepend('key', 'value')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
|
||||
def test_cas_stored():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
result = client.cas('key', 'value', 'cas')
|
||||
tools.assert_equal(result, 'STORED')
|
||||
|
||||
|
||||
def test_cas_exists():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['EXISTS\r\n'])
|
||||
result = client.cas('key', 'value', 'cas')
|
||||
tools.assert_equal(result, 'EXISTS')
|
||||
|
||||
|
||||
def test_cas_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_FOUND\r\n'])
|
||||
result = client.cas('key', 'value', 'cas')
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
|
||||
def test_get_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['END\r\n'])
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
|
||||
def test_get_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key 0 5\r\nvalue\r\nEND\r\n'])
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
|
||||
def test_get_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['ERROR\r\n'])
|
||||
|
||||
def _get():
|
||||
client.get('key')
|
||||
|
||||
tools.assert_raises(MemcacheUnknownCommandError, _get)
|
||||
|
||||
|
||||
def test_get_many_none_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['END\r\n'])
|
||||
result = client.get_many(['key1', 'key2'])
|
||||
tools.assert_equal(result, {})
|
||||
|
||||
|
||||
def test_get_many_some_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key1 0 6\r\nvalue1\r\nEND\r\n'])
|
||||
result = client.get_many(['key1', 'key2'])
|
||||
tools.assert_equal(result, {'key1': 'value1'})
|
||||
|
||||
|
||||
def test_get_many_all_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key1 0 6\r\nvalue1\r\n'
|
||||
'VALUE key2 0 6\r\nvalue2\r\nEND\r\n'])
|
||||
result = client.get_many(['key1', 'key2'])
|
||||
tools.assert_equal(result, {'key1': 'value1', 'key2': 'value2'})
|
||||
|
||||
|
||||
def test_get_unknown_error():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['foobarbaz\r\n'])
|
||||
|
||||
def _get():
|
||||
client.get('key')
|
||||
|
||||
tools.assert_raises(MemcacheUnknownError, _get)
|
||||
|
||||
|
||||
def test_gets_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['END\r\n'])
|
||||
result = client.gets('key')
|
||||
tools.assert_equal(result, (None, None))
|
||||
|
||||
|
||||
def test_gets_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key 0 5 10\r\nvalue\r\nEND\r\n'])
|
||||
result = client.gets('key')
|
||||
tools.assert_equal(result, ('value', '10'))
|
||||
|
||||
|
||||
def test_gets_many_none_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['END\r\n'])
|
||||
result = client.gets_many(['key1', 'key2'])
|
||||
tools.assert_equal(result, {})
|
||||
|
||||
|
||||
def test_gets_many_some_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key1 0 6 11\r\nvalue1\r\nEND\r\n'])
|
||||
result = client.gets_many(['key1', 'key2'])
|
||||
tools.assert_equal(result, {'key1': ('value1', '11')})
|
||||
|
||||
|
||||
def test_get_recv_chunks():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['VALUE key', ' 0 5\r', '\nvalue', '\r\n',
|
||||
'END', '\r', '\n'])
|
||||
result = client.get('key')
|
||||
tools.assert_equal(result, 'value')
|
||||
|
||||
|
||||
def test_delete_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_FOUND\r\n'])
|
||||
result = client.delete('key')
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
|
||||
def test_delete_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['DELETED\r\n'])
|
||||
result = client.delete('key')
|
||||
tools.assert_equal(result, 'DELETED')
|
||||
|
||||
|
||||
def test_delete_noreply():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([])
|
||||
result = client.delete('key', noreply=True)
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
|
||||
def test_delete_exception():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([Exception('fail')])
|
||||
|
||||
def _delete():
|
||||
client.delete('key')
|
||||
|
||||
tools.assert_raises(Exception, _delete)
|
||||
tools.assert_equal(client.sock, None)
|
||||
tools.assert_equal(client.buf, '')
|
||||
|
||||
|
||||
def test_incr_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_FOUND\r\n'])
|
||||
result = client.incr('key', 1)
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
|
||||
def test_incr_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['1\r\n'])
|
||||
result = client.incr('key', 1)
|
||||
tools.assert_equal(result, 1)
|
||||
|
||||
|
||||
def test_incr_noreply():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([])
|
||||
result = client.incr('key', 1, noreply=True)
|
||||
tools.assert_equal(result, None)
|
||||
|
||||
|
||||
def test_incr_exception():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([Exception('fail')])
|
||||
|
||||
def _incr():
|
||||
client.incr('key', 1)
|
||||
|
||||
tools.assert_raises(Exception, _incr)
|
||||
tools.assert_equal(client.sock, None)
|
||||
tools.assert_equal(client.buf, '')
|
||||
|
||||
|
||||
def test_decr_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_FOUND\r\n'])
|
||||
result = client.decr('key', 1)
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
|
||||
def test_decr_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['1\r\n'])
|
||||
result = client.decr('key', 1)
|
||||
tools.assert_equal(result, 1)
|
||||
|
||||
|
||||
def test_flush_all():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['OK\r\n'])
|
||||
result = client.flush_all()
|
||||
tools.assert_equal(result, 'OK')
|
||||
|
||||
|
||||
def test_touch_not_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['NOT_FOUND\r\n'])
|
||||
result = client.touch('key')
|
||||
tools.assert_equal(result, 'NOT_FOUND')
|
||||
|
||||
|
||||
def test_touch_found():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket(['TOUCHED\r\n'])
|
||||
result = client.touch('key')
|
||||
tools.assert_equal(result, 'TOUCHED')
|
||||
|
||||
|
||||
def test_quit():
|
||||
client = Client(None)
|
||||
client.sock = MockSocket([])
|
||||
result = client.quit()
|
||||
tools.assert_equal(result, None)
|
||||
tools.assert_equal(client.sock, None)
|
||||
tools.assert_equal(client.buf, '')
|
||||
|
||||
|
||||
def test_serialization():
|
||||
def _ser(value):
|
||||
return json.dumps(value), 0
|
||||
|
||||
client = Client(None, serializer=_ser)
|
||||
client.sock = MockSocket(['STORED\r\n'])
|
||||
client.set('key', {'a': 'b', 'c': 'd'})
|
||||
print client.sock.send_bufs
|
||||
Reference in New Issue
Block a user