python-memcached
Advanced tools
+48
| 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: [](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 | ||
| [](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: [](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 | ||
| [](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 |
+6
-1
@@ -11,2 +11,7 @@ [ 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: |
+1
-1
| [bdist_rpm] | ||
| release = 1 | ||
| packager = Sean Reifschneider <jafo-rpms@tummy.com> | ||
| packager = Sean Reifschneider <jafo00@gmail.com> | ||
| requires = python-memcached | ||
@@ -5,0 +5,0 @@ |
+10
-6
@@ -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", | ||
| ], | ||
| ) |
| six>=1.4.0 |
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
100885
22.3%17
21.43%1606
23.07%