Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4
Expand Down
54 changes: 15 additions & 39 deletions memcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ def get_stats(self, stat_args=None):
if not s.connect():
continue
if s.family == socket.AF_INET:
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
name = f'{s.ip}:{s.port} ({s.weight})'
elif s.family == socket.AF_INET6:
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
name = f'[{s.ip}]:{s.port} ({s.weight})'
else:
name = 'unix:{} ({})'.format(s.address, s.weight)
name = f'unix:{s.address} ({s.weight})'
if not stat_args:
s.send_cmd('stats')
else:
Expand All @@ -344,11 +344,11 @@ def get_slab_stats(self):
if not s.connect():
continue
if s.family == socket.AF_INET:
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
name = f'{s.ip}:{s.port} ({s.weight})'
elif s.family == socket.AF_INET6:
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
name = f'[{s.ip}]:{s.port} ({s.weight})'
else:
name = 'unix:{} ({})'.format(s.address, s.weight)
name = f'unix:{s.address} ({s.weight})'
serverData = {}
data.append((name, serverData))
s.send_cmd('stats slabs')
Expand Down Expand Up @@ -382,11 +382,11 @@ def get_slabs(self):
if not s.connect():
continue
if s.family == socket.AF_INET:
name = '{}:{} ({})'.format(s.ip, s.port, s.weight)
name = f'{s.ip}:{s.port} ({s.weight})'
elif s.family == socket.AF_INET6:
name = '[{}]:{} ({})'.format(s.ip, s.port, s.weight)
name = f'[{s.ip}]:{s.port} ({s.weight})'
else:
name = 'unix:{} ({})'.format(s.address, s.weight)
name = f'unix:{s.address} ({s.weight})'
serverData = {}
data.append((name, serverData))
s.send_cmd('stats items')
Expand Down Expand Up @@ -506,8 +506,6 @@ def delete_multi(self, keys, time=None, key_prefix='', noreply=False):
server.send_cmds(b''.join(bigcmd))
except OSError as msg:
rc = 0
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
dead_servers.append(server)

Expand All @@ -524,8 +522,6 @@ def delete_multi(self, keys, time=None, key_prefix='', noreply=False):
for key in keys:
server.expect(b"DELETED")
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
rc = 0
return rc
Expand Down Expand Up @@ -554,10 +550,8 @@ def delete(self, key, noreply=False):
line = server.readline()
if line and line.strip() == b'DELETED':
return 1
self.debuglog('delete expected DELETED, got: {!r}'.format(line))
self.debuglog(f'delete expected DELETED, got: {line!r}')
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return 0

Expand Down Expand Up @@ -590,10 +584,8 @@ def touch(self, key, time=0, noreply=False):
line = server.readline()
if line and line.strip() in [b'TOUCHED']:
return 1
self.debuglog('touch expected TOUCHED, got: {!r}'.format(line))
self.debuglog(f'touch expected TOUCHED, got: {line!r}')
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return 0

Expand Down Expand Up @@ -666,8 +658,6 @@ def _incrdecr(self, cmd, key, delta, noreply=False):
return None
return int(line)
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return None

Expand Down Expand Up @@ -937,8 +927,6 @@ def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0,
notstored.append(prefixed_to_orig_key[key])
server.send_cmds(b''.join(bigcmd))
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
dead_servers.append(server)

Expand All @@ -963,8 +951,6 @@ def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0,
# un-mangle.
notstored.append(prefixed_to_orig_key[key])
except (_Error, OSError) as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return notstored

Expand Down Expand Up @@ -1052,8 +1038,6 @@ def _unsafe_set():
return True
return server.expect(b"STORED", raise_exception=True) == b"STORED"
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return 0

Expand Down Expand Up @@ -1103,8 +1087,6 @@ def _unsafe_get():
finally:
server.expect(b"END", raise_exception=True)
except (_Error, OSError) as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return None

Expand Down Expand Up @@ -1203,8 +1185,6 @@ def get_multi(self, keys, key_prefix=''):
fullcmd = b"get " + b" ".join(server_keys[server])
server.send_cmd(fullcmd)
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
dead_servers.append(server)

Expand All @@ -1225,8 +1205,6 @@ def get_multi(self, keys, key_prefix=''):
retvals[prefixed_to_orig_key[rkey]] = val
line = server.readline()
except (_Error, OSError) as msg:
if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return retvals

Expand Down Expand Up @@ -1388,7 +1366,7 @@ def connect(self):
return 0

