From edaf13aa7e7044a99325daec1f5a6711a0ec0165 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Fri, 31 Jul 2026 22:50:13 -0400 Subject: [PATCH 01/13] Update to enable unix sockets for Client() creations: Build to be compatable with all pre-existing usages client.py: - Flag client now uses a port value of 0 to infer a unix socket - holds onto a self._unix boolean (mostly for easy referencing) - Client takes overloads for host/port or path combo - if path is provided, overwrites any host/port value given pool.py: - change self._host to self._target to make clear that it could be IP or path - add self._unix and self._opener values - add _make_opener to MemcachePool: - this controls generating an opener function that will be slightly different for domain/ip sockets - _create_new_conn now calls the self._opener value to open connections simple.py: - add a comment explaining how to use local sockets, and showing that it won't break existing code commands_test.py: - add a test for domain socket setting/setting conftest.py: - build up Params around unix sockets following the existing pattern for IP-based socks - fix host assisnment in mcache_server_docker pool_test.py: - add test for unix socket aquire/release - add a bad path test for local sockets - adjust test_bad_connection MemcachePool creation call to work (the new opener pulls host during creation, but it was being changed inside the test originally) --- aiomcache/client.py | 28 ++++++++++++-- aiomcache/pool.py | 26 +++++++++++-- examples/simple.py | 1 + tests/commands_test.py | 13 +++++++ tests/conftest.py | 84 +++++++++++++++++++++++++++++++++++++++++- tests/pool_test.py | 23 ++++++++++-- 6 files changed, 164 insertions(+), 11 deletions(-) diff --git a/aiomcache/client.py b/aiomcache/client.py index 78896f48..4451603c 100644 --- a/aiomcache/client.py +++ b/aiomcache/client.py @@ -45,7 +45,8 @@ async def wrapper(self: _Client, *args: _P.args, # type: ignore[misc] class FlagClient(Generic[_T]): - def __init__(self, host: str, port: int = 11211, *, + + def __init__(self, host: str, port: int, *, pool_size: int = 2, pool_minsize: Optional[int] = None, conn_args: Optional[Mapping[str, Any]] = None, get_flag_handler: Optional[_GetFlagHandler[_T]] = None, @@ -54,7 +55,7 @@ def __init__(self, host: str, port: int = 11211, *, Creates new Client instance. :param host: memcached host - :param port: memcached port + :param port: memcached port (0 implies local unix socket) :param pool_size: max connection pool size :param pool_minsize: min connection pool size :param conn_args: extra arguments passed to @@ -67,6 +68,8 @@ def __init__(self, host: str, port: int = 11211, *, value to flagged value. Method takes value and must return tuple: (value, flags). """ + self._unix = bool(port) + if not pool_minsize: pool_minsize = pool_size @@ -492,9 +495,28 @@ async def flush_all(self, conn: Connection) -> None: class Client(FlagClient[bytes]): - def __init__(self, host: str, port: int = 11211, *, + @overload + def __init__(self, host: str, port: int, *, + pool_size: int = 2, pool_minsize: Optional[int] = None, + conn_args: Optional[Mapping[str, Any]] = None): + ... + + @overload + def __init__(self, *, path: str, pool_size: int = 2, pool_minsize: Optional[int] = None, conn_args: Optional[Mapping[str, Any]] = None): + ... + + def __init__(self, host: str = '127.0.0.1', port: int = 11211, *, + path: str = '', + pool_size: int = 2, pool_minsize: Optional[int] = None, + conn_args: Optional[Mapping[str, Any]] = None): + + # unlikely to provide host/port with the overloads, but still need to deal with it + if path: + host = path + port = 0 + super().__init__(host, port, pool_size=pool_size, pool_minsize=pool_minsize, conn_args=conn_args, get_flag_handler=None, set_flag_handler=None) diff --git a/aiomcache/pool.py b/aiomcache/pool.py index edee644f..c42d0cdb 100644 --- a/aiomcache/pool.py +++ b/aiomcache/pool.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Mapping, NamedTuple, Optional, Set +from typing import Any, Awaitable, Callable, Mapping, NamedTuple, Optional, Set, Tuple __all__ = ['MemcachePool'] @@ -12,13 +12,15 @@ class Connection(NamedTuple): class MemcachePool: def __init__(self, host: str, port: int, *, minsize: int, maxsize: int, conn_args: Optional[Mapping[str, Any]] = None): - self._host = host + self._target = host + self._unix = not bool(port) self._port = port self._minsize = minsize self._maxsize = maxsize self.conn_args = conn_args or {} self._pool: asyncio.Queue[Connection] = asyncio.Queue() self._in_use: Set[Connection] = set() + self._opener = self._make_opener() async def clear(self) -> None: """Clear pool connections.""" @@ -65,10 +67,26 @@ def release(self, conn: Connection) -> None: else: self._pool.put_nowait(conn) + def _make_opener(self + ) -> Callable[..., + Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]]: + """creates open function that has the same signature for unix/tcp sockets""" + + if self._unix: + def unix_open(str_path: str, _port: int, **kwargs: Any + ) -> Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]: + return asyncio.open_unix_connection(str_path, **kwargs) + return unix_open + + def ip_open(host: str, port: int, **kwargs: Any + ) -> Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]: + return asyncio.open_connection(host, port, **kwargs) + return ip_open + async def _create_new_conn(self) -> Optional[Connection]: if self.size() < self._maxsize: - reader, writer = await asyncio.open_connection( - self._host, self._port, **self.conn_args) + reader, writer = await self._opener( + self._target, self._port, **self.conn_args) if self.size() < self._maxsize: return Connection(reader, writer) else: diff --git a/examples/simple.py b/examples/simple.py index e182d420..16b99019 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -5,6 +5,7 @@ async def hello_aiomcache() -> None: mc = aiomcache.Client("127.0.0.1", 11211) + # can also use Client(path="/var/run/memcached/mc.sock") for unix sockets await mc.set(b"some_key", b"Some value") value = await mc.get(b"some_key") print(value) diff --git a/tests/commands_test.py b/tests/commands_test.py index ae5768e6..cc0f9553 100644 --- a/tests/commands_test.py +++ b/tests/commands_test.py @@ -8,6 +8,7 @@ from aiomcache import Client, FlagClient from aiomcache.exceptions import ClientException, ValidationException +from .conftest import McacheUnixParams from .flag_helper import FlagHelperDemo @@ -427,3 +428,15 @@ async def test_flag_handler_invoked_only_when_expected( assert orig_get_count + 1 == demo_flag_helper.get_invocation_count assert orig_set_count + 1 == demo_flag_helper.set_invocation_count + + +async def test_client_unix_socket_set_get( + mcache_unix_params: McacheUnixParams, +) -> None: + client = Client(path=mcache_unix_params["path"]) + try: + await client.set(b"key", b"value") + v = await client.get(b"key") + assert v == b"value" + finally: + await client.close() diff --git a/tests/conftest.py b/tests/conftest.py index a5832541..0bd1329c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ import contextlib +import os import socket import sys +import tempfile import time import uuid from typing import Any, AsyncIterator, Callable, Iterator, TypedDict @@ -23,6 +25,10 @@ class McacheParams(TypedDict): port: int +class McacheUnixParams(TypedDict): + path: str + + class ServerParams(TypedDict): Id: NotRequired[str] host: str @@ -30,6 +36,12 @@ class ServerParams(TypedDict): mcache_params: McacheParams +class UnixServerParams(TypedDict): + Id: NotRequired[str] + path: str + mcache_unix_params: McacheUnixParams + + mcache_server_option = "localhost" @@ -87,7 +99,7 @@ def mcache_server_docker( # type: ignore[no-any-unimported] container.start() container.reload() net_settings = container.attrs["NetworkSettings"] - host = net_settings["IPAddress"] + host = str(net_settings["Ports"]["11211/tcp"][0]["HostIp"]) port = int(net_settings["Ports"]["11211/tcp"][0]["HostPort"]) mcache_params: McacheParams = {"host": host, "port": port} delay = 0.001 @@ -119,11 +131,74 @@ def mcache_server() -> ServerParams: return mcache_server_actual("localhost") +def mcache_unix_server_actual(path: str) -> UnixServerParams: + return { + "path": path, + "mcache_unix_params": {"path": path} + } + + +@contextlib.contextmanager +def mcache_unix_server_docker( # type: ignore[no-any-unimported] + unused_port: Callable[[], int], docker: docker_mod.Client, session_id: str +) -> Iterator[UnixServerParams]: + sock_dir = tempfile.mkdtemp() + os.chmod(sock_dir, 0o777) # noqa: S103 + sock_path = os.path.join(sock_dir, f"memcached-{session_id}.sock") + container = docker.containers.run( + image='memcached:alpine', + name='memcached-test-unix-{}'.format(session_id), + volumes={sock_dir: {"bind": sock_dir, "mode": "rw"}}, + command=['memcached', '-s', sock_path, '-a', '0766', '-m', '64'], + detach=True, + ) + try: + container.start() + container.reload() + mcache_unix_params: McacheUnixParams = {"path": sock_path} + delay = 0.001 + for _i in range(10): + try: + conn = memcache.Client(sock_path) + conn.get_stats() + break + except Exception: + time.sleep(delay) + delay *= 2 + else: + container.kill() + pytest.fail("Cannot start memcached unix socket") + ret: UnixServerParams = { + "Id": container.id, + "path": sock_path, + "mcache_unix_params": mcache_unix_params + } + time.sleep(.1) + yield ret + finally: + container.kill() + container.remove() + if os.path.exists(sock_path): + os.unlink(sock_path) + os.rmdir(sock_dir) + + +@pytest.fixture(scope='session') +def mcache_unix_server(session_id: str) -> UnixServerParams: + sock_path = '/var/run/memcached/memcached.sock' + return mcache_unix_server_actual(sock_path) + + @pytest.fixture def mcache_params(mcache_server: ServerParams) -> McacheParams: return mcache_server["mcache_params"] +@pytest.fixture +def mcache_unix_params(mcache_unix_server: McacheUnixParams) -> McacheUnixParams: + return mcache_unix_server + + @pytest.fixture async def mcache(mcache_params: McacheParams) -> AsyncIterator[aiomcache.Client]: client = aiomcache.Client(**mcache_params) @@ -131,6 +206,13 @@ async def mcache(mcache_params: McacheParams) -> AsyncIterator[aiomcache.Client] await client.close() +@pytest.fixture +async def mcache_unix(mcache_unix_params: McacheUnixParams) -> AsyncIterator[aiomcache.Client]: + client = aiomcache.Client(path=mcache_unix_params["path"]) + yield client + await client.close() + + test_only_demo_flag_helper = FlagHelperDemo() diff --git a/tests/pool_test.py b/tests/pool_test.py index bc571067..c0d34d83 100644 --- a/tests/pool_test.py +++ b/tests/pool_test.py @@ -6,7 +6,7 @@ from aiomcache.client import Client, acquire from aiomcache.pool import Connection, MemcachePool -from .conftest import McacheParams +from .conftest import McacheParams, McacheUnixParams async def test_pool_creation(mcache_params: McacheParams) -> None: @@ -140,8 +140,7 @@ async def test_0_minsize(mcache_params: McacheParams) -> None: async def test_bad_connection(mcache_params: McacheParams) -> None: - pool = MemcachePool(minsize=5, maxsize=1, **mcache_params) - pool._host = "INVALID_HOST" + pool = MemcachePool("INVALID_HOST", 11211, minsize=5, maxsize=1) assert pool.size() == 0 with pytest.raises(socket.gaierror): conn = await pool.acquire() @@ -149,3 +148,21 @@ async def test_bad_connection(mcache_params: McacheParams) -> None: assert isinstance(conn.writer, asyncio.StreamWriter) pool.release(conn) assert pool.size() == 0 + + +async def test_pool_unix_socket_acquire_release( + mcache_unix_params: McacheUnixParams, +) -> None: + pool = MemcachePool(mcache_unix_params["path"], 0, minsize=1, maxsize=5) + conn = await pool.acquire() + assert isinstance(conn.reader, asyncio.StreamReader) + assert isinstance(conn.writer, asyncio.StreamWriter) + pool.release(conn) + await pool.clear() + + +async def test_pool_unix_socket_bad_path() -> None: + pool = MemcachePool("/tmp/nonexistent-mc.sock", 0, minsize=1, maxsize=1) # noqa: S108 + assert pool.size() == 0 + with pytest.raises(FileNotFoundError): + await pool.acquire() From 34aeb517756026800e197febd19bf4ecd64066dc Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Sun, 2 Aug 2026 20:08:08 -0400 Subject: [PATCH 02/13] Adjust unix socket indicator in FlagClient from 0 to -1: - Adjust FlagClient __init__ method directly - Change path overrides in Client's __init__ method - Update MemcachePool to check port for -1 setting - Adjust tests in pool test for -1 port setting --- aiomcache/client.py | 8 +++++--- aiomcache/pool.py | 2 +- tests/pool_test.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/aiomcache/client.py b/aiomcache/client.py index 4451603c..4153dd03 100644 --- a/aiomcache/client.py +++ b/aiomcache/client.py @@ -55,7 +55,7 @@ def __init__(self, host: str, port: int, *, Creates new Client instance. :param host: memcached host - :param port: memcached port (0 implies local unix socket) + :param port: memcached port (-1 implies local unix socket) :param pool_size: max connection pool size :param pool_minsize: min connection pool size :param conn_args: extra arguments passed to @@ -68,7 +68,9 @@ def __init__(self, host: str, port: int, *, value to flagged value. Method takes value and must return tuple: (value, flags). """ - self._unix = bool(port) + self._unix = False + if port == -1: + self._unix = True if not pool_minsize: pool_minsize = pool_size @@ -515,7 +517,7 @@ def __init__(self, host: str = '127.0.0.1', port: int = 11211, *, # unlikely to provide host/port with the overloads, but still need to deal with it if path: host = path - port = 0 + port = -1 super().__init__(host, port, pool_size=pool_size, pool_minsize=pool_minsize, conn_args=conn_args, diff --git a/aiomcache/pool.py b/aiomcache/pool.py index c42d0cdb..87638898 100644 --- a/aiomcache/pool.py +++ b/aiomcache/pool.py @@ -13,7 +13,7 @@ class MemcachePool: def __init__(self, host: str, port: int, *, minsize: int, maxsize: int, conn_args: Optional[Mapping[str, Any]] = None): self._target = host - self._unix = not bool(port) + self._unix = port == -1 self._port = port self._minsize = minsize self._maxsize = maxsize diff --git a/tests/pool_test.py b/tests/pool_test.py index c0d34d83..8e2716af 100644 --- a/tests/pool_test.py +++ b/tests/pool_test.py @@ -153,7 +153,7 @@ async def test_bad_connection(mcache_params: McacheParams) -> None: async def test_pool_unix_socket_acquire_release( mcache_unix_params: McacheUnixParams, ) -> None: - pool = MemcachePool(mcache_unix_params["path"], 0, minsize=1, maxsize=5) + pool = MemcachePool(mcache_unix_params["path"], -1, minsize=1, maxsize=5) conn = await pool.acquire() assert isinstance(conn.reader, asyncio.StreamReader) assert isinstance(conn.writer, asyncio.StreamWriter) @@ -162,7 +162,7 @@ async def test_pool_unix_socket_acquire_release( async def test_pool_unix_socket_bad_path() -> None: - pool = MemcachePool("/tmp/nonexistent-mc.sock", 0, minsize=1, maxsize=1) # noqa: S108 + pool = MemcachePool("/tmp/nonexistent-mc.sock", -1, minsize=1, maxsize=1) # noqa: S108 assert pool.size() == 0 with pytest.raises(FileNotFoundError): await pool.acquire() From 0bf1aefd9515254577c94b410a9f564f7917f61e Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Mon, 3 Aug 2026 16:02:14 -0400 Subject: [PATCH 03/13] drop opener: - move to in-line if/else check to open connection - drop the make_opener and self._opener logic because it is not needed with direct unix param check --- aiomcache/pool.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/aiomcache/pool.py b/aiomcache/pool.py index 87638898..89b6deff 100644 --- a/aiomcache/pool.py +++ b/aiomcache/pool.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Awaitable, Callable, Mapping, NamedTuple, Optional, Set, Tuple +from typing import Any, Mapping, NamedTuple, Optional, Set __all__ = ['MemcachePool'] @@ -20,7 +20,6 @@ def __init__(self, host: str, port: int, *, minsize: int, maxsize: int, self.conn_args = conn_args or {} self._pool: asyncio.Queue[Connection] = asyncio.Queue() self._in_use: Set[Connection] = set() - self._opener = self._make_opener() async def clear(self) -> None: """Clear pool connections.""" @@ -67,26 +66,14 @@ def release(self, conn: Connection) -> None: else: self._pool.put_nowait(conn) - def _make_opener(self - ) -> Callable[..., - Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]]: - """creates open function that has the same signature for unix/tcp sockets""" - - if self._unix: - def unix_open(str_path: str, _port: int, **kwargs: Any - ) -> Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]: - return asyncio.open_unix_connection(str_path, **kwargs) - return unix_open - - def ip_open(host: str, port: int, **kwargs: Any - ) -> Awaitable[Tuple[asyncio.StreamReader, asyncio.StreamWriter]]: - return asyncio.open_connection(host, port, **kwargs) - return ip_open - async def _create_new_conn(self) -> Optional[Connection]: if self.size() < self._maxsize: - reader, writer = await self._opener( - self._target, self._port, **self.conn_args) + if self._unix: + reader, writer = await asyncio.open_unix_connection(self._target, **self.conn_args) + else: + reader, writer = await asyncio.open_connection(self._target, self._port, + **self.conn_args) + if self.size() < self._maxsize: return Connection(reader, writer) else: From 7860fcc6ba4eaefd64f84ede4840de4cf57ae256 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Mon, 3 Aug 2026 22:54:53 -0400 Subject: [PATCH 04/13] add new memcached-local action --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 715d9468..9ebfd2e7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -70,6 +70,8 @@ jobs: pip install codecov - name: Run memcached service uses: jkeys089/actions-memcached@master + - name: Run local memcached service + uses: Kropyls/actions-memcached-local@master - name: Run tests run: pytest - run: python -m coverage xml From 81cc53d65f777f0e859ad929647104a7a49a0905 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Tue, 4 Aug 2026 09:02:54 -0400 Subject: [PATCH 05/13] fix cicd testing: - start memcached directly via cli args, reducing dependancy on external code - Adjust conftest.py to line up with new path changes - Add a comment about not using privatetmp since we are looking at the /tmp directory for the socket path --- .github/workflows/ci.yaml | 10 ++++++---- tests/conftest.py | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9ebfd2e7..afc3d753 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -68,10 +68,12 @@ jobs: pip install -U setuptools pip install -r requirements.txt pip install codecov - - name: Run memcached service - uses: jkeys089/actions-memcached@master - - name: Run local memcached service - uses: Kropyls/actions-memcached-local@master + - name: Run memcached + run: | + sudo apt-get install -y memcached + sudo systemctl disable --now memcached + memcached -d -p 11211 -l 127.0.0.1 + memcached -d -s /tmp/memcached.sock - name: Run tests run: pytest - run: python -m coverage xml diff --git a/tests/conftest.py b/tests/conftest.py index 0bd1329c..339d5a16 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -185,7 +185,8 @@ def mcache_unix_server_docker( # type: ignore[no-any-unimported] @pytest.fixture(scope='session') def mcache_unix_server(session_id: str) -> UnixServerParams: - sock_path = '/var/run/memcached/memcached.sock' + # if starting memcached via systemd, ensure privatetmp is not on + sock_path = '/tmp/memcached.sock' # noqa: S108 return mcache_unix_server_actual(sock_path) From 2608cac849854799ce830232f4787c1ef8cdc8d3 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Tue, 4 Aug 2026 09:12:36 -0400 Subject: [PATCH 06/13] syntax fix --- .github/workflows/ci.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index afc3d753..75256c16 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -68,12 +68,12 @@ jobs: pip install -U setuptools pip install -r requirements.txt pip install codecov - - name: Run memcached - run: | - sudo apt-get install -y memcached - sudo systemctl disable --now memcached - memcached -d -p 11211 -l 127.0.0.1 - memcached -d -s /tmp/memcached.sock + - name: Run memcached + run: | + sudo apt-get install -y memcached + sudo systemctl disable --now memcached + memcached -d -p 11211 -l 127.0.0.1 + memcached -d -s /tmp/memcached.sock - name: Run tests run: pytest - run: python -m coverage xml From 22fe03851da2603189fd48b0fe070e33d166872c Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 5 Aug 2026 01:13:48 +0100 Subject: [PATCH 07/13] Apply suggestion from @Dreamsorcerer --- aiomcache/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aiomcache/client.py b/aiomcache/client.py index 4153dd03..0f98762b 100644 --- a/aiomcache/client.py +++ b/aiomcache/client.py @@ -45,7 +45,6 @@ async def wrapper(self: _Client, *args: _P.args, # type: ignore[misc] class FlagClient(Generic[_T]): - def __init__(self, host: str, port: int, *, pool_size: int = 2, pool_minsize: Optional[int] = None, conn_args: Optional[Mapping[str, Any]] = None, From 78f3038dc6c94fe1ab001842378df372878f859a Mon Sep 17 00:00:00 2001 From: Matt Cunningham <77254015+Kropyls@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:41:40 -0400 Subject: [PATCH 08/13] Update tests/pool_test.py drop unneeded input to test_bad_connection Co-authored-by: Sam Bull --- tests/pool_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pool_test.py b/tests/pool_test.py index 8e2716af..1b5506c3 100644 --- a/tests/pool_test.py +++ b/tests/pool_test.py @@ -139,7 +139,7 @@ async def test_0_minsize(mcache_params: McacheParams) -> None: await pool.clear() -async def test_bad_connection(mcache_params: McacheParams) -> None: +async def test_bad_connection() -> None: pool = MemcachePool("INVALID_HOST", 11211, minsize=5, maxsize=1) assert pool.size() == 0 with pytest.raises(socket.gaierror): From f082ad7d93f4d42876205d1616be11e5583c8ec0 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Tue, 4 Aug 2026 22:10:43 -0400 Subject: [PATCH 09/13] Improvements to unix test objects: - use TemporaryDirectory inside with block to drop os import - Move os file manipulators to pathlib to support dropping os import - Fix arguments for mcache_unix_params --- tests/conftest.py | 80 +++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 339d5a16..3b2eca23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,10 @@ import contextlib -import os import socket import sys import tempfile import time import uuid +from pathlib import PosixPath from typing import Any, AsyncIterator, Callable, Iterator, TypedDict import docker as docker_mod @@ -142,45 +142,43 @@ def mcache_unix_server_actual(path: str) -> UnixServerParams: def mcache_unix_server_docker( # type: ignore[no-any-unimported] unused_port: Callable[[], int], docker: docker_mod.Client, session_id: str ) -> Iterator[UnixServerParams]: - sock_dir = tempfile.mkdtemp() - os.chmod(sock_dir, 0o777) # noqa: S103 - sock_path = os.path.join(sock_dir, f"memcached-{session_id}.sock") - container = docker.containers.run( - image='memcached:alpine', - name='memcached-test-unix-{}'.format(session_id), - volumes={sock_dir: {"bind": sock_dir, "mode": "rw"}}, - command=['memcached', '-s', sock_path, '-a', '0766', '-m', '64'], - detach=True, - ) - try: - container.start() - container.reload() - mcache_unix_params: McacheUnixParams = {"path": sock_path} - delay = 0.001 - for _i in range(10): - try: - conn = memcache.Client(sock_path) - conn.get_stats() - break - except Exception: - time.sleep(delay) - delay *= 2 - else: + with tempfile.TemporaryDirectory() as sock_dir: + sock_parent = PosixPath(sock_dir) + sock_parent.chmod(0o777) # noqa: S103 + sock_path = sock_parent / f"memcached-{session_id}.sock" + container = docker.containers.run( + image='memcached:alpine', + name='memcached-test-unix-{}'.format(session_id), + volumes={sock_dir: {"bind": sock_dir, "mode": "rw"}}, + command=['memcached', '-s', sock_path, '-a', '0766', '-m', '64'], + detach=True, + ) + try: + container.start() + container.reload() + mcache_unix_params: McacheUnixParams = {"path": sock_path.as_posix()} + delay = 0.001 + for _i in range(10): + try: + conn = memcache.Client(sock_path) + conn.get_stats() + break + except Exception: + time.sleep(delay) + delay *= 2 + else: + container.kill() + pytest.fail("Cannot start memcached unix socket") + ret: UnixServerParams = { + "Id": container.id, + "path": sock_path.as_posix(), + "mcache_unix_params": mcache_unix_params + } + time.sleep(.1) + yield ret + finally: container.kill() - pytest.fail("Cannot start memcached unix socket") - ret: UnixServerParams = { - "Id": container.id, - "path": sock_path, - "mcache_unix_params": mcache_unix_params - } - time.sleep(.1) - yield ret - finally: - container.kill() - container.remove() - if os.path.exists(sock_path): - os.unlink(sock_path) - os.rmdir(sock_dir) + container.remove() @pytest.fixture(scope='session') @@ -196,8 +194,8 @@ def mcache_params(mcache_server: ServerParams) -> McacheParams: @pytest.fixture -def mcache_unix_params(mcache_unix_server: McacheUnixParams) -> McacheUnixParams: - return mcache_unix_server +def mcache_unix_params(mcache_unix_server: UnixServerParams) -> McacheUnixParams: + return mcache_unix_server["mcache_unix_params"] @pytest.fixture From f94079d948663231d3a534e52c6009e7cd04c451 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Wed, 5 Aug 2026 20:58:20 -0400 Subject: [PATCH 10/13] remove unneeded self_unix var in FlagClient --- aiomcache/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aiomcache/client.py b/aiomcache/client.py index 0f98762b..db905801 100644 --- a/aiomcache/client.py +++ b/aiomcache/client.py @@ -67,9 +67,6 @@ def __init__(self, host: str, port: int, *, value to flagged value. Method takes value and must return tuple: (value, flags). """ - self._unix = False - if port == -1: - self._unix = True if not pool_minsize: pool_minsize = pool_size From 871530c38f53be0793e217f13b6824ef97229ce9 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Wed, 5 Aug 2026 20:58:56 -0400 Subject: [PATCH 11/13] remove self._unix since port already contains unix informatin --- aiomcache/pool.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aiomcache/pool.py b/aiomcache/pool.py index 89b6deff..55a1e266 100644 --- a/aiomcache/pool.py +++ b/aiomcache/pool.py @@ -13,7 +13,6 @@ class MemcachePool: def __init__(self, host: str, port: int, *, minsize: int, maxsize: int, conn_args: Optional[Mapping[str, Any]] = None): self._target = host - self._unix = port == -1 self._port = port self._minsize = minsize self._maxsize = maxsize @@ -68,7 +67,7 @@ def release(self, conn: Connection) -> None: async def _create_new_conn(self) -> Optional[Connection]: if self.size() < self._maxsize: - if self._unix: + if self._port == -1: # -1 implies unix socket reader, writer = await asyncio.open_unix_connection(self._target, **self.conn_args) else: reader, writer = await asyncio.open_connection(self._target, self._port, From 1d6760822870032ede2643e0290ac98039c8e034 Mon Sep 17 00:00:00 2001 From: Matt Cunningham Date: Wed, 5 Aug 2026 20:59:18 -0400 Subject: [PATCH 12/13] drop unused docker functions --- tests/conftest.py | 92 +---------------------------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3b2eca23..aa40d7e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,9 @@ -import contextlib import socket import sys -import tempfile -import time import uuid -from pathlib import PosixPath -from typing import Any, AsyncIterator, Callable, Iterator, TypedDict +from typing import Any, AsyncIterator, Callable, TypedDict import docker as docker_mod -import memcache import pytest import aiomcache @@ -84,48 +79,6 @@ def mcache_server_actual(host: str, port: int = 11211) -> ServerParams: } -@contextlib.contextmanager -def mcache_server_docker( # type: ignore[no-any-unimported] - unused_port: Callable[[], int], docker: docker_mod.Client, session_id: str -) -> Iterator[ServerParams]: - docker.images.pull("memcached:alpine") - container = docker.containers.run( - image='memcached:alpine', - name='memcached-test-server-{}'.format(session_id), - ports={"11211/tcp": None}, - detach=True, - ) - try: - container.start() - container.reload() - net_settings = container.attrs["NetworkSettings"] - host = str(net_settings["Ports"]["11211/tcp"][0]["HostIp"]) - port = int(net_settings["Ports"]["11211/tcp"][0]["HostPort"]) - mcache_params: McacheParams = {"host": host, "port": port} - delay = 0.001 - for _i in range(10): - try: - conn = memcache.Client(["{host}:{port}".format_map(mcache_params)]) - conn.get_stats() - break - except Exception: - time.sleep(delay) - delay *= 2 - else: - pytest.fail("Cannot start memcached") - ret: ServerParams = { - "Id": container.id, - "host": host, - "port": port, - "mcache_params": mcache_params - } - time.sleep(0.1) - yield ret - finally: - container.kill() - container.remove() - - @pytest.fixture(scope='session') def mcache_server() -> ServerParams: return mcache_server_actual("localhost") @@ -138,49 +91,6 @@ def mcache_unix_server_actual(path: str) -> UnixServerParams: } -@contextlib.contextmanager -def mcache_unix_server_docker( # type: ignore[no-any-unimported] - unused_port: Callable[[], int], docker: docker_mod.Client, session_id: str -) -> Iterator[UnixServerParams]: - with tempfile.TemporaryDirectory() as sock_dir: - sock_parent = PosixPath(sock_dir) - sock_parent.chmod(0o777) # noqa: S103 - sock_path = sock_parent / f"memcached-{session_id}.sock" - container = docker.containers.run( - image='memcached:alpine', - name='memcached-test-unix-{}'.format(session_id), - volumes={sock_dir: {"bind": sock_dir, "mode": "rw"}}, - command=['memcached', '-s', sock_path, '-a', '0766', '-m', '64'], - detach=True, - ) - try: - container.start() - container.reload() - mcache_unix_params: McacheUnixParams = {"path": sock_path.as_posix()} - delay = 0.001 - for _i in range(10): - try: - conn = memcache.Client(sock_path) - conn.get_stats() - break - except Exception: - time.sleep(delay) - delay *= 2 - else: - container.kill() - pytest.fail("Cannot start memcached unix socket") - ret: UnixServerParams = { - "Id": container.id, - "path": sock_path.as_posix(), - "mcache_unix_params": mcache_unix_params - } - time.sleep(.1) - yield ret - finally: - container.kill() - container.remove() - - @pytest.fixture(scope='session') def mcache_unix_server(session_id: str) -> UnixServerParams: # if starting memcached via systemd, ensure privatetmp is not on From 465017b8250fc4ab9b893138137c01faf287c27c Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 6 Aug 2026 18:19:05 +0100 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Sam Bull --- aiomcache/client.py | 1 - examples/simple.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/aiomcache/client.py b/aiomcache/client.py index db905801..27a92fa8 100644 --- a/aiomcache/client.py +++ b/aiomcache/client.py @@ -67,7 +67,6 @@ def __init__(self, host: str, port: int, *, value to flagged value. Method takes value and must return tuple: (value, flags). """ - if not pool_minsize: pool_minsize = pool_size diff --git a/examples/simple.py b/examples/simple.py index 16b99019..39ae898e 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -5,7 +5,7 @@ async def hello_aiomcache() -> None: mc = aiomcache.Client("127.0.0.1", 11211) - # can also use Client(path="/var/run/memcached/mc.sock") for unix sockets + # Can also use Client(path="/var/run/memcached/mc.sock") for unix sockets await mc.set(b"some_key", b"Some value") value = await mc.get(b"some_key") print(value)