diff options
author | Sean Reifschneider <jafo@tummy.com> | 2014-06-07 15:27:05 -0600 |
---|---|---|
committer | Sean Reifschneider <jafo@tummy.com> | 2014-06-07 15:27:05 -0600 |
commit | 45403325e0249ff0f61d6ae449a7daeeb7e852e5 (patch) | |
tree | 6188eb2f35d20b48e8bdccdbbe199698d88eceef | |
parent | e268a8b0cae77a9cf669e5e51d9216c6d5881871 (diff) | |
download | python-memcached-45403325e0249ff0f61d6ae449a7daeeb7e852e5.tar.gz |
Resolving conflicts from cabrera-py33_fixes.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | memcache.py | 729 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | setup.py | 8 | ||||
-rw-r--r-- | test-requirements.txt | 3 | ||||
-rw-r--r-- | tox.ini | 6 |
7 files changed, 425 insertions, 324 deletions
@@ -2,3 +2,4 @@ /dist /python_memcached.egg-info .tox +.coverage diff --git a/.travis.yml b/.travis.yml index 64854a6..ef41904 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - 3.3 - 3.4 - pypy + - 3.3 install: python setup.py install before_script: pip install nose script: nosetests diff --git a/memcache.py b/memcache.py index ce93c82..2df9f28 100644 --- a/memcache.py +++ b/memcache.py @@ -1,12 +1,12 @@ #!/usr/bin/env python -""" -client module for memcached (memory cache daemon) +"""client module for memcached (memory cache daemon) Overview ======== -See U{the MemCached homepage<http://www.danga.com/memcached>} for more about memcached. +See U{the MemCached homepage<http://www.danga.com/memcached>} for more +about memcached. Usage summary ============= @@ -22,7 +22,8 @@ This should give you a feel for how this module operates:: mc.set("another_key", 3) mc.delete("another_key") - mc.set("key", "1") # note that the key used for incr/decr must be a string. + mc.set("key", "1") # note that the key used for incr/decr must be + # a string. mc.incr("key") mc.decr("key") @@ -41,6 +42,7 @@ Detailed Documentation ====================== More detailed documentation is available in the L{Client} class. + """ from __future__ import print_function @@ -48,22 +50,26 @@ from __future__ import print_function import sys import socket import time +import binascii import os +import pickle import re -try: - import cPickle as pickle -except ImportError: - import pickle +import threading +import zlib +import six + -from binascii import crc32 # zlib version is not cross-platform def cmemcache_hash(key): - return((((crc32(key.encode('ASCII')) & 0xffffffff) >> 16) & 0x7fff) or 1) + return ( + (((binascii.crc32(key.encode('ascii')) & 0xffffffff) + >> 16) & 0x7fff) or 1) serverHashFunction = cmemcache_hash + def useOldServerHashFunction(): """Use the old python-memcache server hash function.""" global serverHashFunction - serverHashFunction = crc32 + serverHashFunction = binascii.crc32 try: from zlib import compress, decompress @@ -71,8 +77,11 @@ try: except ImportError: _supports_compress = False # quickly define a decompress just in case we recv compressed data. + def decompress(val): - raise _Error("received compressed data but I don't support compression (import error)") + raise _Error( + "Received compressed data but I don't support " + "compression (import error)") from io import BytesIO try: @@ -91,17 +100,17 @@ valid_key_chars_re = re.compile('[\x21-\x7e\x80-\xff]+$') # Original author: Evan Martin of Danga Interactive -__author__ = "Sean Reifschneider <jafo-memcached@tummy.com>" +__author__ = "Sean Reifschneider <jafo-memcached@tummy.com>" __version__ = "1.53" __copyright__ = "Copyright (C) 2003 Danga Interactive" # http://en.wikipedia.org/wiki/Python_Software_Foundation_License -__license__ = "Python Software Foundation License" +__license__ = "Python Software Foundation License" SERVER_MAX_KEY_LENGTH = 250 -# Storing values larger than 1MB requires recompiling memcached. If you do, -# this value can be changed by doing "memcache.SERVER_MAX_VALUE_LENGTH = N" -# after importing this module. -SERVER_MAX_VALUE_LENGTH = 1024*1024 +# Storing values larger than 1MB requires recompiling memcached. If +# you do, this value can be changed by doing +# "memcache.SERVER_MAX_VALUE_LENGTH = N" after importing this module. +SERVER_MAX_VALUE_LENGTH = 1024 * 1024 class _Error(Exception): @@ -112,58 +121,57 @@ class _ConnectionDeadError(Exception): pass -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # TODO: add the pure-python local implementation - class local(object): - pass - - _DEAD_RETRY = 30 # number of seconds before retrying a dead server. -_SOCKET_TIMEOUT = 3 # number of seconds before sockets timeout. +_SOCKET_TIMEOUT = 3 # number of seconds before sockets timeout. -class Client(local): - """ - Object representing a pool of memcache servers. +class Client(threading.local): + """Object representing a pool of memcache servers. See L{memcache} for an overview. In all cases where a key is used, the key can be either: 1. A simple hashable type (string, integer, etc.). - 2. A tuple of C{(hashvalue, key)}. This is useful if you want to avoid - making this module calculate a hash value. You may prefer, for - example, to keep all of a given user's objects on the same memcache - server, so you could use the user's unique id as the hash value. + 2. A tuple of C{(hashvalue, key)}. This is useful if you want + to avoid making this module calculate a hash value. You may + prefer, for example, to keep all of a given user's objects on + the same memcache server, so you could use the user's unique + id as the hash value. + - @group Setup: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog + @group Setup: __init__, set_servers, forget_dead_hosts, + disconnect_all, debuglog @group Insertion: set, add, replace, set_multi @group Retrieval: get, get_multi @group Integers: incr, decr @group Removal: delete, delete_multi - @sort: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog,\ - set, set_multi, add, replace, get, get_multi, incr, decr, delete, delete_multi + @sort: __init__, set_servers, forget_dead_hosts, disconnect_all, + debuglog,\ set, set_multi, add, replace, get, get_multi, + incr, decr, delete, delete_multi """ - _FLAG_PICKLE = 1<<0 - _FLAG_INTEGER = 1<<1 - _FLAG_LONG = 1<<2 - _FLAG_COMPRESSED = 1<<3 + _FLAG_PICKLE = 1 << 0 + _FLAG_INTEGER = 1 << 1 + _FLAG_LONG = 1 << 2 + _FLAG_COMPRESSED = 1 << 3 _SERVER_RETRIES = 10 # how many times to try finding a free server. # exceptions for Client class MemcachedKeyError(Exception): pass + class MemcachedKeyLengthError(MemcachedKeyError): pass + class MemcachedKeyCharacterError(MemcachedKeyError): pass + class MemcachedKeyNoneError(MemcachedKeyError): pass + class MemcachedKeyTypeError(MemcachedKeyError): pass + class MemcachedStringEncodingError(Exception): pass @@ -172,43 +180,51 @@ class Client(local): pload=None, pid=None, server_max_key_length=None, server_max_value_length=None, dead_retry=_DEAD_RETRY, socket_timeout=_SOCKET_TIMEOUT, - cache_cas = False, flush_on_reconnect=0, check_keys=True): - """ - Create a new Client object with the given list of servers. + cache_cas=False, flush_on_reconnect=0, check_keys=True): + """Create a new Client object with the given list of servers. @param servers: C{servers} is passed to L{set_servers}. - @param debug: whether to display error messages when a server can't be - contacted. - @param pickleProtocol: number to mandate protocol used by (c)Pickle. - @param pickler: optional override of default Pickler to allow subclassing. - @param unpickler: optional override of default Unpickler to allow subclassing. - @param pload: optional persistent_load function to call on pickle loading. - Useful for cPickle since subclassing isn't allowed. - @param pid: optional persistent_id function to call on pickle storing. - Useful for cPickle since subclassing isn't allowed. - @param dead_retry: number of seconds before retrying a blacklisted - server. Default to 30 s. - @param socket_timeout: timeout in seconds for all calls to a server. Defaults - to 3 seconds. - @param cache_cas: (default False) If true, cas operations will be - cached. WARNING: This cache is not expired internally, if you have - a long-running process you will need to expire it manually via - client.reset_cas(), or the cache can grow unlimited. + @param debug: whether to display error messages when a server + can't be contacted. + @param pickleProtocol: number to mandate protocol used by + (c)Pickle. + @param pickler: optional override of default Pickler to allow + subclassing. + @param unpickler: optional override of default Unpickler to + allow subclassing. + @param pload: optional persistent_load function to call on + pickle loading. Useful for cPickle since subclassing isn't + allowed. + @param pid: optional persistent_id function to call on pickle + storing. Useful for cPickle since subclassing isn't allowed. + @param dead_retry: number of seconds before retrying a + blacklisted server. Default to 30 s. + @param socket_timeout: timeout in seconds for all calls to a + server. Defaults to 3 seconds. + @param cache_cas: (default False) If true, cas operations will + be cached. WARNING: This cache is not expired internally, if + you have a long-running process you will need to expire it + manually via client.reset_cas(), or the cache can grow + unlimited. @param server_max_key_length: (default SERVER_MAX_KEY_LENGTH) Data that is larger than this will not be sent to the server. - @param server_max_value_length: (default SERVER_MAX_VALUE_LENGTH) - Data that is larger than this will not be sent to the server. - @param flush_on_reconnect: optional flag which prevents a scenario that - can cause stale data to be read: If there's more than one memcached - server and the connection to one is interrupted, keys that mapped to - that server will get reassigned to another. If the first server comes - back, those keys will map to it again. If it still has its data, get()s - can read stale data that was overwritten on another server. This flag - is off by default for backwards compatibility. - @param check_keys: (default True) If True, the key is checked to - ensure it is the correct length and composed of the right characters. + @param server_max_value_length: (default + SERVER_MAX_VALUE_LENGTH) Data that is larger than this will + not be sent to the server. + @param flush_on_reconnect: optional flag which prevents a + scenario that can cause stale data to be read: If there's more + than one memcached server and the connection to one is + interrupted, keys that mapped to that server will get + reassigned to another. If the first server comes back, those + keys will map to it again. If it still has its data, get()s + can read stale data that was overwritten on another + server. This flag is off by default for backwards + compatibility. + @param check_keys: (default True) If True, the key is checked + to ensure it is the correct length and composed of the right + characters. """ - local.__init__(self) + super(Client, self).__init__() self.debug = debug self.dead_retry = dead_retry self.socket_timeout = socket_timeout @@ -235,67 +251,71 @@ class Client(local): # figure out the pickler style file = BytesIO() try: - pickler = self.pickler(file, protocol = self.pickleProtocol) + pickler = self.pickler(file, protocol=self.pickleProtocol) self.picklerIsKeyword = True except TypeError: self.picklerIsKeyword = False def reset_cas(self): - """ - Reset the cas cache. This is only used if the Client() object - was created with "cache_cas=True". If used, this cache does not - expire internally, so it can grow unbounded if you do not clear it + """Reset the cas cache. + + This is only used if the Client() object was created with + "cache_cas=True". If used, this cache does not expire + internally, so it can grow unbounded if you do not clear it yourself. """ self.cas_ids = {} - def set_servers(self, servers): - """ - Set the pool of servers used by this client. + """Set the pool of servers used by this client. @param servers: an array of servers. Servers can be passed in two forms: - 1. Strings of the form C{"host:port"}, which implies a default weight of 1. - 2. Tuples of the form C{("host:port", weight)}, where C{weight} is - an integer weight value. + 1. Strings of the form C{"host:port"}, which implies a + default weight of 1. + 2. Tuples of the form C{("host:port", weight)}, where + C{weight} is an integer weight value. + """ self.servers = [_Host(s, self.debug, dead_retry=self.dead_retry, - socket_timeout=self.socket_timeout, - flush_on_reconnect=self.flush_on_reconnect) - for s in servers] + socket_timeout=self.socket_timeout, + flush_on_reconnect=self.flush_on_reconnect) + for s in servers] self._init_buckets() - def get_stats(self, stat_args = None): - '''Get statistics from each of the servers. + def get_stats(self, stat_args=None): + """Get statistics from each of the servers. @param stat_args: Additional arguments to pass to the memcache "stats" command. - @return: A list of tuples ( server_identifier, stats_dictionary ). - The dictionary contains a number of name/value pairs specifying - the name of the status field and the string value associated with - it. The values are not converted from strings. - ''' + @return: A list of tuples ( server_identifier, + stats_dictionary ). The dictionary contains a number of + name/value pairs specifying the name of the status field + and the string value associated with it. The values are + not converted from strings. + """ data = [] for s in self.servers: - if not s.connect(): continue + if not s.connect(): + continue if s.family == socket.AF_INET: - name = '%s:%s (%s)' % ( s.ip, s.port, s.weight ) + name = '%s:%s (%s)' % (s.ip, s.port, s.weight) elif s.family == socket.AF_INET6: - name = '[%s]:%s (%s)' % ( s.ip, s.port, s.weight ) + name = '[%s]:%s (%s)' % (s.ip, s.port, s.weight) else: - name = 'unix:%s (%s)' % ( s.address, s.weight ) + name = 'unix:%s (%s)' % (s.address, s.weight) if not stat_args: s.send_cmd('stats') else: s.send_cmd('stats ' + stat_args) serverData = {} - data.append(( name, serverData )) + data.append((name, serverData)) readline = s.readline while 1: line = readline() - if not line or line.strip() == 'END': break + if not line or line.strip() == 'END': + break stats = line.split(' ', 2) serverData[stats[1]] = stats[2] @@ -304,24 +324,26 @@ class Client(local): def get_slabs(self): data = [] for s in self.servers: - if not s.connect(): continue + if not s.connect(): + continue if s.family == socket.AF_INET: - name = '%s:%s (%s)' % ( s.ip, s.port, s.weight ) + name = '%s:%s (%s)' % (s.ip, s.port, s.weight) elif s.family == socket.AF_INET6: - name = '[%s]:%s (%s)' % ( s.ip, s.port, s.weight ) + name = '[%s]:%s (%s)' % (s.ip, s.port, s.weight) else: - name = 'unix:%s (%s)' % ( s.address, s.weight ) + name = 'unix:%s (%s)' % (s.address, s.weight) serverData = {} - data.append(( name, serverData )) + data.append((name, serverData)) s.send_cmd('stats items') readline = s.readline while 1: line = readline() - if not line or line.strip() == 'END': break + if not line or line.strip() == 'END': + break item = line.split(' ', 2) - #0 = STAT, 1 = ITEM, 2 = Value + # 0 = STAT, 1 = ITEM, 2 = Value slab = item[1].split(':', 2) - #0 = items, 1 = Slab #, 2 = Name + # 0 = items, 1 = Slab #, 2 = Name if slab[1] not in serverData: serverData[slab[1]] = {} serverData[slab[1]][slab[2]] = item[2] @@ -330,7 +352,8 @@ class Client(local): def flush_all(self): """Expire all data in memcache servers that are reachable.""" for s in self.servers: - if not s.connect(): continue + if not s.connect(): + continue s.flush() def debuglog(self, str): @@ -344,9 +367,7 @@ class Client(local): self.stats[func] += 1 def forget_dead_hosts(self): - """ - Reset every host in the pool to an "alive" state. - """ + """Reset every host in the pool to an "alive" state.""" for s in self.servers: s.deaduntil = 0 @@ -368,7 +389,7 @@ class Client(local): for i in range(Client._SERVER_RETRIES): server = self.buckets[serverhash % len(self.buckets)] if server.connect(): - #print("(using server %s)" % server,) + # print("(using server %s)" % server,) return server, key serverhash = serverHashFunction(str(serverhash) + str(i)) return None, None @@ -378,54 +399,54 @@ class Client(local): s.close_socket() def delete_multi(self, keys, time=0, key_prefix=''): - ''' - Delete multiple keys in the memcache doing just one query. + """Delete multiple keys in the memcache doing just one query. - >>> notset_keys = mc.set_multi({'key1' : 'val1', 'key2' : 'val2'}) - >>> mc.get_multi(['key1', 'key2']) == {'key1' : 'val1', 'key2' : 'val2'} + >>> notset_keys = mc.set_multi({'a1' : 'val1', 'a2' : 'val2'}) + >>> mc.get_multi(['a1', 'a2']) == {'a1' : 'val1','a2' : 'val2'} 1 >>> mc.delete_multi(['key1', 'key2']) 1 >>> mc.get_multi(['key1', 'key2']) == {} 1 - - This method is recommended over iterated regular L{delete}s as it reduces total latency, since - your app doesn't have to wait for each round-trip of L{delete} before sending - the next one. + This method is recommended over iterated regular L{delete}s as + it reduces total latency, since your app doesn't have to wait + for each round-trip of L{delete} before sending the next one. @param keys: An iterable of keys to clear - @param time: number of seconds any subsequent set / update commands should fail. Defaults to 0 for no delay. - @param key_prefix: Optional string to prepend to each key when sending to memcache. - See docs for L{get_multi} and L{set_multi}. - + @param time: number of seconds any subsequent set / update + commands should fail. Defaults to 0 for no delay. + @param key_prefix: Optional string to prepend to each key when + sending to memcache. See docs for L{get_multi} and + L{set_multi}. @return: 1 if no failure in communication with any memcacheds. @rtype: int - - ''' + """ self._statlog('delete_multi') - server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(keys, key_prefix) + server_keys, prefixed_to_orig_key = self._map_and_prefix_keys( + keys, key_prefix) # send out all requests on each server before reading anything dead_servers = [] rc = 1 - for server in server_keys: + for server in six.iterkeys(server_keys): bigcmd = [] write = bigcmd.append - if time != None: - for key in server_keys[server]: # These are mangled keys - write("delete %s %d\r\n" % (key, time)) + if time is not None: + for key in server_keys[server]: # These are mangled keys + write("delete %s %d\r\n" % (key, time)) else: - for key in server_keys[server]: # These are mangled keys - write("delete %s\r\n" % key) + for key in server_keys[server]: # These are mangled keys + write("delete %s\r\n" % key) try: server.send_cmds(''.join(bigcmd)) except socket.error as msg: rc = 0 - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) dead_servers.append(server) @@ -433,12 +454,13 @@ class Client(local): for server in dead_servers: del server_keys[server] - for server, keys in server_keys.iteritems(): + for server, keys in six.iteritems(server_keys): try: for key in keys: server.expect("DELETED") except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) rc = 0 return rc @@ -451,7 +473,7 @@ class Client(local): should fail. Defaults to None for no delay. @rtype: int ''' - return self._deletetouch(['DELETED','NOT_FOUND'], "delete", key, time) + return self._deletetouch(['DELETED', 'NOT_FOUND'], "delete", key, time) def touch(self, key, time=0): '''Updates the expiration time of a key in memcache. @@ -473,7 +495,7 @@ class Client(local): if not server: return 0 self._statlog(cmd) - if time != None and time != 0: + if time is not None and time != 0: cmd = "%s %s %d" % (cmd, key, time) else: cmd = "%s %s" % (cmd, key) @@ -481,23 +503,25 @@ class Client(local): try: server.send_cmd(cmd) line = server.readline() - if line and line.strip() in expected: return 1 - self.debuglog('%s expected %s, got: %s' - % (cmd, ' or '.join(expected), repr(line))) + if line and line.strip() in expected: + return 1 + self.debuglog('%s expected %s, got: %r' + % (cmd, ' or '.join(expected), line)) except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return 0 def incr(self, key, delta=1): - """ - Sends a command to the server to atomically increment the value - for C{key} by C{delta}, or by 1 if C{delta} is unspecified. - Returns None if C{key} doesn't exist on server, otherwise it - returns the new value after incrementing. + """Sends a command to the server to atomically increment the + value for C{key} by C{delta}, or by 1 if C{delta} is + unspecified. Returns None if C{key} doesn't exist on server, + otherwise it returns the new value after incrementing. - Note that the value for C{key} must already exist in the memcache, - and it must be the string representation of an integer. + Note that the value for C{key} must already exist in the + memcache, and it must be the string representation of an + integer. >>> mc.set("counter", "20") # returns 1, indicating success 1 @@ -506,22 +530,25 @@ class Client(local): >>> mc.incr("counter") 22 - Overflow on server is not checked. Be aware of values approaching - 2**32. See L{decr}. + Overflow on server is not checked. Be aware of values + approaching 2**32. See L{decr}. + + @param delta: Integer amount to increment by (should be zero + or greater). - @param delta: Integer amount to increment by (should be zero or greater). @return: New value after incrementing. @rtype: int """ return self._incrdecr("incr", key, delta) def decr(self, key, delta=1): - """ - Like L{incr}, but decrements. Unlike L{incr}, underflow is checked and - new values are capped at 0. If server value is 1, a decrement of 2 - returns 0, not -1. + """Like L{incr}, but decrements. Unlike L{incr}, underflow is + checked and new values are capped at 0. If server value is 1, + a decrement of 2 returns 0, not -1. + + @param delta: Integer amount to decrement by (should be zero + or greater). - @param delta: Integer amount to decrement by (should be zero or greater). @return: New value after decrementing or None on error. @rtype: int """ @@ -538,18 +565,20 @@ class Client(local): try: server.send_cmd(cmd) line = server.readline() - if line == None or line.strip() =='NOT_FOUND': return None + if line is None or line.strip() == 'NOT_FOUND': + return None return int(line) except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return None - def add(self, key, val, time = 0, min_compress_len = 0): - ''' - Add new key with value. + def add(self, key, val, time=0, min_compress_len=0): + '''Add new key with value. - Like L{set}, but only stores in memcache if the key doesn't already exist. + Like L{set}, but only stores in memcache if the key doesn't + already exist. @return: Nonzero on success. @rtype: int @@ -593,67 +622,73 @@ class Client(local): '''Unconditionally sets a key to a given value in the memcache. The C{key} can optionally be an tuple, with the first element - being the server hash value and the second being the key. - If you want to avoid making this module calculate a hash value. - You may prefer, for example, to keep all of a given user's objects - on the same memcache server, so you could use the user's unique - id as the hash value. + being the server hash value and the second being the key. If + you want to avoid making this module calculate a hash value. + You may prefer, for example, to keep all of a given user's + objects on the same memcache server, so you could use the + user's unique id as the hash value. @return: Nonzero on success. @rtype: int - @param time: Tells memcached the time which this value should expire, either - as a delta number of seconds, or an absolute unix time-since-the-epoch - value. See the memcached protocol docs section "Storage Commands" - for more info on <exptime>. We default to 0 == cache forever. - @param min_compress_len: The threshold length to kick in auto-compression - of the value using the zlib.compress() routine. If the value being cached is - a string, then the length of the string is measured, else if the value is an - object, then the length of the pickle result is measured. If the resulting - attempt at compression yeilds a larger string than the input, then it is - discarded. For backwards compatability, this parameter defaults to 0, - indicating don't ever try to compress. + + @param time: Tells memcached the time which this value should + expire, either as a delta number of seconds, or an absolute + unix time-since-the-epoch value. See the memcached protocol + docs section "Storage Commands" for more info on <exptime>. We + default to 0 == cache forever. + + @param min_compress_len: The threshold length to kick in + auto-compression of the value using the zlib.compress() + routine. If the value being cached is a string, then the + length of the string is measured, else if the value is an + object, then the length of the pickle result is measured. If + the resulting attempt at compression yeilds a larger string + than the input, then it is discarded. For backwards + compatability, this parameter defaults to 0, indicating don't + ever try to compress. + ''' return self._set("set", key, val, time, min_compress_len) - def cas(self, key, val, time=0, min_compress_len=0): '''Sets a key to a given value in the memcache if it hasn't been altered since last fetched. (See L{gets}). The C{key} can optionally be an tuple, with the first element - being the server hash value and the second being the key. - If you want to avoid making this module calculate a hash value. - You may prefer, for example, to keep all of a given user's objects - on the same memcache server, so you could use the user's unique - id as the hash value. + being the server hash value and the second being the key. If + you want to avoid making this module calculate a hash value. + You may prefer, for example, to keep all of a given user's + objects on the same memcache server, so you could use the + user's unique id as the hash value. @return: Nonzero on success. @rtype: int - @param time: Tells memcached the time which this value should expire, - either as a delta number of seconds, or an absolute unix - time-since-the-epoch value. See the memcached protocol docs section - "Storage Commands" for more info on <exptime>. We default to - 0 == cache forever. + + @param time: Tells memcached the time which this value should + expire, either as a delta number of seconds, or an absolute + unix time-since-the-epoch value. See the memcached protocol + docs section "Storage Commands" for more info on <exptime>. We + default to 0 == cache forever. + @param min_compress_len: The threshold length to kick in - auto-compression of the value using the zlib.compress() routine. If - the value being cached is a string, then the length of the string is - measured, else if the value is an object, then the length of the - pickle result is measured. If the resulting attempt at compression - yeilds a larger string than the input, then it is discarded. For - backwards compatability, this parameter defaults to 0, indicating - don't ever try to compress. + auto-compression of the value using the zlib.compress() + routine. If the value being cached is a string, then the + length of the string is measured, else if the value is an + object, then the length of the pickle result is measured. If + the resulting attempt at compression yeilds a larger string + than the input, then it is discarded. For backwards + compatability, this parameter defaults to 0, indicating don't + ever try to compress. ''' return self._set("cas", key, val, time, min_compress_len) - def _map_and_prefix_keys(self, key_iterable, key_prefix): - """Compute the mapping of server (_Host instance) -> list of keys to stuff onto that server, as well as the mapping of - prefixed key -> original key. - - + """Compute the mapping of server (_Host instance) -> list of keys to + stuff onto that server, as well as the mapping of prefixed key + -> original key. """ # Check it just once ... - key_extra_len=len(key_prefix) + key_extra_len = len(key_prefix) if key_prefix and self.do_check_key: self.check_key(key_prefix) @@ -664,12 +699,18 @@ class Client(local): # build up a list for each server of all the keys we want. for orig_key in key_iterable: if isinstance(orig_key, tuple): - # Tuple of hashvalue, key ala _get_server(). Caller is essentially telling us what server to stuff this on. + # Tuple of hashvalue, key ala _get_server(). Caller is + # essentially telling us what server to stuff this on. # Ensure call to _get_server gets a Tuple as well. str_orig_key = str(orig_key[1]) - server, key = self._get_server((orig_key[0], key_prefix + str_orig_key)) # Gotta pre-mangle key before hashing to a server. Returns the mangled key. + + # Gotta pre-mangle key before hashing to a + # server. Returns the mangled key. + server, key = self._get_server( + (orig_key[0], key_prefix + str_orig_key)) else: - str_orig_key = str(orig_key) # set_multi supports int / long keys. + # set_multi supports int / long keys. + str_orig_key = str(orig_key) server, key = self._get_server(key_prefix + str_orig_key) # Now check to make sure key length is proper ... @@ -687,34 +728,39 @@ class Client(local): return (server_keys, prefixed_to_orig_key) def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0): - ''' - Sets multiple keys in the memcache doing just one query. + '''Sets multiple keys in the memcache doing just one query. >>> notset_keys = mc.set_multi({'key1' : 'val1', 'key2' : 'val2'}) - >>> mc.get_multi(['key1', 'key2']) == {'key1' : 'val1', 'key2' : 'val2'} + >>> mc.get_multi(['key1', 'key2']) == {'key1' : 'val1', + ... 'key2' : 'val2'} 1 - This method is recommended over regular L{set} as it lowers the - number of total packets flying around your network, reducing - total latency, since your app doesn't have to wait for each - round-trip of L{set} before sending the next one. + This method is recommended over regular L{set} as it lowers + the number of total packets flying around your network, + reducing total latency, since your app doesn't have to wait + for each round-trip of L{set} before sending the next one. @param mapping: A dict of key/value pairs to set. + @param time: Tells memcached the time which this value should - expire, either as a delta number of seconds, or an absolute - unix time-since-the-epoch value. See the memcached protocol - docs section "Storage Commands" for more info on <exptime>. We - default to 0 == cache forever. - @param key_prefix: Optional string to prepend to each key when + expire, either as a delta number of seconds, or an + absolute unix time-since-the-epoch value. See the + memcached protocol docs section "Storage Commands" for + more info on <exptime>. We default to 0 == cache forever. + + @param key_prefix: Optional string to prepend to each key when sending to memcache. Allows you to efficiently stuff these keys into a pseudo-namespace in memcache: >>> notset_keys = mc.set_multi( - ... {'key1' : 'val1', 'key2' : 'val2'}, key_prefix='subspace_') + ... {'key1' : 'val1', 'key2' : 'val2'}, + ... key_prefix='subspace_') >>> len(notset_keys) == 0 True - >>> mc.get_multi(['subspace_key1', 'subspace_key2']) == {'subspace_key1' : 'val1', 'subspace_key2' : 'val2'} + >>> mc.get_multi(['subspace_key1', + ... 'subspace_key2']) == {'subspace_key1': 'val1', + ... 'subspace_key2' : 'val2'} True Causes key 'subspace_key1' and 'subspace_key2' to be @@ -725,44 +771,49 @@ class Client(local): @param min_compress_len: The threshold length to kick in auto-compression of the value using the zlib.compress() - routine. If the value being cached is a string, then - the length of the string is measured, else if the value - is an object, then the length of the pickle result is - measured. If the resulting attempt at compression yeilds - a larger string than the input, then it is discarded. For + routine. If the value being cached is a string, then the + length of the string is measured, else if the value is an + object, then the length of the pickle result is + measured. If the resulting attempt at compression yeilds a + larger string than the input, then it is discarded. For backwards compatability, this parameter defaults to 0, indicating don't ever try to compress. - @return: List of keys which failed to be stored [ memcache out of - memory, etc. ]. - @rtype: list - ''' + @return: List of keys which failed to be stored [ memcache out + of memory, etc. ]. + @rtype: list + ''' self._statlog('set_multi') server_keys, prefixed_to_orig_key = self._map_and_prefix_keys( - mapping, key_prefix) + six.iterkeys(mapping), key_prefix) # send out all requests on each server before reading anything dead_servers = [] - notstored = [] # original keys. + notstored = [] # original keys. - for server in server_keys: + for server in six.iterkeys(server_keys): bigcmd = [] write = bigcmd.append try: - for key in server_keys[server]: # These are mangled keys + for key in server_keys[server]: # These are mangled keys store_info = self._val_to_store_info( - mapping[prefixed_to_orig_key[key]], - min_compress_len) + mapping[prefixed_to_orig_key[key]], + min_compress_len) if store_info: - write("set %s %d %d %d\r\n%s\r\n" % (key, store_info[0], - time, store_info[1], store_info[2])) + msg = "set %s %d %d %d\r\n%s\r\n" + write(msg % (key, + store_info[0], + time, + store_info[1], + store_info[2])) else: notstored.append(prefixed_to_orig_key[key]) server.send_cmds(''.join(bigcmd)) except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) dead_servers.append(server) @@ -771,23 +822,28 @@ class Client(local): del server_keys[server] # short-circuit if there are no servers, just return all keys - if not server_keys: return(mapping.keys()) + if not server_keys: + return(mapping.keys()) - for server, keys in server_keys.items(): + for server, keys in six.iteritems(server_keys): try: for key in keys: if server.readline() == 'STORED': continue else: - notstored.append(prefixed_to_orig_key[key]) #un-mangle. + # un-mangle. + notstored.append(prefixed_to_orig_key[key]) except (_Error, socket.error) as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return notstored def _val_to_store_info(self, val, min_compress_len): - """ - Transform val to a storable representation, returning a tuple of the flags, the length of the new value, and the new value itself. + """Transform val to a storable representation. + + Returns a tuple of the flags, the length of the new value, and + the new value itself. """ flags = 0 if isinstance(val, str): @@ -806,7 +862,7 @@ class Client(local): flags |= Client._FLAG_PICKLE file = BytesIO() if self.picklerIsKeyword: - pickler = self.pickler(file, protocol = self.pickleProtocol) + pickler = self.pickler(file, protocol=self.pickleProtocol) else: pickler = self.pickler(file, self.pickleProtocol) if self.persistent_id: @@ -815,10 +871,11 @@ class Client(local): val = file.getvalue() lv = len(val) - # We should try to compress if min_compress_len > 0 and we could - # import zlib and this string is longer than our min threshold. - if min_compress_len and _supports_compress and lv > min_compress_len: - comp_val = compress(val) + # We should try to compress if min_compress_len > 0 and we + # could import zlib and this string is longer than our min + # threshold. + if min_compress_len and lv > min_compress_len: + comp_val = zlib.compress(val) # Only retain the result if the compression result is smaller # than the original. if len(comp_val) < lv: @@ -827,11 +884,12 @@ class Client(local): # silently do not store if value length exceeds maximum if self.server_max_value_length != 0 and \ - len(val) > self.server_max_value_length: return(0) + len(val) > self.server_max_value_length: + return(0) return (flags, len(val), val) - def _set(self, cmd, key, val, time, min_compress_len = 0): + def _set(self, cmd, key, val, time, min_compress_len=0): if self.do_check_key: self.check_key(key) server, key = self._get_server(key) @@ -842,24 +900,28 @@ class Client(local): self._statlog(cmd) store_info = self._val_to_store_info(val, min_compress_len) - if not store_info: return(0) + if not store_info: + return(0) if cmd == 'cas': if key not in self.cas_ids: return self._set('set', key, val, time, min_compress_len) fullcmd = "%s %s %d %d %d %d\r\n%s" % ( - cmd, key, store_info[0], time, store_info[1], - self.cas_ids[key], store_info[2]) + cmd, key, store_info[0], time, store_info[1], + self.cas_ids[key], store_info[2]) else: fullcmd = "%s %s %d %d %d\r\n%s" % ( - cmd, key, store_info[0], time, store_info[1], store_info[2]) + cmd, key, store_info[0], + time, store_info[1], store_info[2] + ) try: server.send_cmd(fullcmd) return(server.expect("STORED", raise_exception=True) - == "STORED") + == "STORED") except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return 0 @@ -889,13 +951,15 @@ class Client(local): rkey = flags = rlen = cas_id = None if cmd == 'gets': - rkey, flags, rlen, cas_id, = self._expect_cas_value(server, - raise_exception=True) + rkey, flags, rlen, cas_id, = self._expect_cas_value( + server, raise_exception=True + ) if rkey and self.cache_cas: self.cas_ids[rkey] = cas_id else: - rkey, flags, rlen, = self._expectvalue(server, - raise_exception=True) + rkey, flags, rlen, = self._expectvalue( + server, raise_exception=True + ) if not rkey: return None @@ -904,7 +968,8 @@ class Client(local): finally: server.expect("END", raise_exception=True) except (_Error, socket.error) as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return None @@ -937,54 +1002,73 @@ class Client(local): return self._get('gets', key) def get_multi(self, keys, key_prefix=''): - ''' - Retrieves multiple keys from the memcache doing just one query. + '''Retrieves multiple keys from the memcache doing just one query. >>> success = mc.set("foo", "bar") >>> success = mc.set("baz", 42) - >>> mc.get_multi(["foo", "baz", "foobar"]) == {"foo": "bar", "baz": 42} + >>> mc.get_multi(["foo", "baz", "foobar"]) == { + ... "foo": "bar", "baz": 42 + ... } 1 >>> mc.set_multi({'k1' : 1, 'k2' : 2}, key_prefix='pfx_') == [] 1 - This looks up keys 'pfx_k1', 'pfx_k2', ... . Returned dict will just have unprefixed keys 'k1', 'k2'. - >>> mc.get_multi(['k1', 'k2', 'nonexist'], key_prefix='pfx_') == {'k1' : 1, 'k2' : 2} + This looks up keys 'pfx_k1', 'pfx_k2', ... . Returned dict + will just have unprefixed keys 'k1', 'k2'. + + >>> mc.get_multi(['k1', 'k2', 'nonexist'], + ... key_prefix='pfx_') == {'k1' : 1, 'k2' : 2} 1 - get_mult [ and L{set_multi} ] can take str()-ables like ints / longs as keys too. Such as your db pri key fields. - They're rotored through str() before being passed off to memcache, with or without the use of a key_prefix. - In this mode, the key_prefix could be a table name, and the key itself a db primary key number. + get_mult [ and L{set_multi} ] can take str()-ables like ints / + longs as keys too. Such as your db pri key fields. They're + rotored through str() before being passed off to memcache, + with or without the use of a key_prefix. In this mode, the + key_prefix could be a table name, and the key itself a db + primary key number. - >>> mc.set_multi({42: 'douglass adams', 46 : 'and 2 just ahead of me'}, key_prefix='numkeys_') == [] + >>> mc.set_multi({42: 'douglass adams', + ... 46: 'and 2 just ahead of me'}, + ... key_prefix='numkeys_') == [] 1 - >>> mc.get_multi([46, 42], key_prefix='numkeys_') == {42: 'douglass adams', 46 : 'and 2 just ahead of me'} + >>> mc.get_multi([46, 42], key_prefix='numkeys_') == { + ... 42: 'douglass adams', + ... 46: 'and 2 just ahead of me' + ... } 1 - This method is recommended over regular L{get} as it lowers the number of - total packets flying around your network, reducing total latency, since - your app doesn't have to wait for each round-trip of L{get} before sending - the next one. + This method is recommended over regular L{get} as it lowers + the number of total packets flying around your network, + reducing total latency, since your app doesn't have to wait + for each round-trip of L{get} before sending the next one. See also L{set_multi}. @param keys: An array of keys. - @param key_prefix: A string to prefix each key when we communicate with memcache. - Facilitates pseudo-namespaces within memcache. Returned dictionary keys will not have this prefix. - @return: A dictionary of key/value pairs that were available. If key_prefix was provided, the keys in the retured dictionary will not have it present. + @param key_prefix: A string to prefix each key when we + communicate with memcache. Facilitates pseudo-namespaces + within memcache. Returned dictionary keys will not have this + prefix. + + @return: A dictionary of key/value pairs that were + available. If key_prefix was provided, the keys in the retured + dictionary will not have it present. ''' self._statlog('get_multi') - server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(keys, key_prefix) + server_keys, prefixed_to_orig_key = self._map_and_prefix_keys( + keys, key_prefix) # send out all requests on each server before reading anything dead_servers = [] - for server in server_keys: + for server in six.iterkeys(server_keys): try: server.send_cmd("get %s" % " ".join(server_keys[server])) except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) dead_servers.append(server) @@ -993,7 +1077,7 @@ class Client(local): del server_keys[server] retvals = {} - for server in server_keys: + for server in six.iterkeys(server_keys): try: line = server.readline() while line and line != 'END': @@ -1001,10 +1085,12 @@ class Client(local): # Bo Yang reports that this can sometimes be None if rkey is not None: val = self._recv_value(server, flags, rlen) - retvals[prefixed_to_orig_key[rkey]] = val # un-prefix returned key. + # un-prefix returned key. + retvals[prefixed_to_orig_key[rkey]] = val line = server.readline() except (_Error, socket.error) as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] server.mark_dead(msg) return retvals @@ -1031,19 +1117,19 @@ class Client(local): return (None, None, None) def _recv_value(self, server, flags, rlen): - rlen += 2 # include \r\n + rlen += 2 # include \r\n buf = server.recv(rlen) if len(buf) != rlen: raise _Error("received %d bytes when expecting %d" - % (len(buf), rlen)) + % (len(buf), rlen)) if len(buf) == rlen: buf = buf[:-2] # strip \r\n if flags & Client._FLAG_COMPRESSED: - buf = decompress(buf) + buf = zlib.decompress(buf) - if flags == 0 or flags == Client._FLAG_COMPRESSED: + if flags == 0 or flags == Client._FLAG_COMPRESSED: # Either a bare string or a compressed string now decompressed... val = buf elif flags & Client._FLAG_INTEGER: @@ -1075,7 +1161,8 @@ class Client(local): Is not a string (Raises MemcachedKeyError) Is None (Raises MemcachedKeyError) """ - if isinstance(key, tuple): key = key[1] + if isinstance(key, tuple): + key = key[1] if not key: raise Client.MemcachedKeyNoneError("Key is None") @@ -1090,12 +1177,14 @@ class Client(local): if isinstance(key, _str_cls): if self.server_max_key_length != 0 and \ - len(key) + key_extra_len > self.server_max_key_length: - raise Client.MemcachedKeyLengthError("Key length is > %s" - % self.server_max_key_length) + len(key) + key_extra_len > self.server_max_key_length: + raise Client.MemcachedKeyLengthError( + "Key length is > %s" % self.server_max_key_length + ) if not valid_key_chars_re.match(key): raise Client.MemcachedKeyCharacterError( - "Control characters not allowed") + "Control characters not allowed") + class _Host(object): @@ -1114,11 +1203,12 @@ class _Host(object): m = re.match(r'^(?P<proto>unix):(?P<path>.*)$', host) if not m: m = re.match(r'^(?P<proto>inet6):' - r'\[(?P<host>[^\[\]]+)\](:(?P<port>[0-9]+))?$', host) + r'\[(?P<host>[^\[\]]+)\](:(?P<port>[0-9]+))?$', host) if not m: m = re.match(r'^(?P<proto>inet):' - r'(?P<host>[^:]+)(:(?P<port>[0-9]+))?$', host) - if not m: m = re.match(r'^(?P<host>[^:]+)(:(?P<port>[0-9]+))?$', host) + r'(?P<host>[^:]+)(:(?P<port>[0-9]+))?$', host) + if not m: + m = re.match(r'^(?P<host>[^:]+)(:(?P<port>[0-9]+))?$', host) if not m: raise ValueError('Unable to parse connection string: "%s"' % host) @@ -1130,12 +1220,12 @@ class _Host(object): self.family = socket.AF_INET6 self.ip = hostData['host'] self.port = int(hostData.get('port') or 11211) - self.address = ( self.ip, self.port ) + self.address = (self.ip, self.port) else: self.family = socket.AF_INET self.ip = hostData['host'] self.port = int(hostData.get('port') or 11211) - self.address = ( self.ip, self.port ) + self.address = (self.ip, self.port) self.deaduntil = 0 self.socket = None @@ -1171,14 +1261,16 @@ class _Host(object): if self.socket: return self.socket s = socket.socket(self.family, socket.SOCK_STREAM) - if hasattr(s, 'settimeout'): s.settimeout(self.socket_timeout) + if hasattr(s, 'settimeout'): + s.settimeout(self.socket_timeout) try: s.connect(self.address) except socket.timeout as msg: self.mark_dead("connect: %s" % msg) return None except socket.error as msg: - if isinstance(msg, tuple): msg = msg[1] + if isinstance(msg, tuple): + msg = msg[1] self.mark_dead("connect: %s" % msg) return None self.socket = s @@ -1197,13 +1289,14 @@ class _Host(object): self.socket.sendall(cmd + '\r\n') def send_cmds(self, cmds): - """ cmds already has trailing \r\n's applied """ + """cmds already has trailing \r\n's applied.""" self.socket.sendall(cmds) def readline(self, raise_exception=False): - """Read a line and return it. If "raise_exception" is set, - raise _ConnectionDeadError if the read fails, otherwise return - an empty string. + """Read a line and return it. + + If "raise_exception" is set, raise _ConnectionDeadError if the + read fails, otherwise return an empty string. """ buf = self.buffer if self.socket: @@ -1225,14 +1318,14 @@ class _Host(object): return '' buf += data - self.buffer = buf[index+2:] + self.buffer = buf[index + 2:] return buf[:index] def expect(self, text, raise_exception=False): line = self.readline(raise_exception) if line != text: self.debuglog("while expecting '%s', got unexpected response '%s'" - % (text, line)) + % (text, line)) return line def recv(self, rlen): @@ -1242,8 +1335,8 @@ class _Host(object): foo = self_socket_recv(max(rlen - len(buf), 4096)) buf += foo if not foo: - raise _Error( 'Read %d bytes, expecting %d, ' - 'read returned 0 length bytes' % ( len(buf), rlen )) + raise _Error('Read %d bytes, expecting %d, ' + 'read returned 0 length bytes' % (len(buf), rlen)) self.buffer = buf[rlen:] return buf[:rlen] @@ -1265,7 +1358,8 @@ class _Host(object): def _doctest(): - import doctest, memcache + import doctest + import memcache servers = ["127.0.0.1:11211"] mc = Client(servers, debug=1) globs = {"mc": mc} @@ -1288,6 +1382,7 @@ if __name__ == "__main__": if not isinstance(val, _str_cls): return "%s (%s)" % (val, type(val)) return "%s" % val + def test_setget(key, val): global failures print("Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)), end=" ") @@ -1301,12 +1396,14 @@ if __name__ == "__main__": failures += 1 return 0 - class FooStruct(object): + def __init__(self): self.bar = "baz" + def __str__(self): return "A FooStruct" + def __eq__(self, other): if isinstance(other, FooStruct): return self.bar == other.bar @@ -1422,7 +1519,7 @@ if __name__ == "__main__": import pickle s = pickle.loads('V\\u4f1a\np0\n.') try: - x = mc.set((s*SERVER_MAX_KEY_LENGTH).encode('utf-8'), 1) + x = mc.set((s * SERVER_MAX_KEY_LENGTH).encode('utf-8'), 1) except Client.MemcachedKeyLengthError: print("OK") else: @@ -1446,7 +1543,7 @@ if __name__ == "__main__": print("Testing set_multi() with no memcacheds running", end=" ") mc.disconnect_all() - errors = mc.set_multi({'keyhere' : 'a', 'keythere' : 'b'}) + errors = mc.set_multi({'keyhere': 'a', 'keythere': 'b'}) if errors != []: print("FAIL") failures += 1 @@ -1455,7 +1552,7 @@ if __name__ == "__main__": print("Testing delete_multi() with no memcacheds running", end=" ") mc.disconnect_all() - ret = mc.delete_multi({'keyhere' : 'a', 'keythere' : 'b'}) + ret = mc.delete_multi({'keyhere': 'a', 'keythere': 'b'}) if ret != 1: print("FAIL") failures += 1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ffe2fce --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six @@ -1,10 +1,10 @@ #!/usr/bin/env python -from setuptools import setup -import memcache +from setuptools import setup # noqa + setup(name="python-memcached", - version=memcache.__version__, + version="1.53", description="Pure python memcached client", long_description=open("README.md").read(), author="Evan Martin", @@ -14,6 +14,7 @@ setup(name="python-memcached", url="http://www.tummy.com/Community/software/python-memcached/", download_url="ftp://ftp.tummy.com/pub/python-memcached/", py_modules=["memcache"], + install_requires=open('requirements.txt').read().split(), classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -31,4 +32,3 @@ setup(name="python-memcached", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4" ]) - diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..8f21390 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,3 @@ +nose +coverage +hacking @@ -7,19 +7,17 @@ skipsdist = True usedevelop = True # Customize pip command, add -U to force updates. install_command = pip install -U {opts} {packages} -deps = nose +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = nosetests {posargs} [tox:jenkins] downloadcache = ~/cache/pip [testenv:pep8] -deps = hacking commands = flake8 [testenv:cover] -deps = nose - coverage commands = nosetests --with-coverage {posargs} [flake8] |