🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

python-memcached

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-memcached - pypi Package Compare versions

Comparing version
1.59
to
1.60
+48
PSF.LICENSE
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
Please report any security issues to jafo00@gmail.com
# -*- coding: utf-8 -*-
from __future__ import print_function
import unittest
import zlib
try:
import unittest.mock as mock
except ImportError:
import mock
from memcache import Client, _Host, SERVER_MAX_KEY_LENGTH, SERVER_MAX_VALUE_LENGTH # noqa: H301
from .utils import captured_stderr
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
return 0
class TestMemcache(unittest.TestCase):
def setUp(self):
# TODO(): unix socket server stuff
servers = ["127.0.0.1:11211"]
self.mc = Client(servers, debug=1)
def tearDown(self):
self.mc.flush_all()
self.mc.disconnect_all()
def check_setget(self, key, val, noreply=False):
self.mc.set(key, val, noreply=noreply)
newval = self.mc.get(key)
self.assertEqual(newval, val)
def test_setget(self):
self.check_setget("a_string", "some random string")
self.check_setget("a_string_2", "some random string", noreply=True)
self.check_setget("an_integer", 42)
self.check_setget("an_integer_2", 42, noreply=True)
def test_quit_all(self):
self.mc.quit_all()
def test_delete(self):
self.check_setget("long", int(1 << 30))
result = self.mc.delete("long")
self.assertEqual(result, True)
self.assertEqual(self.mc.get("long"), None)
def test_default(self):
key = "default"
default = object()
result = self.mc.get(key, default=default)
self.assertEqual(result, default)
self.mc.set("default", None)
result = self.mc.get(key, default=default)
self.assertIsNone(result)
self.mc.set("default", 123)
result = self.mc.get(key, default=default)
self.assertEqual(result, 123)
@mock.patch.object(_Host, 'send_cmd')
@mock.patch.object(_Host, 'readline')
def test_touch(self, mock_readline, mock_send_cmd):
with captured_stderr():
self.mc.touch('key')
mock_send_cmd.assert_called_with(b'touch key 0')
def test_get_multi(self):
self.check_setget("gm_a_string", "some random string")
self.check_setget("gm_an_integer", 42)
self.assertEqual(
self.mc.get_multi(["gm_a_string", "gm_an_integer"]),
{"gm_an_integer": 42, "gm_a_string": "some random string"})
def test_get_unknown_value(self):
self.mc.delete("unknown_value")
self.assertEqual(self.mc.get("unknown_value"), None)
def test_setget_foostruct(self):
f = FooStruct()
self.check_setget("foostruct", f)
self.check_setget("foostruct_2", f, noreply=True)
def test_incr(self):
self.check_setget("i_an_integer", 42)
self.assertEqual(self.mc.incr("i_an_integer", 1), 43)
def test_incr_noreply(self):
self.check_setget("i_an_integer_2", 42)
self.assertEqual(self.mc.incr("i_an_integer_2", 1, noreply=True), None)
self.assertEqual(self.mc.get("i_an_integer_2"), 43)
def test_decr(self):
self.check_setget("i_an_integer", 42)
self.assertEqual(self.mc.decr("i_an_integer", 1), 41)
def test_decr_noreply(self):
self.check_setget("i_an_integer_2", 42)
self.assertEqual(self.mc.decr("i_an_integer_2", 1, noreply=True), None)
self.assertEqual(self.mc.get("i_an_integer_2"), 41)
def test_sending_spaces(self):
try:
self.mc.set("this has spaces", 1)
except Client.MemcachedKeyCharacterError as err:
self.assertTrue("characters not allowed" in err.args[0])
else:
self.fail(
"Expected Client.MemcachedKeyCharacterError, nothing raised")
def test_sending_control_characters(self):
try:
self.mc.set("this\x10has\x11control characters\x02", 1)
except Client.MemcachedKeyCharacterError as err:
self.assertTrue("characters not allowed" in err.args[0])
else:
self.fail(
"Expected Client.MemcachedKeyCharacterError, nothing raised")
def test_sending_key_too_long(self):
try:
self.mc.set('a' * SERVER_MAX_KEY_LENGTH + 'a', 1)
except Client.MemcachedKeyLengthError as err:
self.assertTrue("length is >" in err.args[0])
else:
self.fail(
"Expected Client.MemcachedKeyLengthError, nothing raised")
# These should work.
self.mc.set('a' * SERVER_MAX_KEY_LENGTH, 1)
self.mc.set('a' * SERVER_MAX_KEY_LENGTH, 1, noreply=True)
def test_setget_boolean(self):
"""GitHub issue #75. Set/get with boolean values."""
self.check_setget("bool", True)
def test_unicode_key(self):
s = u'\u4f1a'
maxlen = SERVER_MAX_KEY_LENGTH // len(s.encode('utf-8'))
key = s * maxlen
self.mc.set(key, 5)
value = self.mc.get(key)
self.assertEqual(value, 5)
def test_unicode_value(self):
key = 'key'
value = u'Iñtërnâtiônàlizætiøn2'
self.mc.set(key, value)
cached_value = self.mc.get(key)
self.assertEqual(value, cached_value)
def test_binary_string(self):
value = 'value_to_be_compressed'
compressed_value = zlib.compress(value.encode())
self.mc.set('binary1', compressed_value)
compressed_result = self.mc.get('binary1')
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, zlib.decompress(compressed_result).decode())
self.mc.add('binary1-add', compressed_value)
compressed_result = self.mc.get('binary1-add')
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, zlib.decompress(compressed_result).decode())
self.mc.set_multi({'binary1-set_many': compressed_value})
compressed_result = self.mc.get('binary1-set_many')
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, zlib.decompress(compressed_result).decode())
def test_ignore_too_large_value(self):
# NOTE: "MemCached: while expecting[...]" is normal...
key = 'keyhere'
value = 'a' * (SERVER_MAX_VALUE_LENGTH // 2)
self.assertTrue(self.mc.set(key, value))
self.assertEqual(self.mc.get(key), value)
value = 'a' * SERVER_MAX_VALUE_LENGTH
with captured_stderr() as log:
self.assertIs(self.mc.set(key, value), False)
self.assertEqual(
log.getvalue(),
"MemCached: while expecting 'STORED', got unexpected response "
"'SERVER_ERROR object too large for cache'\n"
)
# This test fails if the -I option is used on the memcached server
self.assertTrue(self.mc.get(key) is None)
def test_get_set_multi_key_prefix(self):
"""Testing set_multi() with no memcacheds running."""
prefix = 'pfx_'
values = {'key1': 'a', 'key2': 'b'}
errors = self.mc.set_multi(values, key_prefix=prefix)
self.assertEqual(errors, [])
keys = list(values)
self.assertEqual(self.mc.get_multi(keys, key_prefix=prefix),
values)
def test_set_multi_dead_servers(self):
"""Testing set_multi() with no memcacheds running."""
self.mc.disconnect_all()
with captured_stderr() as log:
for server in self.mc.servers:
server.mark_dead('test')
self.assertIn('Marking dead.', log.getvalue())
errors = self.mc.set_multi({'key1': 'a', 'key2': 'b'})
self.assertEqual(sorted(errors), ['key1', 'key2'])
def test_disconnect_all_delete_multi(self):
"""Testing delete_multi() with no memcacheds running."""
self.mc.disconnect_all()
with captured_stderr() as output:
ret = self.mc.delete_multi(('keyhere', 'keythere'))
self.assertEqual(ret, 1)
self.assertEqual(
output.getvalue(),
"MemCached: while expecting 'DELETED', got unexpected response "
"'NOT_FOUND'\n"
"MemCached: while expecting 'DELETED', got unexpected response "
"'NOT_FOUND'\n"
)
@mock.patch.object(_Host, 'send_cmd') # Don't send any commands.
@mock.patch.object(_Host, 'readline')
def test_touch_unexpected_reply(self, mock_readline, mock_send_cmd):
"""touch() logs an error upon receiving an unexpected reply."""
mock_readline.return_value = 'SET' # the unexpected reply
with captured_stderr() as output:
self.mc.touch('key')
self.assertEqual(
output.getvalue(),
"MemCached: touch expected %s, got: 'SET'\n" % 'TOUCHED'
)
class TestMemcacheEncoder(unittest.TestCase):
def setUp(self):
# TODO(): unix socket server stuff
servers = ["127.0.0.1:11211"]
self.mc = Client(servers, debug=1, key_encoder=self.encoder)
def tearDown(self):
self.mc.flush_all()
self.mc.disconnect_all()
def encoder(self, key):
return key.lower()
def check_setget(self, key, val, noreply=False):
self.mc.set(key, val, noreply=noreply)
newval = self.mc.get(key)
self.assertEqual(newval, val)
def test_setget(self):
self.check_setget("a_string", "some random string")
self.check_setget("A_String2", "some random string")
self.check_setget("an_integer", 42)
self.assertEqual("some random string", self.mc.get("A_String"))
self.assertEqual("some random string", self.mc.get("a_sTRing2"))
self.assertEqual(42, self.mc.get("An_Integer"))
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
#
# Tests for set_multi.
#
# ==============
# This is based on a skeleton test file, more information at:
#
# https://github.com/linsomniac/python-unittest-skeleton
from __future__ import print_function
import socket
import sys
import unittest
from .utils import captured_stderr
sys.path.append('..')
import memcache # noqa: E402
DEBUG = False
class test_Memcached_Set_Multi(unittest.TestCase):
def setUp(self):
RECV_CHUNKS = [b'chunk1']
class FakeSocket(object):
def __init__(self, *args):
if DEBUG:
print('FakeSocket{0!r}'.format(args))
self._recv_chunks = list(RECV_CHUNKS)
def connect(self, *args):
if DEBUG:
print('FakeSocket.connect{0!r}'.format(args))
def sendall(self, *args):
if DEBUG:
print('FakeSocket.sendall{0!r}'.format(args))
def recv(self, *args):
if self._recv_chunks:
data = self._recv_chunks.pop(0)
else:
data = ''
if DEBUG:
print('FakeSocket.recv{0!r} -> {1!r}'.format(args, data))
return data
def close(self):
if DEBUG:
print('FakeSocket.close()')
self.old_socket = socket.socket
socket.socket = FakeSocket
self.mc = memcache.Client(['memcached'], debug=True)
def tearDown(self):
socket.socket = self.old_socket
def test_Socket_Disconnect(self):
mapping = {'foo': 'FOO', 'bar': 'BAR'}
with captured_stderr() as log:
bad_keys = self.mc.set_multi(mapping)
self.assertIn('connection closed in readline().', log.getvalue())
self.assertEqual(sorted(bad_keys), ['bar', 'foo'])
if DEBUG:
print('set_multi({0!r}) -> {1!r}'.format(mapping, bad_keys))
if __name__ == '__main__':
unittest.main()
+1
-0

@@ -7,2 +7,3 @@ include *.md

include MakeFile
include PSF.LICENSE

@@ -9,0 +10,0 @@ global-exclude *.pyc

+155
-136

@@ -48,5 +48,5 @@ #!/usr/bin/env python

from __future__ import print_function
import binascii
from datetime import timedelta
from io import BytesIO

@@ -60,13 +60,9 @@ import re

import six
import pickle
if six.PY2:
# With Python 2, the faster C implementation has to be imported explicitly.
import cPickle as pickle
else:
import pickle
def cmemcache_hash(key):
return ((binascii.crc32(key) & 0xffffffff) >> 16) & 0x7fff
def cmemcache_hash(key):
return (((binascii.crc32(key) & 0xffffffff) >> 16) & 0x7fff) or 1
serverHashFunction = cmemcache_hash

@@ -86,3 +82,3 @@

__author__ = "Sean Reifschneider <jafo00@gmail.com>"
__version__ = "1.59"
__version__ = "1.60"
__copyright__ = "Copyright (C) 2003 Danga Interactive"

@@ -133,3 +129,3 @@ # http://en.wikipedia.org/wiki/Python_Software_Foundation_License

@sort: __init__, set_servers, forget_dead_hosts, disconnect_all,
debuglog,\ set, set_multi, add, replace, get, get_multi,
debuglog, set, set_multi, add, replace, get, get_multi,
incr, decr, delete, delete_multi

@@ -170,3 +166,4 @@ """

dead_retry=_DEAD_RETRY, socket_timeout=_SOCKET_TIMEOUT,
cache_cas=False, flush_on_reconnect=0, check_keys=True):
cache_cas=False, flush_on_reconnect=0, check_keys=True,
key_encoder=None):
"""Create a new Client object with the given list of servers.

@@ -214,4 +211,8 @@

characters.
@param key_encoder: (default None) If provided a functor that will
be called to encode keys before they are checked and used. It will
be expected to take one parameter (the key) and return a new encoded
key as a result.
"""
super(Client, self).__init__()
super().__init__()
self.debug = debug

@@ -236,2 +237,6 @@ self.dead_retry = dead_retry

self.server_max_key_length = server_max_key_length
if key_encoder is None:
def key_encoder(key):
return key
self.key_encoder = key_encoder
if self.server_max_key_length is None:

@@ -253,5 +258,5 @@ self.server_max_key_length = SERVER_MAX_KEY_LENGTH

if isinstance(key, tuple):
if isinstance(key[1], six.text_type):
if isinstance(key[1], str):
return (key[0], key[1].encode('utf8'))
elif isinstance(key, six.text_type):
elif isinstance(key, str):
return key.encode('utf8')

@@ -261,8 +266,7 @@ return key

def _encode_cmd(self, cmd, key, headers, noreply, *args):
cmd_bytes = cmd.encode('utf-8') if six.PY3 else cmd
cmd_bytes = cmd.encode('utf-8')
fullcmd = [cmd_bytes, b' ', key]
if headers:
if six.PY3:
headers = headers.encode('utf-8')
headers = headers.encode('utf-8')
fullcmd.append(b' ')

@@ -323,7 +327,7 @@ fullcmd.append(headers)

if s.family == socket.AF_INET:
name = '%s:%s (%s)' % (s.ip, s.port, s.weight)
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
elif s.family == socket.AF_INET6:
name = '[%s]:%s (%s)' % (s.ip, s.port, s.weight)
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
else:
name = 'unix:%s (%s)' % (s.address, s.weight)
name = 'unix:{} ({})'.format(s.address, s.weight)
if not stat_args:

@@ -336,7 +340,9 @@ s.send_cmd('stats')

readline = s.readline
while 1:
while True:
line = readline()
if not line or line.decode('ascii').strip() == 'END':
if line:
line = line.decode('ascii')
if not line or line.strip() == 'END':
break
stats = line.decode('ascii').split(' ', 2)
stats = line.split(' ', 2)
serverData[stats[1]] = stats[2]

@@ -352,7 +358,7 @@

if s.family == socket.AF_INET:
name = '%s:%s (%s)' % (s.ip, s.port, s.weight)
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
elif s.family == socket.AF_INET6:
name = '[%s]:%s (%s)' % (s.ip, s.port, s.weight)
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
else:
name = 'unix:%s (%s)' % (s.address, s.weight)
name = 'unix:{} ({})'.format(s.address, s.weight)
serverData = {}

@@ -362,4 +368,6 @@ data.append((name, serverData))

readline = s.readline
while 1:
while True:
line = readline()
if line:
line = line.decode('ascii')
if not line or line.strip() == 'END':

@@ -379,2 +387,7 @@ break

def quit_all(self) -> None:
'''Send a "quit" command to all servers and wait for the connection to close.'''
for s in self.servers:
s.quit()
def get_slabs(self):

@@ -386,7 +399,7 @@ data = []

if s.family == socket.AF_INET:
name = '%s:%s (%s)' % (s.ip, s.port, s.weight)
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
elif s.family == socket.AF_INET6:
name = '[%s]:%s (%s)' % (s.ip, s.port, s.weight)
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
else:
name = 'unix:%s (%s)' % (s.address, s.weight)
name = 'unix:{} ({})'.format(s.address, s.weight)
serverData = {}

@@ -396,3 +409,3 @@ data.append((name, serverData))

readline = s.readline
while 1:
while True:
line = readline()

@@ -453,3 +466,3 @@ if not line or line.strip() == 'END':

serverhash = str(serverhash) + str(i)
if isinstance(serverhash, six.text_type):
if isinstance(serverhash, str):
serverhash = serverhash.encode('ascii')

@@ -499,3 +512,3 @@ serverhash = serverHashFunction(serverhash)

rc = 1
for server in six.iterkeys(server_keys):
for server in server_keys.keys():
bigcmd = []

@@ -508,7 +521,7 @@ write = bigcmd.append

for key in server_keys[server]: # These are mangled keys
cmd = self._encode_cmd('delete', key, headers, noreply, b'\r\n')
cmd = self._encode_cmd('delete', self.key_encoder(key), headers, noreply, b'\r\n')
write(cmd)
try:
server.send_cmds(b''.join(bigcmd))
except socket.error as msg:
except OSError as msg:
rc = 0

@@ -528,7 +541,7 @@ if isinstance(msg, tuple):

for server, keys in six.iteritems(server_keys):
for server, keys in server_keys.items():
try:
for key in keys:
server.expect(b"DELETED")
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -540,8 +553,6 @@ msg = msg[1]

def delete(self, key, time=None, noreply=False):
def delete(self, key, noreply=False):
'''Deletes a key from the memcache.
@return: Nonzero on success.
@param time: number of seconds any subsequent set / update commands
should fail. Defaults to None for no delay.
@param noreply: optional parameter instructs the server to not send the

@@ -551,5 +562,25 @@ reply.

'''
return self._deletetouch([b'DELETED', b'NOT_FOUND'], "delete", key,
time, noreply)
key = self._encode_key(self.key_encoder(key))
if self.do_check_key:
self.check_key(key)
server, key = self._get_server(key)
if not server:
return 0
self._statlog('delete')
fullcmd = self._encode_cmd('delete', key, None, noreply)
try:
server.send_cmd(fullcmd)
if noreply:
return 1
line = server.readline()
if line and line.strip() in [b'DELETED', b'NOT_FOUND']:
return 1
self.debuglog('delete expected DELETED or NOT_FOUND, got: {!r}'.format(line))
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return 0
def touch(self, key, time=0, noreply=False):

@@ -568,6 +599,3 @@ '''Updates the expiration time of a key in memcache.

'''
return self._deletetouch([b'TOUCHED'], "touch", key, time, noreply)
def _deletetouch(self, expected, cmd, key, time=0, noreply=False):
key = self._encode_key(key)
key = self._encode_key(self.key_encoder(key))
if self.do_check_key:

@@ -578,8 +606,4 @@ self.check_key(key)

return 0
self._statlog(cmd)
if time is not None:
headers = str(time)
else:
headers = None
fullcmd = self._encode_cmd(cmd, key, headers, noreply)
self._statlog('touch')
fullcmd = self._encode_cmd('touch', key, str(time), noreply)

@@ -591,7 +615,6 @@ try:

line = server.readline()
if line and line.strip() in expected:
if line and line.strip() in [b'TOUCHED']:
return 1
self.debuglog('%s expected %s, got: %r'
% (cmd, b' or '.join(expected), line))
except socket.error as msg:
self.debuglog('touch expected TOUCHED, got: {!r}'.format(line))
except OSError as msg:
if isinstance(msg, tuple):

@@ -633,3 +656,3 @@ msg = msg[1]

"""
return self._incrdecr("incr", key, delta, noreply)
return self._incrdecr("incr", self.key_encoder(key), delta, noreply)

@@ -652,3 +675,3 @@ def decr(self, key, delta=1, noreply=False):

"""
return self._incrdecr("decr", key, delta, noreply)
return self._incrdecr("decr", self.key_encoder(key), delta, noreply)

@@ -672,3 +695,3 @@ def _incrdecr(self, cmd, key, delta, noreply=False):

return int(line)
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -688,3 +711,3 @@ msg = msg[1]

'''
return self._set("add", key, val, time, min_compress_len, noreply)
return self._set("add", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -700,3 +723,3 @@ def append(self, key, val, time=0, min_compress_len=0, noreply=False):

'''
return self._set("append", key, val, time, min_compress_len, noreply)
return self._set("append", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -712,3 +735,3 @@ def prepend(self, key, val, time=0, min_compress_len=0, noreply=False):

'''
return self._set("prepend", key, val, time, min_compress_len, noreply)
return self._set("prepend", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -724,3 +747,3 @@ def replace(self, key, val, time=0, min_compress_len=0, noreply=False):

'''
return self._set("replace", key, val, time, min_compress_len, noreply)
return self._set("replace", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -744,3 +767,3 @@ def set(self, key, val, time=0, min_compress_len=0, noreply=False):

docs section "Storage Commands" for more info on <exptime>. We
default to 0 == cache forever.
default to 0 == cache forever. Optionnaly now accepts a timedelta.

@@ -760,3 +783,5 @@ @param min_compress_len: The threshold length to kick in

'''
return self._set("set", key, val, time, min_compress_len, noreply)
if isinstance(time, timedelta):
time = int(time.total_seconds())
return self._set("set", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -798,3 +823,3 @@ def cas(self, key, val, time=0, min_compress_len=0, noreply=False):

'''
return self._set("cas", key, val, time, min_compress_len, noreply)
return self._set("cas", self.key_encoder(key), val, time, min_compress_len, noreply)

@@ -826,8 +851,6 @@ def _map_and_prefix_keys(self, key_iterable, key_prefix):

key = self._encode_key(key)
if not isinstance(key, six.binary_type):
key = self._encode_key(self.key_encoder(key))
if not isinstance(key, bytes):
# set_multi supports int / long keys.
key = str(key)
if six.PY3:
key = key.encode('utf8')
key = str(key).encode('utf8')
bytes_orig_key = key

@@ -839,11 +862,7 @@

(serverhash, key_prefix + key))
orig_key = orig_key[1]
else:
key = self._encode_key(orig_key)
if not isinstance(key, six.binary_type):
key = self._encode_key(self.key_encoder(orig_key))
if not isinstance(key, bytes):
# set_multi supports int / long keys.
key = str(key)
if six.PY3:
key = key.encode('utf8')
key = str(key).encode('utf8')
bytes_orig_key = key

@@ -933,3 +952,3 @@ server, key = self._get_server(key_prefix + key)

server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(
six.iterkeys(mapping), key_prefix)
mapping.keys(), key_prefix)

@@ -940,3 +959,3 @@ # send out all requests on each server before reading anything

for server in six.iterkeys(server_keys):
for server in server_keys.keys():
bigcmd = []

@@ -952,3 +971,3 @@ write = bigcmd.append

headers = "%d %d %d" % (flags, time, len_val)
fullcmd = self._encode_cmd('set', key, headers,
fullcmd = self._encode_cmd('set', self.key_encoder(key), headers,
noreply,

@@ -960,3 +979,3 @@ b'\r\n', val, b'\r\n')

server.send_cmds(b''.join(bigcmd))
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -979,3 +998,3 @@ msg = msg[1]

for server, keys in six.iteritems(server_keys):
for server, keys in server_keys.items():
try:

@@ -988,3 +1007,3 @@ for key in keys:

notstored.append(prefixed_to_orig_key[key])
except (_Error, socket.error) as msg:
except (_Error, OSError) as msg:
if isinstance(msg, tuple):

@@ -1006,5 +1025,5 @@ msg = msg[1]

val_type = type(val)
if val_type == six.binary_type:
if val_type == bytes:
pass
elif val_type == six.text_type:
elif val_type == str:
flags |= Client._FLAG_TEXT

@@ -1014,14 +1033,5 @@ val = val.encode('utf-8')

flags |= Client._FLAG_INTEGER
val = '%d' % val
if six.PY3:
val = val.encode('ascii')
val = ('%d' % val).encode('ascii')
# force no attempt to compress this silly string.
min_compress_len = 0
elif six.PY2 and isinstance(val, long): # noqa: F821
flags |= Client._FLAG_LONG
val = str(val)
if six.PY3:
val = val.encode('ascii')
# force no attempt to compress this silly string.
min_compress_len = 0
else:

@@ -1051,4 +1061,3 @@ flags |= Client._FLAG_PICKLE

# silently do not store if value length exceeds maximum
if (self.server_max_value_length != 0 and
len(val) > self.server_max_value_length):
if (self.server_max_value_length != 0 and len(val) > self.server_max_value_length):
return 0

@@ -1091,3 +1100,3 @@

return server.expect(b"STORED", raise_exception=True) == b"STORED"
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -1105,7 +1114,7 @@ msg = msg[1]

return _unsafe_set()
except (_ConnectionDeadError, socket.error) as msg:
except (_ConnectionDeadError, OSError) as msg:
server.mark_dead(msg)
return 0
def _get(self, cmd, key):
def _get(self, cmd, key, default=None):
key = self._encode_key(key)

@@ -1122,3 +1131,3 @@ if self.do_check_key:

try:
cmd_bytes = cmd.encode('utf-8') if six.PY3 else cmd
cmd_bytes = cmd.encode('utf-8')
fullcmd = b''.join((cmd_bytes, b' ', key))

@@ -1140,3 +1149,3 @@ server.send_cmd(fullcmd)

if not rkey:
return None
return default
try:

@@ -1146,3 +1155,3 @@ value = self._recv_value(server, flags, rlen)

server.expect(b"END", raise_exception=True)
except (_Error, socket.error) as msg:
except (_Error, OSError) as msg:
if isinstance(msg, tuple):

@@ -1163,7 +1172,7 @@ msg = msg[1]

return None
except (_ConnectionDeadError, socket.error) as msg:
except (_ConnectionDeadError, OSError) as msg:
server.mark_dead(msg)
return None
def get(self, key):
def get(self, key, default=None):
'''Retrieves a key from the memcache.

@@ -1173,3 +1182,3 @@

'''
return self._get('get', key)
return self._get('get', self.key_encoder(key), default)

@@ -1181,3 +1190,3 @@ def gets(self, key):

'''
return self._get('gets', key)
return self._get('gets', self.key_encoder(key))

@@ -1242,11 +1251,11 @@ def get_multi(self, keys, key_prefix=''):

server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(
keys, key_prefix)
[self.key_encoder(k) for k in keys], key_prefix)
# send out all requests on each server before reading anything
dead_servers = []
for server in six.iterkeys(server_keys):
for server in server_keys.keys():
try:
fullcmd = b"get " + b" ".join(server_keys[server])
server.send_cmd(fullcmd)
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -1262,3 +1271,3 @@ msg = msg[1]

retvals = {}
for server in six.iterkeys(server_keys):
for server in server_keys.keys():
try:

@@ -1274,3 +1283,3 @@ line = server.readline()

line = server.readline()
except (_Error, socket.error) as msg:
except (_Error, OSError) as msg:
if isinstance(msg, tuple):

@@ -1324,6 +1333,3 @@ msg = msg[1]

elif flags & Client._FLAG_LONG:
if six.PY3:
val = int(buf)
else:
val = long(buf) # noqa: F821
val = int(buf)
elif flags & Client._FLAG_PICKLE:

@@ -1361,4 +1367,4 @@ try:

raise Client.MemcachedKeyNoneError("Key is None")
if key is '':
if key_extra_len is 0:
if key == '':
if key_extra_len == 0:
raise Client.MemcachedKeyNoneError("Key is empty")

@@ -1369,7 +1375,6 @@

if not isinstance(key, six.binary_type):
if not isinstance(key, bytes):
raise Client.MemcachedKeyTypeError("Key must be a binary string")
if (self.server_max_key_length != 0 and
len(key) + key_extra_len > self.server_max_key_length):
if (self.server_max_key_length != 0 and len(key) + key_extra_len > self.server_max_key_length):
raise Client.MemcachedKeyLengthError(

@@ -1383,3 +1388,3 @@ "Key length is > %s" % self.server_max_key_length

class _Host(object):
class _Host:

@@ -1447,3 +1452,3 @@ def __init__(self, host, debug=0, dead_retry=_DEAD_RETRY,

def mark_dead(self, reason):
self.debuglog("MemCache: %s: %s. Marking dead." % (self, reason))
self.debuglog("MemCache: {}: {}. Marking dead.".format(self, reason))
self.deaduntil = time.time() + self.dead_retry

@@ -1467,3 +1472,3 @@ if self.flush_on_reconnect:

return None
except socket.error as msg:
except OSError as msg:
if isinstance(msg, tuple):

@@ -1486,3 +1491,3 @@ msg = msg[1]

def send_cmd(self, cmd):
if isinstance(cmd, six.text_type):
if isinstance(cmd, str):
cmd = cmd.encode('utf8')

@@ -1493,3 +1498,3 @@ self.socket.sendall(cmd + b'\r\n')

"""cmds already has trailing \r\n's applied."""
if isinstance(cmds, six.text_type):
if isinstance(cmds, str):
cmds = cmds.encode('utf8')

@@ -1508,3 +1513,4 @@ self.socket.sendall(cmds)

else:
recv = lambda bufsize: b''
def recv(bufsize):
return b''

@@ -1531,7 +1537,4 @@ while True:

if self.debug and line != text:
if six.PY3:
text = text.decode('utf8')
log_line = line.decode('utf8', 'replace')
else:
log_line = line
text = text.decode('utf8')
log_line = line.decode('utf8', 'replace')
self.debuglog("while expecting %r, got unexpected response %r"

@@ -1553,2 +1556,18 @@ % (text, log_line))

def quit(self) -> None:
'''Send a "quit" command to remote server and wait for connection to close.'''
if self.socket:
self.send_cmd('quit')
# We can't close the local socket until the remote end processes the quit
# command and sends us a FIN packet. When that happens, socket.recv()
# will stop blocking and return an empty string. If we try to close the
# socket before then, the OS will think we're initiating the connection
# close and will put the socket into TIME_WAIT.
self.socket.recv(1)
# At this point, socket should be in CLOSE_WAIT. Closing the socket should
# release the port back to the OS.
self.close_socket()
def flush(self):

@@ -1568,3 +1587,3 @@ self.send_cmd('flush_all')

else:
return "unix:%s%s" % (self.address, d)
return "unix:{}{}".format(self.address, d)

@@ -1580,3 +1599,3 @@

mc.disconnect_all()
print("Doctests: %s" % (results,))
print("Doctests: {}".format(results))
if results.failed:

@@ -1583,0 +1602,0 @@ sys.exit(1)

+52
-44

@@ -1,44 +0,11 @@

Metadata-Version: 1.1
Metadata-Version: 2.1
Name: python-memcached
Version: 1.59
Version: 1.60
Summary: Pure python memcached client
Home-page: https://github.com/linsomniac/python-memcached
Author: Sean Reifschneider
Author-email: jafo@tummy.com
License: UNKNOWN
Download-URL: https://github.com/linsomniac/python-memcached/releases/download/1.59/python-memcached-1.59.tar.gz
Description: [![Build
Status](https://travis-ci.org/linsomniac/python-memcached.svg)](https://travis-ci.org/linsomniac/python-memcached)
## Overview
This software is a 100% Python interface to the memcached memory cache
daemon. It is the client side software which allows storing values
in one or more, possibly remote, memcached servers. Search google for
memcached for more information.
This package was originally written by Evan Martin of Danga. Please do
not contact Evan about maintenance. Sean Reifschneider of tummy.com,
ltd. has taken over maintenance of it.
Please report issues and submit code changes to the github repository at:
https://github.com/linsomniac/python-memcached
For changes prior to 2013-03-26, see the old Launchpad repository at:
Historic issues: https://launchpad.net/python-memcached
## Testing
Test patches locally and easily by running tox:
pip install tox
tox -e py27
Test for style by running tox:
tox -e pep8
Platform: UNKNOWN
Download-URL: https://github.com/linsomniac/python-memcached/releases/download/1.60/python-memcached-1.60.tar.gz
Author: Evan Martin
Author-email: martine@danga.com
Maintainer: Sean Reifschneider
Maintainer-email: jafo00@gmail.com
Classifier: Development Status :: 5 - Production/Stable

@@ -52,7 +19,48 @@ Classifier: Intended Audience :: Developers

Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Description-Content-Type: text/markdown
[![Build
Status](https://travis-ci.org/linsomniac/python-memcached.svg)](https://travis-ci.org/linsomniac/python-memcached)
## Overview
This software is a 100% Python interface to the memcached memory cache
daemon. It is the client side software which allows storing values
in one or more, possibly remote, memcached servers. Search google for
memcached for more information.
This library is stable and largely in maintenance mode. Another library that
is getting more active enhancements is
[pymemcache](https://pypi.org/project/pymemcache/) and they have links and a
good set of comparisons between them on their page.
This package was originally written by Evan Martin of Danga. Please do
not contact Evan about maintenance. Sean Reifschneider of tummy.com,
ltd. has taken over maintenance of it.
Please report issues and submit code changes to the github repository at:
https://github.com/linsomniac/python-memcached
For changes prior to 2013-03-26, see the old Launchpad repository at:
Historic issues: https://launchpad.net/python-memcached
## Testing
Test patches locally and easily by running tox:
pip install tox
tox -e py312
Test for style by running tox:
tox -e pep8

@@ -1,44 +0,11 @@

Metadata-Version: 1.1
Metadata-Version: 2.1
Name: python-memcached
Version: 1.59
Version: 1.60
Summary: Pure python memcached client
Home-page: https://github.com/linsomniac/python-memcached
Author: Sean Reifschneider
Author-email: jafo@tummy.com
License: UNKNOWN
Download-URL: https://github.com/linsomniac/python-memcached/releases/download/1.59/python-memcached-1.59.tar.gz
Description: [![Build
Status](https://travis-ci.org/linsomniac/python-memcached.svg)](https://travis-ci.org/linsomniac/python-memcached)
## Overview
This software is a 100% Python interface to the memcached memory cache
daemon. It is the client side software which allows storing values
in one or more, possibly remote, memcached servers. Search google for
memcached for more information.
This package was originally written by Evan Martin of Danga. Please do
not contact Evan about maintenance. Sean Reifschneider of tummy.com,
ltd. has taken over maintenance of it.
Please report issues and submit code changes to the github repository at:
https://github.com/linsomniac/python-memcached
For changes prior to 2013-03-26, see the old Launchpad repository at:
Historic issues: https://launchpad.net/python-memcached
## Testing
Test patches locally and easily by running tox:
pip install tox
tox -e py27
Test for style by running tox:
tox -e pep8
Platform: UNKNOWN
Download-URL: https://github.com/linsomniac/python-memcached/releases/download/1.60/python-memcached-1.60.tar.gz
Author: Evan Martin
Author-email: martine@danga.com
Maintainer: Sean Reifschneider
Maintainer-email: jafo00@gmail.com
Classifier: Development Status :: 5 - Production/Stable

@@ -52,7 +19,48 @@ Classifier: Intended Audience :: Developers

Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Description-Content-Type: text/markdown
[![Build
Status](https://travis-ci.org/linsomniac/python-memcached.svg)](https://travis-ci.org/linsomniac/python-memcached)
## Overview
This software is a 100% Python interface to the memcached memory cache
daemon. It is the client side software which allows storing values
in one or more, possibly remote, memcached servers. Search google for
memcached for more information.
This library is stable and largely in maintenance mode. Another library that
is getting more active enhancements is
[pymemcache](https://pypi.org/project/pymemcache/) and they have links and a
good set of comparisons between them on their page.
This package was originally written by Evan Martin of Danga. Please do
not contact Evan about maintenance. Sean Reifschneider of tummy.com,
ltd. has taken over maintenance of it.
Please report issues and submit code changes to the github repository at:
https://github.com/linsomniac/python-memcached
For changes prior to 2013-03-26, see the old Launchpad repository at:
Historic issues: https://launchpad.net/python-memcached
## Testing
Test patches locally and easily by running tox:
pip install tox
tox -e py312
Test for style by running tox:
tox -e pep8
ChangeLog
MANIFEST.in
PSF.LICENSE
README.md
SECURITY.md
memcache.py

@@ -12,3 +14,4 @@ requirements.txt

python_memcached.egg-info/dependency_links.txt
python_memcached.egg-info/requires.txt
python_memcached.egg-info/top_level.txt
python_memcached.egg-info/top_level.txt
tests/test_memcache.py
tests/test_setmulti.py

@@ -11,2 +11,7 @@ [![Build

This library is stable and largely in maintenance mode. Another library that
is getting more active enhancements is
[pymemcache](https://pypi.org/project/pymemcache/) and they have links and a
good set of comparisons between them on their page.
This package was originally written by Evan Martin of Danga. Please do

@@ -29,3 +34,3 @@ not contact Evan about maintenance. Sean Reifschneider of tummy.com,

pip install tox
tox -e py27
tox -e py312

@@ -32,0 +37,0 @@ Test for style by running tox:

[bdist_rpm]
release = 1
packager = Sean Reifschneider <jafo-rpms@tummy.com>
packager = Sean Reifschneider <jafo00@gmail.com>
requires = python-memcached

@@ -5,0 +5,0 @@

@@ -6,2 +6,3 @@ #!/usr/bin/env python

dl_url = "https://github.com/linsomniac/python-memcached/releases/download/{0}/python-memcached-{0}.tar.gz"

@@ -14,8 +15,9 @@ version = get_module_constant('memcache', '__version__')

long_description=open("README.md").read(),
long_description_content_type="text/markdown",
author="Evan Martin",
author_email="martine@danga.com",
maintainer="Sean Reifschneider",
maintainer_email="jafo@tummy.com",
maintainer_email="jafo00@gmail.com",
url="https://github.com/linsomniac/python-memcached",
download_url="https://github.com/linsomniac/python-memcached/releases/download/{0}/python-memcached-{0}.tar.gz".format(version),
download_url="https://github.com/linsomniac/python-memcached/releases/download/{0}/python-memcached-{0}.tar.gz".format(version), # noqa
py_modules=["memcache"],

@@ -32,9 +34,11 @@ install_requires=open('requirements.txt').read().split(),

"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
)

Sorry, the diff of this file is not supported yet