def mark_dead(self, reason):
self.debuglog("MemCache: {}: {}. Marking dead.".format(self, reason))
self.debuglog(f"MemCache: {self}: {reason}. Marking dead.")
self.deaduntil = time.time() + self.dead_retry
if self.flush_on_reconnect:
self.flush_on_next_connect = 1
Expand All @@ -1404,12 +1382,10 @@ def _get_socket(self):
s.settimeout(self.socket_timeout)
try:
s.connect(self.address)
except socket.timeout as msg:
except TimeoutError as msg:
self.mark_dead("connect: %s" % msg)
return None
except OSError as msg:
if isinstance(msg, tuple):
msg = msg[1]
self.mark_dead("connect: %s" % msg)
return None
self.socket = s
Expand Down Expand Up @@ -1516,7 +1492,7 @@ def __str__(self):
elif self.family == socket.AF_INET6:
return "inet6:[%s]:%d%s" % (self.address[0], self.address[1], d)
else:
return "unix:{}{}".format(self.address, d)
return f"unix:{self.address}{d}"


def _doctest():
Expand All @@ -1527,7 +1503,7 @@ def _doctest():
globs = {"mc": mc}
results = doctest.testmod(memcache, globs=globs)
mc.disconnect_all()
print("Doctests: {}".format(results))
print(f"Doctests: {results}")
if results.failed:
sys.exit(1)

Expand Down
10 changes: 7 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#!/usr/bin/env python

import re
from setuptools import setup # noqa
from setuptools.depends import get_module_constant

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

version = get_module_constant("memcache", "__version__")
version = re.search(
r'^__version__ = ["\']([^"\']+)["\']',
open("memcache.py", encoding="utf-8").read(),
re.M,
).group(1)
setup(
name="python-memcached",
version=version,
Expand All @@ -32,10 +36,10 @@
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
],
)
3 changes: 1 addition & 2 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
nose
pytest
coverage
hacking
mock
14 changes: 4 additions & 10 deletions tests/test_memcache.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import print_function

import unittest
import zlib

try:
import unittest.mock as mock
except ImportError:
import mock
from unittest 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):
class FooStruct:

def __init__(self):
self.bar = "baz"
Expand Down Expand Up @@ -151,7 +145,7 @@ def test_setget_boolean(self):
self.check_setget("bool", True)

def test_unicode_key(self):
s = u'\u4f1a'
s = '\u4f1a'
maxlen = SERVER_MAX_KEY_LENGTH // len(s.encode('utf-8'))
key = s * maxlen

Expand All @@ -161,7 +155,7 @@ def test_unicode_key(self):

def test_unicode_value(self):
key = 'key'
value = u'Iñtërnâtiônàlizætiøn2'
value = 'Iñtërnâtiônàlizætiøn2'
self.mc.set(key, value)
cached_value = self.mc.get(key)
self.assertEqual(value, cached_value)
Expand Down
13 changes: 6 additions & 7 deletions tests/test_setmulti.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#
# https://github.com/linsomniac/python-unittest-skeleton

from __future__ import print_function

import socket
import sys
Expand All @@ -25,27 +24,27 @@ class test_Memcached_Set_Multi(unittest.TestCase):
def setUp(self):
RECV_CHUNKS = [b'chunk1']

class FakeSocket(object):
class FakeSocket:
def __init__(self, *args):
if DEBUG:
print('FakeSocket{0!r}'.format(args))
print(f'FakeSocket{args!r}')
self._recv_chunks = list(RECV_CHUNKS)

def connect(self, *args):
if DEBUG:
print('FakeSocket.connect{0!r}'.format(args))
print(f'FakeSocket.connect{args!r}')

def sendall(self, *args):
if DEBUG:
print('FakeSocket.sendall{0!r}'.format(args))
print(f'FakeSocket.sendall{args!r}')

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))
print(f'FakeSocket.recv{args!r} -> {data!r}')
return data

def close(self):
Expand All @@ -67,7 +66,7 @@ def test_Socket_Disconnect(self):
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))
print(f'set_multi({mapping!r}) -> {bad_keys!r}')


if __name__ == '__main__':
Expand Down
12 changes: 4 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[tox]
minversion = 1.6
envlist = py{36,37,38,39,310,311,312},pypy,pep8
envlist = py{310,311,312,313,314},pep8
skipsdist = True

[testenv]
Expand All @@ -10,18 +9,15 @@ install_command = pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
nosetests {posargs}
pytest {posargs}
python -c 'import memcache; memcache._doctest()'

[tox:jenkins]
downloadcache = ~/cache/pip

[testenv:pep8]
commands = flake8

[testenv:cover]
commands = nosetests --with-coverage {posargs}
commands = pytest --cov {posargs}

[flake8]
exclude = .venv*,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*.egg,.update-venv,build
exclude = venv*,.venv*,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*.egg,.update-venv,build
max-line-length = 119