diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06ad2e1..ccfd37c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,61 +15,59 @@ jobs: fail-fast: false matrix: python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" + - "3.13" services: - redis: - image: redis + valkey: + image: valkey/valkey ports: - 6379:6379 options: >- - --health-cmd "redis-cli ping" + --health-cmd "valkey-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 sentinel: - image: bitnami/redis-sentinel + image: bitnami/valkey-sentinel ports: - 26379:26379 options: >- - --health-cmd "redis-cli -p 26379 ping" + --health-cmd "valkey-cli -p 26379 ping" --health-interval 10s --health-timeout 5s --health-retries 5 env: - REDIS_MASTER_HOST: redis - REDIS_MASTER_SET: sentinel - REDIS_SENTINEL_QUORUM: "1" - REDIS_SENTINEL_PASSWORD: channels_redis +# VALKEY_MASTER_HOST: valkey +# VALKEY_MASTER_SET: sentinel + VALKEY_SENTINEL_QUORUM: "1" + VALKEY_SENTINEL_PASSWORD: channels_valkey steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install poetry + uses: abatilo/actions-poetry@v3 + - name: Install dependencies run: | - python -m pip install --upgrade pip wheel setuptools tox + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + poetry install --no-interaction --all-extras --with dev + - name: Run tox targets for ${{ matrix.python-version }} run: | ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") - TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox + TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') poetry run tox lint: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - name: Install dependencies - run: | - python -m pip install --upgrade pip tox - - name: Run lint - run: tox -e qa + - uses: actions/checkout@v4 + - uses: chartboost/ruff-actions@v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index bf5a3e1..21209d0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ .pytest_cache .vscode .idea +poetry.lock \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..502225e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/adamchainz/django-upgrade + rev: 1.21.0 + hooks: + - id: django-upgrade + args: [--target-version, "5.1"] + + - repo: https://github.com/psf/black + rev: 24.8.0 + hooks: + - id: black + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.8 + hooks: + - id: ruff diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a6de0b3..0405843 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +0.1.0 +----- + +* package forked from channels_redis and switched to work as a valkey backend + +channels_redis: + 4.2.0 (2024-01-12) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 1aba38f..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include LICENSE diff --git a/README.rst b/README.rst index 2fa5626..cf3990e 100644 --- a/README.rst +++ b/README.rst @@ -1,26 +1,25 @@ -channels_redis +channels_valkey ============== -.. image:: https://github.com/django/channels_redis/workflows/Tests/badge.svg - :target: https://github.com/django/channels_redis/actions?query=workflow%3ATests - .. image:: https://img.shields.io/pypi/v/channels_redis.svg - :target: https://pypi.python.org/pypi/channels_redis + :target: https://pypi.python.org/pypi/channels_valkey + +Provides Django Channels channel layers that use Valkey as a backing store. -Provides Django Channels channel layers that use Redis as a backing store. +this is a fork of the wonderful ``channels-redis`` project. There are two available implementations: -* ``RedisChannelLayer`` is the original layer, and implements channel and group +* ``ValkeyChannelLayer`` is the original layer, and implements channel and group handling itself. -* ``RedisPubSubChannelLayer`` is newer and leverages Redis Pub/Sub for message +* ``ValkeyPubSubChannelLayer`` is newer and leverages Valkey Pub/Sub for message dispatch. This layer is currently at *Beta* status, meaning it may be subject to breaking changes whilst it matures. Both layers support a single-server and sharded configurations. -`channels_redis` is tested against Python 3.8 to 3.12, `redis-py` versions 4.6, -5.0, and the development branch, and Channels versions 3, 4 and the development +`channels_valkey` is tested against Python 3.9 to 3.13, `valkey-py` versions 6.x, +and the development branch, and Channels versions 3, 4 and the development branch there. Installation @@ -28,12 +27,7 @@ Installation .. code-block:: - pip install channels-redis - -**Note:** Prior versions of this package were called ``asgi_redis`` and are -still available under PyPI as that name if you need them for Channels 1.x projects. -This package is for Channels 2 projects only. - + pip install channels-valkey Usage ----- @@ -44,20 +38,20 @@ Set up the channel layer in your Django settings file like so: CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", + "BACKEND": "channels_valkey.core.ValkeyChannelLayer", "CONFIG": { "hosts": [("localhost", 6379)], }, }, } -Or, you can use the alternate implementation which uses Redis Pub/Sub: +Or, you can use the alternate implementation which uses Valkey Pub/Sub: .. code-block:: python CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", + "BACKEND": "channels_valkey.pubsub.ValkeyPubSubChannelLayer", "CONFIG": { "hosts": [("localhost", 6379)], }, @@ -69,8 +63,8 @@ Possible options for ``CONFIG`` are listed below. ``hosts`` ~~~~~~~~~ -The server(s) to connect to, as either URIs, ``(host, port)`` tuples, or dicts conforming to `redis Connection `_. -Defaults to ``redis://localhost:6379``. Pass multiple hosts to enable sharding, +The server(s) to connect to, as either URIs, ``(host, port)`` tuples, or dicts conforming to `valkey Connection `_. +Defaults to ``valkey://localhost:6379``. Pass multiple hosts to enable sharding, but note that changing the host list will lose some sharded data. SSL connections that are self-signed (ex: Heroku): @@ -78,10 +72,10 @@ SSL connections that are self-signed (ex: Heroku): .. code-block:: python "default": { - "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", + "BACKEND": "channels_valkey.pubsub.ValkeyPubSubChannelLayer", "CONFIG": { "hosts":[{ - "address": "rediss://user@host:port", # "REDIS_TLS_URL" + "address": "valkeys://user@host:port", # "VALKEY_TLS_URL" "ssl_cert_reqs": None, }] } @@ -89,7 +83,7 @@ SSL connections that are self-signed (ex: Heroku): Sentinel connections require dicts conforming to: -.. code-block:: +.. code-block:: python { "sentinels": [ @@ -99,17 +93,17 @@ Sentinel connections require dicts conforming to: **kwargs } -note the additional ``master_name`` key specifying the Sentinel master set and any additional connection kwargs can also be passed. Plain Redis and Sentinel connections can be mixed and matched if +note the additional ``master_name`` key specifying the Sentinel master set and any additional connection kwargs can also be passed. Plain Valkey and Sentinel connections can be mixed and matched if sharding. -If your server is listening on a UNIX domain socket, you can also use that to connect: ``["unix:///path/to/redis.sock"]``. +If your server is listening on a UNIX domain socket, you can also use that to connect: ``["unix:///path/to/valkey.sock"]``. This should be slightly faster than a loopback TCP connection. ``prefix`` ~~~~~~~~~~ -Prefix to add to all Redis keys. Defaults to ``asgi``. If you're running -two or more entirely separate channel layers through the same Redis instance, +Prefix to add to all Valkey keys. Defaults to ``asgi``. If you're running +two or more entirely separate channel layers through the same Valkey instance, make sure they have different prefixes. All servers talking to the same layer should have the same prefix, though. @@ -146,7 +140,7 @@ Per-channel capacity configuration. This lets you tweak the channel capacity based on the channel name, and supports both globbing and regular expressions. It should be a dict mapping channel name pattern to desired capacity; if the -dict key is a string, it's intepreted as a glob, while if it's a compiled +dict key is a string, it's interpreted as a glob, while if it's a compiled ``re`` object, it's treated as a regular expression. This example sets ``http.request`` to 200, all ``http.response!`` channels @@ -156,7 +150,7 @@ to 10, and all ``websocket.send!`` channels to 20: CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", + "BACKEND": "channels_valkey.core.ValkeyChannelLayer", "CONFIG": { "hosts": [("localhost", 6379)], "channel_capacity": { @@ -176,9 +170,9 @@ argument; channels will then be matched in the order the dict provides them. Pass this to enable the optional symmetric encryption mode of the backend. To use it, make sure you have the ``cryptography`` package installed, or specify -the ``cryptography`` extra when you install ``channels-redis``:: +the ``cryptography`` extra when you install ``channels-valkey``:: - pip install channels-redis[cryptography] + pip install channels-valkey[cryptography] ``symmetric_encryption_keys`` should be a list of strings, with each string being an encryption key. The first key is always used for encryption; all are @@ -186,8 +180,8 @@ considered for decryption, so you can rotate keys without downtime - just add a new key at the start and move the old one down, then remove the old one after the message expiry time has passed. -Data is encrypted both on the wire and at rest in Redis, though we advise -you also route your Redis connections over TLS for higher security; the Redis +Data is encrypted both on the wire and at rest in Valkey, though we advise +you also route your Valkey connections over TLS for higher security; the Valkey protocol is still unencrypted, and the channel and group key names could potentially contain metadata patterns of use to attackers. @@ -202,9 +196,9 @@ If you're using Django, you may also wish to set this to your site's CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", + "BACKEND": "channels_valkey.core.ValkeyChannelLayer", "CONFIG": { - "hosts": ["redis://:password@127.0.0.1:6379/0"], + "hosts": ["valkey://:password@127.0.0.1:6379/0"], "symmetric_encryption_keys": [SECRET_KEY], }, }, @@ -213,18 +207,18 @@ If you're using Django, you may also wish to set this to your site's ``on_disconnect`` / ``on_reconnect`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The PubSub layer, which maintains long-running connections to Redis, can drop messages in the event of a network partition. -To handle such situations the PubSub layer accepts optional arguments which will notify consumers of Redis disconnect/reconnect events. +The PubSub layer, which maintains long-running connections to Valkey, can drop messages in the event of a network partition. +To handle such situations the PubSub layer accepts optional arguments which will notify consumers of Valkey disconnect/reconnect events. A common use-case is for consumers to ensure that they perform a full state re-sync to ensure that no messages have been missed. .. code-block:: python CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", + "BACKEND": "channels_valkey.pubsub.ValkeyPubSubChannelLayer", "CONFIG": { "hosts": [...], - "on_disconnect": "redis.disconnect", + "on_disconnect": "valkey.disconnect", }, }, } @@ -234,63 +228,58 @@ And then in your channels consumer, you can implement the handler: .. code-block:: python - async def redis_disconnect(self, *args): + async def valkey_disconnect(self, *args): # Handle disconnect Dependencies ------------ -Redis server >= 5.0 is required for `channels-redis`. Python 3.8 or higher is required. +Valkey server >= 7.2.7 is required for `channels-valkey`. Python 3.9 or higher is required. Used commands ~~~~~~~~~~~~~ -Your Redis server must support the following commands: +Your Valkey server must support the following commands: -* ``RedisChannelLayer`` uses ``BZPOPMIN``, ``DEL``, ``EVAL``, ``EXPIRE``, +* ``ValkeyChannelLayer`` uses ``BZPOPMIN``, ``DEL``, ``EVAL``, ``EXPIRE``, ``KEYS``, ``PIPELINE``, ``ZADD``, ``ZCOUNT``, ``ZPOPMIN``, ``ZRANGE``, ``ZREM``, ``ZREMRANGEBYSCORE`` -* ``RedisPubSubChannelLayer`` uses ``PUBLISH``, ``SUBSCRIBE``, ``UNSUBSCRIBE`` +* ``ValkeyPubSubChannelLayer`` uses ``PUBLISH``, ``SUBSCRIBE``, ``UNSUBSCRIBE`` Local Development ----------------- -You can run the necessary Redis instances in Docker with the following commands: +You can run the necessary Valkey instances in Docker with the following commands: .. code-block:: shell - $ docker network create redis-network + $ docker network create valkey-network $ docker run --rm \ - --network=redis-network \ - --name=redis-server \ + --network=valkey-network \ + --name=valkey-server \ -p 6379:6379 \ - redis + valkey/valkey $ docker run --rm \ - --network redis-network \ - --name redis-sentinel \ - -e REDIS_MASTER_HOST=redis-server \ - -e REDIS_MASTER_SET=sentinel \ - -e REDIS_SENTINEL_QUORUM=1 \ + --network valkey-network \ + --name valkey-sentinel \ + -e VALKEY_MASTER_HOST=valkey-server \ + -e VALKEY_MASTER_SET=sentinel \ + -e VALKEY_SENTINEL_QUORUM=1 \ -p 26379:26379 \ - bitnami/redis-sentinel + bitnami/valkey-sentinel Contributing ------------ +this project is a fork of ``channels_redis`` project, it's mostly the same setup, only replace ``redis`` with ``valkey``. + Please refer to the `main Channels contributing docs `_. That also contains advice on how to set up the development environment and run the tests. -Maintenance and Security ------------------------- - -To report security issues, please contact security@djangoproject.com. For GPG -signatures and more security process information, see -https://docs.djangoproject.com/en/dev/internals/security/. +Maintenance +----------- To report bugs or request new features, please open a new GitHub issue. - -This repository is part of the Channels project. For the shepherd and maintenance team, please see the -`main Channels readme `_. diff --git a/channels_redis/__init__.py b/channels_valkey/__init__.py similarity index 100% rename from channels_redis/__init__.py rename to channels_valkey/__init__.py diff --git a/channels_redis/core.py b/channels_valkey/core.py similarity index 100% rename from channels_redis/core.py rename to channels_valkey/core.py diff --git a/channels_redis/pubsub.py b/channels_valkey/pubsub.py similarity index 100% rename from channels_redis/pubsub.py rename to channels_valkey/pubsub.py diff --git a/channels_redis/utils.py b/channels_valkey/utils.py similarity index 100% rename from channels_redis/utils.py rename to channels_valkey/utils.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ec7557d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[tool.poetry] +name = "channels-valkey" +version = "0.1.0" +description = "Valkey-backed ASGI channel layer implementation" +authors = ["amirreza "] +license = "BSD" +readme = "README.rst" + +[tool.poetry.urls] +Homepage = "https://github.com/amirreza8002/channels-valkey" + +[tool.poetry.dependencies] +python = ">=3.9" +valkey = ">=6.0.0" +msgpack = "^1.1.0" +asgiref = ">=3.2.10" +channels = "^4.1.0" + +cryptography = { optional = true, version = ">=1.3.0" } + +[tool.poetry.extras] +cryptography = ["cryptography"] + +[tool.poetry.group.dev.dependencies] +black = "^24.10.0" +ruff = "^0.6.9" +pytest = "^8.3.3" +pytest-asyncio = "^0.24.0" +async-timeout = "^4.0.3" +pytest-timeout = "^2.3.1" +pre-commit = "^4.0.1" +coverage = "^7.6.2" +tox = "^4.21.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +addopts = "-p no:django" +testpaths = "tests" +timeout = 10 + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402", "F401", "F403"] + +[tool.coverage.run] +parallel = true \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3888deb..0000000 --- a/setup.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[tool:pytest] -addopts = -p no:django -testpaths = tests -asyncio_mode = auto -timeout = 10 - -[flake8] -exclude = venv/*,tox/*,specs/*,build/* -ignore = E123,E128,E266,E402,W503,E731,W601 -max-line-length = 119 - -[isort] -profile = black -known_first_party = channels, asgiref, channels_redis, daphne diff --git a/setup.py b/setup.py deleted file mode 100644 index a368709..0000000 --- a/setup.py +++ /dev/null @@ -1,40 +0,0 @@ -from os.path import dirname, join - -from setuptools import find_packages, setup - -from channels_redis import __version__ - -# We use the README as the long_description -readme = open(join(dirname(__file__), "README.rst")).read() - -crypto_requires = ["cryptography>=1.3.0"] - -test_requires = crypto_requires + [ - "pytest", - "pytest-asyncio", - "async-timeout", - "pytest-timeout", -] - - -setup( - name="channels_redis", - version=__version__, - url="http://github.com/django/channels_redis/", - author="Django Software Foundation", - author_email="foundation@djangoproject.com", - description="Redis-backed ASGI channel layer implementation", - long_description=readme, - license="BSD", - zip_safe=False, - packages=find_packages(exclude=["tests"]), - include_package_data=True, - python_requires=">=3.8", - install_requires=[ - "redis>=4.6", - "msgpack~=1.0", - "asgiref>=3.2.10,<4", - "channels", - ], - extras_require={"cryptography": crypto_requires, "tests": test_requires}, -) diff --git a/tests/test_core.py b/tests/test_core.py index 2752040..578b258 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,7 +5,7 @@ import pytest from asgiref.sync import async_to_sync -from channels_redis.core import ChannelFull, RedisChannelLayer +from channels_valkey.core import ChannelFull, RedisChannelLayer TEST_HOSTS = ["redis://localhost:6379"] diff --git a/tests/test_pubsub.py b/tests/test_pubsub.py index 3c00dd6..89d1c09 100644 --- a/tests/test_pubsub.py +++ b/tests/test_pubsub.py @@ -7,8 +7,8 @@ import pytest from asgiref.sync import async_to_sync -from channels_redis.pubsub import RedisPubSubChannelLayer -from channels_redis.utils import _close_redis +from channels_valkey.pubsub import RedisPubSubChannelLayer +from channels_valkey.utils import _close_redis TEST_HOSTS = ["redis://localhost:6379"] diff --git a/tests/test_pubsub_sentinel.py b/tests/test_pubsub_sentinel.py index 41b7de2..5946a04 100644 --- a/tests/test_pubsub_sentinel.py +++ b/tests/test_pubsub_sentinel.py @@ -5,8 +5,8 @@ import pytest from asgiref.sync import async_to_sync -from channels_redis.pubsub import RedisPubSubChannelLayer -from channels_redis.utils import _close_redis +from channels_valkey.pubsub import RedisPubSubChannelLayer +from channels_valkey.utils import _close_redis SENTINEL_MASTER = "sentinel" SENTINEL_KWARGS = {"password": "channels_redis"} diff --git a/tests/test_sentinel.py b/tests/test_sentinel.py index 4fb7de5..c09cc3c 100644 --- a/tests/test_sentinel.py +++ b/tests/test_sentinel.py @@ -5,7 +5,7 @@ import pytest from asgiref.sync import async_to_sync -from channels_redis.core import ChannelFull, RedisChannelLayer +from channels_valkey.core import ChannelFull, RedisChannelLayer SENTINEL_MASTER = "sentinel" SENTINEL_KWARGS = {"password": "channels_redis"} diff --git a/tests/test_utils.py b/tests/test_utils.py index 3a78330..5b6440c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ import pytest -from channels_redis.utils import _consistent_hash +from channels_valkey.utils import _consistent_hash @pytest.mark.parametrize( diff --git a/tox.ini b/tox.ini index d7486f2..c54dd1f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,29 +1,27 @@ [tox] envlist = - py{38,39,310,311,312}-ch{30,40,main}-redis50 - py311-chmain-redis{45,46,50,main} + py{39,310,311,312,313}-ch{30,40,main}-valkey60 + py311-chmain-valkey{60,main} qa [testenv] usedevelop = true -extras = tests +extras = dev commands = pytest -v {posargs} deps = ch30: channels>=3.0,<3.1 ch40: channels>=4.0,<4.1 chmain: https://github.com/django/channels/archive/main.tar.gz - redis46: redis>=4.6,<4.7 - redis50: redis>=5.0,<5.1 - redismain: https://github.com/redis/redis-py/archive/master.tar.gz + valkey72: + valkey60: valkey>=6.0,<6.1 + valkeymain: https://github.com/valkey-io/valkey-py/archive/master.tar.gz [testenv:qa] skip_install=true deps = black - flake8 - isort + ruff commands = - flake8 channels_redis tests - black --check channels_redis tests - isort --check-only --diff channels_redis tests + ruff check channels_valkey tests + black --check channels_valkey tests