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
8 changes: 6 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +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 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
Expand Down
25 changes: 22 additions & 3 deletions aiomcache/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ 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,
Expand All @@ -54,7 +54,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 (-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
Expand Down Expand Up @@ -492,9 +492,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 = -1

super().__init__(host, port, pool_size=pool_size, pool_minsize=pool_minsize,
conn_args=conn_args,
get_flag_handler=None, set_flag_handler=None)
10 changes: 7 additions & 3 deletions aiomcache/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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._port = port
self._minsize = minsize
self._maxsize = maxsize
Expand Down Expand Up @@ -67,8 +67,12 @@ def release(self, conn: Connection) -> None:

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)
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,
**self.conn_args)

if self.size() < self._maxsize:
return Connection(reader, writer)
else:
Expand Down
1 change: 1 addition & 0 deletions examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions tests/commands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from aiomcache import Client, FlagClient
from aiomcache.exceptions import ClientException, ValidationException
from .conftest import McacheUnixParams
from .flag_helper import FlagHelperDemo


Expand Down Expand Up @@ -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()
83 changes: 37 additions & 46 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import contextlib
import socket
import sys
import time
import uuid
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
Expand All @@ -23,13 +20,23 @@ class McacheParams(TypedDict):
port: int


class McacheUnixParams(TypedDict):
path: str


class ServerParams(TypedDict):
Id: NotRequired[str]
host: str
port: int
mcache_params: McacheParams


class UnixServerParams(TypedDict):
Id: NotRequired[str]
path: str
mcache_unix_params: McacheUnixParams


mcache_server_option = "localhost"


Expand Down Expand Up @@ -72,65 +79,49 @@ 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 = net_settings["IPAddress"]
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")


def mcache_unix_server_actual(path: str) -> UnixServerParams:
Comment thread
Kropyls marked this conversation as resolved.
return {
"path": path,
"mcache_unix_params": {"path": path}
}


@pytest.fixture(scope='session')
def mcache_unix_server(session_id: str) -> UnixServerParams:
# if starting memcached via systemd, ensure privatetmp is not on
sock_path = '/tmp/memcached.sock' # noqa: S108
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: UnixServerParams) -> McacheUnixParams:
return mcache_unix_server["mcache_unix_params"]


@pytest.fixture
async def mcache(mcache_params: McacheParams) -> AsyncIterator[aiomcache.Client]:
client = aiomcache.Client(**mcache_params)
yield 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()


Expand Down
25 changes: 21 additions & 4 deletions tests/pool_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -139,13 +139,30 @@ async def test_0_minsize(mcache_params: McacheParams) -> None:
await pool.clear()


async def test_bad_connection(mcache_params: McacheParams) -> None:
pool = MemcachePool(minsize=5, maxsize=1, **mcache_params)
pool._host = "INVALID_HOST"
async def test_bad_connection() -> None:
pool = MemcachePool("INVALID_HOST", 11211, minsize=5, maxsize=1)
assert pool.size() == 0
with pytest.raises(socket.gaierror):
conn = await pool.acquire()
assert isinstance(conn.reader, asyncio.StreamReader)
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"], -1, 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", -1, minsize=1, maxsize=1) # noqa: S108
assert pool.size() == 0
with pytest.raises(FileNotFoundError):
await pool.acquire()
Loading