Skip to content
Merged
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
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', 'pypy-3.9']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-python-${{ matrix.python-version }}-pip-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: ${{ runner.os }}-python-${{ matrix.python-version }}-pip
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox tox-gh-actions
sudo apt-get install libmemcached-dev
- name: Lint
Expand Down
2 changes: 1 addition & 1 deletion docs-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sphinx==5.2.1
sphinx==6.2.1
sphinx_rtd_theme==1.2.2
sphinxcontrib-apidoc==0.5.0
sphinxcontrib-napoleon==0.7
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# pymemcache documentation build configuration file, created by
# sphinx-quickstart on Wed Aug 3 11:15:43 2016.
Expand Down
3 changes: 2 additions & 1 deletion lint-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
black==24.4.2
docutils==0.20.1
flake8==7.1.1
pygments==2.18.0
pygments==2.18.0
setuptools; python_version >= "3.12"
51 changes: 26 additions & 25 deletions pymemcache/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from functools import partial
from ssl import SSLContext
from types import ModuleType
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
from typing import Any, Callable, Optional, Union
from collections.abc import Iterable

from pymemcache import pool
from pymemcache.exceptions import (
Expand Down Expand Up @@ -52,7 +53,7 @@
b"EXISTS": False,
}

ServerSpec = Union[Tuple[str, int], str]
ServerSpec = Union[tuple[str, int], str]
Key = Union[bytes, str]


Expand All @@ -74,7 +75,7 @@ def _parse_hex(value: bytes) -> int:
return int(value, 8)


STAT_TYPES: Dict[bytes, Callable[[bytes], Any]] = {
STAT_TYPES: dict[bytes, Callable[[bytes], Any]] = {
# General stats
b"version": bytes,
b"rusage_user": _parse_float,
Expand Down Expand Up @@ -476,11 +477,11 @@ def set(

def set_many(
self,
values: Dict[Key, Any],
values: dict[Key, Any],
expire: int = 0,
noreply: Optional[bool] = None,
flags: Optional[int] = None,
) -> List[Key]:
) -> list[Key]:
"""
A convenience function for setting multiple values.

Expand Down Expand Up @@ -705,7 +706,7 @@ def gat(self, key: Key, expire: int = 0, default: Optional[Any] = None) -> Any:
b"gat", [key], False, key_prefix=self.key_prefix, expire=expire
).get(key, default)

def get_many(self, keys: Iterable[Key]) -> Dict[Key, Any]:
def get_many(self, keys: Iterable[Key]) -> dict[Key, Any]:
"""
The memcached "get" command.

Expand All @@ -726,7 +727,7 @@ def get_many(self, keys: Iterable[Key]) -> Dict[Key, Any]:

def gets(
self, key: Key, default: Any = None, cas_default: Any = None
) -> Tuple[Any, Any]:
) -> tuple[Any, Any]:
"""
The memcached "gets" command for one key, as a convenience.

Expand All @@ -746,7 +747,7 @@ def gets(

def gats(
self, key: Key, expire: int = 0, default: Any = None, cas_default: Any = None
) -> Tuple[Any, Any]:
) -> tuple[Any, Any]:
"""
The memcached "gats" command, but only for one key, as a convenience.

Expand All @@ -766,7 +767,7 @@ def gats(
b"gats", [key], True, key_prefix=self.key_prefix, expire=expire
).get(key, defaults)

def gets_many(self, keys: Iterable[Key]) -> Dict[Key, Tuple[Any, Any]]:
def gets_many(self, keys: Iterable[Key]) -> dict[Key, tuple[Any, Any]]:
"""
The memcached "gets" command.

Expand Down Expand Up @@ -1118,9 +1119,9 @@ def _extract_value(
expect_cas: bool,
line: bytes,
buf: bytes,
remapped_keys: Dict[bytes, Key],
prefixed_keys: List[bytes],
) -> Tuple[Key, Union[Any, Tuple[Any, bytes]], bytes]:
remapped_keys: dict[bytes, Key],
prefixed_keys: list[bytes],
) -> tuple[Key, Union[Any, tuple[Any, bytes]], bytes]:
"""
This function is abstracted from _fetch_cmd to support different ways
of value extraction. In order to use this feature, _extract_value needs
Expand Down Expand Up @@ -1158,7 +1159,7 @@ def _fetch_cmd(
expect_cas: bool,
key_prefix: bytes = b"",
expire: Optional[int] = None,
) -> Dict[Key, Any]:
) -> dict[Key, Any]:
prefixed_keys = [self.check_key(k, key_prefix=key_prefix) for k in keys]
remapped_keys = dict(zip(prefixed_keys, keys))

Expand All @@ -1183,7 +1184,7 @@ def _fetch_cmd(

buf = b""
line = None
result: Dict[Key, Any] = {}
result: dict[Key, Any] = {}
while True:
try:
buf, line = _readline(self.sock, buf)
Expand Down Expand Up @@ -1216,12 +1217,12 @@ def _fetch_cmd(
def _store_cmd(
self,
name: bytes,
values: Dict[Key, Any],
values: dict[Key, Any],
expire: int,
noreply: bool,
flags: Optional[int] = None,
cas: Optional[bytes] = None,
) -> Dict[Key, Optional[bool]]:
) -> dict[Key, Optional[bool]]:
cmds = []
keys = []

Expand Down Expand Up @@ -1305,10 +1306,10 @@ def _misc_cmd(
cmd_name: bytes,
noreply: Optional[bool],
end_tokens=None,
) -> List[bytes]:
) -> list[bytes]:
# If no end_tokens have been given, just assume standard memcached
# operations, which end in "\r\n", use regular code for that.
_reader: Callable[[socket.socket, bytes], Tuple[bytes, bytes]]
_reader: Callable[[socket.socket, bytes], tuple[bytes, bytes]]
if end_tokens:
_reader = partial(_readsegment, end_tokens=end_tokens)
else:
Expand Down Expand Up @@ -1561,7 +1562,7 @@ def gats(self, key: Key, expire: int = 0, default: Optional[Any] = None) -> Any:
else:
raise

