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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,24 @@ jobs:
- run: pip install tox

- run: tox -e redis

valkey-tox:
runs-on: ubuntu-latest

container: python:3.13

services:
valkey:
image: valkey/valkey
options: >-
--health-cmd "valkey-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- run: pip install tox

- run: tox -e valkey
21 changes: 20 additions & 1 deletion docs/how_to_guides/store.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ multiple instances together will allow a higher overall limit. To
avoid this you can centralise the store so that all instances use the
same store and hence limits.

Redis store
-----------

Quart-Rate-Limiter has a builtin interface to use a redis store which
can be used as so,
can be used after installing Quart-Rate-Limiter with the ``redis``
extension, ``pip install quart-rate-limiter[redis]``, as so,

.. code-block:: python

Expand All @@ -17,6 +21,21 @@ can be used as so,
redis_store = RedisStore("address")
RateLimiter(app, store=redis_store)


Valkey store
------------

Quart-Rate-Limiter has a builtin interface to use a valkey store which
can be used after installing Quart-Rate-Limiter with the ``valkey``
extension, ``pip install quart-rate-limiter[valkey]``, as so,

.. code-block:: python

from quart_rate_limiter.valkey_store import valkeyStore

valkey_store = valkeyStore("address")
RateLimiter(app, store=valkey_store)

Custom store
------------

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ requires-python = ">=3.9"
[project.optional-dependencies]
docs = ["pydata_sphinx_theme"]
redis = ["redis >= 4.4.0"]
valkey = ["valkey"]

[tool.black]
line-length = 100
Expand Down
37 changes: 37 additions & 0 deletions src/quart_rate_limiter/valkey_store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from datetime import datetime
from typing import Any, Optional

import valkey.asyncio as valkey

from .store import RateLimiterStoreABC


class ValkeyStore(RateLimiterStoreABC):
"""An Valkey backed store of rate limits.

Arguments:
address: The address of the valkey instance.
kwargs: Any keyword arguments to pass to the valkey client on
creation, see the valkey-py documentation.
"""

def __init__(self, address: str, **kwargs: Any) -> None:
self._valkey: Optional[valkey.Valkey] = None
self._valkey_arguments = (address, kwargs)

async def before_serving(self) -> None:
self._valkey = valkey.from_url(self._valkey_arguments[0], **self._valkey_arguments[1])

async def get(self, key: str, default: datetime) -> datetime:
result = await self._valkey.get(key)
if result is None:
return default
else:
return datetime.fromtimestamp(float(result))

async def set(self, key: str, tat: datetime) -> None:
await self._valkey.set(key, tat.timestamp())

async def after_serving(self) -> None:
await self._valkey.aclose()
self._valkey = None
11 changes: 8 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
from quart_rate_limiter import rate_limit, RateLimiter
from quart_rate_limiter.redis_store import RedisStore
from quart_rate_limiter.store import MemoryStore, RateLimiterStoreABC
from quart_rate_limiter.valkey_store import ValkeyStore


def pytest_addoption(parser: Parser) -> None:
parser.addoption("--redis-host", action="store", default=None)
parser.addoption("--valkey-host", action="store", default=None)


@pytest.fixture(name="fixed_datetime")
Expand Down Expand Up @@ -44,10 +46,13 @@ async def index() -> ResponseReturnValue:

store: RateLimiterStoreABC
redis_host = pytestconfig.getoption("redis_host")
if redis_host is None:
store = MemoryStore()
else:
valkey_host = pytestconfig.getoption("valkey_host")
if redis_host is not None:
store = RedisStore(f"redis://{redis_host}")
elif valkey_host is not None:
store = ValkeyStore(f"redis://{valkey_host}")
else:
store = MemoryStore()

RateLimiter(app, store=store, skip_function=_skip_function)
async with app.test_app():
Expand Down
14 changes: 14 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ deps =
pytest-asyncio
pytest-cov
pytest-sugar
valkey
commands = pytest --cov=quart_rate_limiter {posargs}

[testenv:redis]
Expand All @@ -19,8 +20,20 @@ deps =
pytest-asyncio
pytest-cov
pytest-sugar
valkey
commands = pytest --cov=quart_rate_limiter --redis-host="redis" {posargs}

[testenv:valkey]
basepython = python3.13
deps =
redis
pytest
pytest-asyncio
pytest-cov
pytest-sugar
valkey
commands = pytest --cov=quart_rate_limiter --valkey-host="valkey" {posargs}

[testenv:docs]
basepython = python3.13
deps =
Expand Down Expand Up @@ -53,6 +66,7 @@ deps =
redis
mypy
pytest
valkey
commands =
mypy src/quart_rate_limiter/ tests/

Expand Down