diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7aee34..58c1c57 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/memcache.py b/memcache.py index 3462d64..6c8144b 100644 --- a/memcache.py +++ b/memcache.py @@ -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: @@ -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') @@ -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') @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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(): @@ -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) diff --git a/setup.py b/setup.py index f272fe7..3a40843 100644 --- a/setup.py +++ b/setup.py @@ -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, @@ -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", ], ) diff --git a/test-requirements.txt b/test-requirements.txt index d80827e..b519cd3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,3 @@ -nose +pytest coverage hacking -mock diff --git a/tests/test_memcache.py b/tests/test_memcache.py index 2e907ac..1079dd7 100644 --- a/tests/test_memcache.py +++ b/tests/test_memcache.py @@ -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" @@ -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 @@ -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) diff --git a/tests/test_setmulti.py b/tests/test_setmulti.py index 756afe8..4e59e95 100644 --- a/tests/test_setmulti.py +++ b/tests/test_setmulti.py @@ -7,7 +7,6 @@ # # https://github.com/linsomniac/python-unittest-skeleton -from __future__ import print_function import socket import sys @@ -25,19 +24,19 @@ 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: @@ -45,7 +44,7 @@ def recv(self, *args): 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): @@ -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__': diff --git a/tox.ini b/tox.ini index d741a8e..ecd2c56 100644 --- a/tox.ini +++ b/tox.ini @@ -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] @@ -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