def get_many(self, keys: Iterable[Key]) -> Dict[Key, Any]:
def get_many(self, keys: Iterable[Key]) -> dict[Key, Any]:
with self.client_pool.get_and_release(destroy_on_fail=True) as client:
try:
return client.get_many(keys)
Expand All @@ -1573,7 +1574,7 @@ def get_many(self, keys: Iterable[Key]) -> Dict[Key, Any]:

get_multi = get_many

def gets(self, key: Key) -> Tuple[Any, Any]:
def gets(self, key: Key) -> tuple[Any, Any]:
with self.client_pool.get_and_release(destroy_on_fail=True) as client:
try:
return client.gets(key)
Expand All @@ -1583,7 +1584,7 @@ def gets(self, key: Key) -> Tuple[Any, Any]:
else:
raise

def gets_many(self, keys: Iterable[Key]) -> Dict[Key, Tuple[Any, Any]]:
def gets_many(self, keys: Iterable[Key]) -> dict[Key, tuple[Any, Any]]:
with self.client_pool.get_and_release(destroy_on_fail=True) as client:
try:
return client.gets_many(keys)
Expand Down Expand Up @@ -1672,7 +1673,7 @@ def __delitem__(self, key):
self.delete(key, noreply=True)


def _readline(sock: socket.socket, buf: bytes) -> Tuple[bytes, bytes]:
def _readline(sock: socket.socket, buf: bytes) -> tuple[bytes, bytes]:
"""Read line of text from the socket.

Read a line of text (delimited by "\r\n") from the socket, and
Expand All @@ -1692,7 +1693,7 @@ def _readline(sock: socket.socket, buf: bytes) -> Tuple[bytes, bytes]:
byte string).

"""
chunks: List[bytes] = []
chunks: list[bytes] = []
last_char = b""

while True:
Expand Down Expand Up @@ -1770,7 +1771,7 @@ def _readvalue(sock: socket.socket, buf: bytes, size: int):

def _readsegment(
sock: socket.socket, buf: bytes, end_tokens: bytes
) -> Tuple[bytes, bytes]:
) -> tuple[bytes, bytes]:
"""Read a segment from the socket.

Read a segment from the socket, up to the first end_token sub-string/bytes,
Expand All @@ -1791,7 +1792,7 @@ def _readsegment(
bytes object).

"""
result = bytes()
result = b""

while True:
tokens_pos = buf.find(end_tokens)
Expand Down
2 changes: 1 addition & 1 deletion pymemcache/client/retrying.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def _ensure_tuple_argument(argument_name, argument_value):
return argument_tuple


class RetryingClient(object):
class RetryingClient:
"""
Client that allows retrying calls for the other clients.
"""
Expand Down
5 changes: 3 additions & 2 deletions pymemcache/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
import contextlib
import threading
import time
from typing import Callable, Optional, TypeVar, Deque, List, Generic, Iterator
from typing import Callable, Optional, TypeVar, Deque, Generic
from collections.abc import Iterator


T = TypeVar("T")
Expand Down Expand Up @@ -120,7 +121,7 @@ def release(self, obj, silent=True) -> None:

def clear(self) -> None:
if self._after_remove is not None:
needs_destroy: List[T] = []
needs_destroy: list[T] = []
with self._lock:
needs_destroy.extend(self._used_objs)
needs_destroy.extend(self._free_objs)
Expand Down
2 changes: 1 addition & 1 deletion pymemcache/test/test_client_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def make_client(self, mock_socket_values, **kwargs):

# Retry specific tests.
@pytest.mark.unit()
class TestRetryingClient(object):
class TestRetryingClient:
def make_base_client(self, mock_socket_values, **kwargs):
"""Creates a regular mock client to wrap in the RetryClient."""
base_client = Client("localhost", **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.black]
target-version = ['py37', 'py38', 'py39', 'py310']
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']

[tool.mypy]
python_version = 3.7
python_version = 3.9
ignore_missing_imports = true
7 changes: 4 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ keywords = memcache, client, database
classifiers =
Programming Language :: Python
Programming Language :: Python :: 3 :: Only
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
Programming Language :: Python :: 3.13
Programming Language :: Python :: Implementation :: PyPy
License :: OSI Approved :: Apache Software License
Topic :: Database

[options]
python_requires = >= 3.7
python_requires = >= 3.9

[bdist_wheel]
universal = true
Expand Down
7 changes: 4 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[tox]
envlist =
py37,
py38,
py39,
py310,
py311,
py312,
py313,
pypy3,
docs,
lint,
Expand All @@ -13,7 +14,7 @@ skip_missing_interpreters = true

[gh-actions]
python =
pypy-3.7: pypy3
pypy-3.10: pypy3

[testenv]
description = run tests with {basepython}
Expand Down