Skip to content

add: Trio support via AnyIO #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
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 dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ invoke
mock
packaging>=20.4
pytest
pytest-asyncio
pytest-cov
pytest-timeout
trio
ujson>=4.2.0
uvloop
vulture>=2.3.0
Expand Down
2 changes: 0 additions & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ markers =
onlycluster: marks tests to be run only with cluster mode valkey
onlynoncluster: marks tests to be run only with standalone valkey
ssl: marker for only the ssl tests
asyncio: marker for async tests
replica: replica tests
experimental: run only experimental tests
asyncio_mode = auto
timeout = 30
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
async-timeout>=4.0.3; python_version<"3.11.3"
anyio>=4.0.0,<4.6
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
author_email="[email protected]",
python_requires=">=3.8",
install_requires=[
'async-timeout>=4.0.3; python_version<"3.11.3"',
"anyio>=4.0.0,<4.6",
],
classifiers=[
"Development Status :: 5 - Production/Stable",
Expand Down
44 changes: 19 additions & 25 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def build_docs(c):
def linters(c, color=False):
"""Run code linters"""
run(f"flake8 --color {'always' if color else 'never'} tests valkey")
run(f"black {'--color' if color else ''} --target-version py37 --check --diff tests valkey")
run(
f"black {'--color' if color else ''} --target-version py38 --check --diff tests valkey"
)
run(f"isort {'--color' if color else ''} --check-only --diff tests valkey")
run("vulture valkey whitelist.py --min-confidence 80")
run("flynt --fail-on-change --dry-run tests valkey")
Expand All @@ -41,41 +43,33 @@ def all_tests(c, color=False):
tests(c, color=color)


@task
def tests(c, uvloop=False, protocol=2, color=False):
@task(iterable=["async_backend"])
def tests(c, async_backend, protocol=2, color=False):
"""Run the valkey-py test suite against the current python,
with and without libvalkey.
"""
print("Starting Valkey tests")
standalone_tests(c, uvloop=uvloop, protocol=protocol, color=color)
cluster_tests(c, uvloop=uvloop, protocol=protocol, color=color)
standalone_tests(c, async_backend=async_backend, protocol=protocol, color=color)
cluster_tests(c, async_backend=async_backend, protocol=protocol, color=color)


@task
def standalone_tests(c, uvloop=False, protocol=2, color=False):
@task(iterable=["async_backend"])
def standalone_tests(c, async_backend, protocol=2, color=False):
"""Run tests against a standalone valkey instance"""
if uvloop:
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_valkey.xml -W always -m 'not onlycluster' --uvloop --junit-xml=standalone-uvloop-results.xml"
)
else:
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_valkey.xml -W always -m 'not onlycluster' --junit-xml=standalone-results.xml"
)
aopts = f"--async-backend={' '.join(async_backend)}" if async_backend else ""
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_valkey.xml -W always -m 'not onlycluster' {aopts} --junit-xml=standalone-results.xml"
)


@task
def cluster_tests(c, uvloop=False, protocol=2, color=False):
@task(iterable=["async_backend"])
def cluster_tests(c, async_backend, protocol=2, color=False):
"""Run tests against a valkey cluster"""
cluster_url = "valkey://localhost:16379/0"
if uvloop:
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_cluster.xml -W always -m 'not onlynoncluster and not valkeymod' --valkey-url={cluster_url} --junit-xml=cluster-uvloop-results.xml --uvloop"
)
else:
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_clusteclient.xml -W always -m 'not onlynoncluster and not valkeymod' --valkey-url={cluster_url} --junit-xml=cluster-results.xml"
)
aopts = f"--async-backend={' '.join(async_backend)}" if async_backend else ""
run(
f"pytest --color={'yes' if color else 'no'} --protocol={protocol} --cov=./ --cov-report=xml:coverage_clusteclient.xml -W always -m 'not onlynoncluster and not valkeymod' --valkey-url={cluster_url} {aopts} --junit-xml=cluster-results.xml"
)


@task
Expand Down
74 changes: 16 additions & 58 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import math
import time
from typing import Callable, TypeVar
Expand All @@ -7,8 +6,9 @@
from urllib.parse import urlparse

import pytest
import valkey
from packaging.version import Version

import valkey
from valkey import Sentinel
from valkey._parsers import parse_url
from valkey.backoff import NoBackoff
Expand All @@ -29,50 +29,6 @@
_TestDecorator = Callable[[_DecoratedTest], _DecoratedTest]


# Taken from python3.9
class BooleanOptionalAction(argparse.Action):
def __init__(
self,
option_strings,
dest,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None,
):
_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)

if option_string.startswith("--"):
option_string = "--no-" + option_string[2:]
_option_strings.append(option_string)

if help is not None and default is not None:
help += f" (default: {default})"

super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)

def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith("--no-"))

def format_usage(self):
return " | ".join(self.option_strings)


def pytest_addoption(parser):
parser.addoption(
"--valkey-url",
Expand Down Expand Up @@ -104,7 +60,13 @@ def pytest_addoption(parser):
)

parser.addoption(
"--uvloop", action=BooleanOptionalAction, help="Run tests with uvloop"
"--async-backend",
default=[],
action="extend",
Comment on lines +64 to +65
Copy link
Author

@thearchitector thearchitector Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyio's docs suggest (by example) parametrizing the tests by framework, but im not opposed to making this a single option per run if we want to keep it like that

on the invoke side i think it'd still make sense to specify several, but it'd just run the suite N times and dump to separate junit xmls instead of collecting all ~6.5k tests in one file

nargs="*",
type=str,
help="Backend(s) to use for async tests",
choices=("asyncio", "uvloop", "trio"),
)

parser.addoption(
Expand Down Expand Up @@ -159,17 +121,13 @@ def pytest_sessionstart(session):
cluster_nodes = session.config.getoption("--valkey-cluster-nodes")
wait_for_cluster_creation(valkey_url, cluster_nodes)

use_uvloop = session.config.getoption("--uvloop")

if use_uvloop:
try:
import uvloop

uvloop.install()
except ImportError as e:
raise RuntimeError(
"Can not import uvloop, make sure it is installed"
) from e
# store async backends to test against, and which to test by default when none are
# specified
session.config.ASYNC_BACKENDS = session.config.getoption("--async-backend") or [
"asyncio",
# "uvloop",
"trio",
]


def wait_for_cluster_creation(valkey_url, cluster_nodes, timeout=60):
Expand Down
12 changes: 0 additions & 12 deletions tests/test_asyncio/compat.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
import asyncio
from unittest import mock

try:
mock.AsyncMock
except AttributeError:
from unittest import mock

try:
from contextlib import aclosing
except ImportError:
Expand All @@ -17,7 +9,3 @@ async def aclosing(thing):
yield thing
finally:
await thing.aclose()


def create_task(coroutine):
return asyncio.create_task(coroutine)
Loading