Skip to content
Snippets Groups Projects
Commit a0792f4b authored by Michael Fladischer's avatar Michael Fladischer
Browse files

New upstream version 4.1.0

parent 0a28117e
No related branches found
No related tags found
No related merge requests found
Showing
with 140 additions and 44 deletions
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
......@@ -14,38 +14,42 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel setuptools
python -m pip install --upgrade tox tox-py
python -m pip install --upgrade tox
- name: Run tox targets for ${{ matrix.python-version }}
run: tox --py current
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip tox
- name: Run lint
run: tox -e qa
Full release notes, with more details and upgrade information, are available at:
https://channels.readthedocs.io/en/latest/releases
4.1.0 (2024-04-03)
------------------
Channels 4.1 is maintenance release in the 4.x series.
The main change is an update in the required Python and Django version.
Python 3.8, and Django 4.2 are now the minimum required versions.
There are a number of other small bugfixes. Please ensure to review the
`Version 4.1.0 release notes
<https://channels.readthedocs.io/en/latest/releases/4.1.0.html>`_ for full
details.
4.0.0 (2022-10-15)
------------------
......@@ -15,7 +28,7 @@ the top of your ``INSTALLED_APPS`` setting.
First ``pip``::
pip install -U 'channels[dapne]' channels-redis
pip install -U 'channels[daphne]' channels-redis
Then in your Django settings file::
......
......@@ -25,14 +25,21 @@ Make sure the tests pass:
.. code-block:: sh
python -m pip install -e .[tests]
python -m pip install -e .[tests,daphne]
pytest
.. note::
If you're using ``zsh`` for your shell, the above command will fail with a
``zsh: no matches found: .[tests]`` error.
To fix this use ``noglob``::
noglob python -m pip install -e .[tests]
Make your change. Add tests for your change. Make the tests pass:
.. code-block:: sh
pytest
tox
Make sure your code conforms to the coding style:
......
......@@ -35,8 +35,8 @@ and `tutorial <https://channels.readthedocs.io/en/latest/tutorial/index.html>`_
Dependencies
------------
All Channels projects currently support Python 3.7 and up. ``channels`` is
compatible with Django 2.2, 3.2, 4.0 and 4.1.
All Channels projects currently support Python 3.8 and up. ``channels`` is
compatible with Django 4.2 and 5.0.
Contributing
......
__version__ = "4.0.0"
__version__ = "4.1.0"
DEFAULT_CHANNEL_LAYER = "default"
......@@ -2,6 +2,5 @@ from django.apps import AppConfig
class ChannelsConfig(AppConfig):
name = "channels"
verbose_name = "Channels"
......@@ -81,7 +81,7 @@ class AsyncHttpConsumer(AsyncConsumer):
await self.handle(b"".join(self.body))
finally:
await self.disconnect()
raise StopConsumer()
raise StopConsumer()
async def http_disconnect(self, message):
"""
......
......@@ -44,11 +44,15 @@ class WebsocketConsumer(SyncConsumer):
def connect(self):
self.accept()
def accept(self, subprotocol=None):
def accept(self, subprotocol=None, headers=None):
"""
Accepts an incoming socket
"""
super().send({"type": "websocket.accept", "subprotocol": subprotocol})
message = {"type": "websocket.accept", "subprotocol": subprotocol}
if headers:
message["headers"] = list(headers)
super().send(message)
def websocket_receive(self, message):
"""
......@@ -79,14 +83,16 @@ class WebsocketConsumer(SyncConsumer):
if close:
self.close(close)
def close(self, code=None):
def close(self, code=None, reason=None):
"""
Closes the WebSocket from the server end
"""
message = {"type": "websocket.close"}
if code is not None and code is not True:
super().send({"type": "websocket.close", "code": code})
else:
super().send({"type": "websocket.close"})
message["code"] = code
if reason:
message["reason"] = reason
super().send(message)
def websocket_disconnect(self, message):
"""
......@@ -179,11 +185,14 @@ class AsyncWebsocketConsumer(AsyncConsumer):
async def connect(self):
await self.accept()
async def accept(self, subprotocol=None):
async def accept(self, subprotocol=None, headers=None):
"""
Accepts an incoming socket
"""
await super().send({"type": "websocket.accept", "subprotocol": subprotocol})
message = {"type": "websocket.accept", "subprotocol": subprotocol}
if headers:
message["headers"] = list(headers)
await super().send(message)
async def websocket_receive(self, message):
"""
......@@ -214,14 +223,16 @@ class AsyncWebsocketConsumer(AsyncConsumer):
if close:
await self.close(close)
async def close(self, code=None):
async def close(self, code=None, reason=None):
"""
Closes the WebSocket from the server end
"""
message = {"type": "websocket.close"}
if code is not None and code is not True:
await super().send({"type": "websocket.close", "code": code})
else:
await super().send({"type": "websocket.close"})
message["code"] = code
if reason:
message["reason"] = reason
await super().send(message)
async def websocket_disconnect(self, message):
"""
......
......@@ -11,7 +11,6 @@ logger = logging.getLogger("django.channels.worker")
class Command(BaseCommand):
leave_locale_alone = True
worker_class = Worker
......
......@@ -3,7 +3,7 @@ import importlib
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.urls.exceptions import Resolver404
from django.urls.resolvers import URLResolver
from django.urls.resolvers import RegexPattern, RoutePattern, URLResolver
"""
All Routing instances inside this file are also valid ASGI applications - with
......@@ -87,7 +87,14 @@ class URLRouter:
# The inner ASGI app wants to do additional routing, route
# must not be an endpoint
if getattr(route.callback, "_path_routing", False) is True:
route.pattern._is_endpoint = False
pattern = route.pattern
if isinstance(pattern, RegexPattern):
arg = pattern._regex
elif isinstance(pattern, RoutePattern):
arg = pattern._route
else:
raise ValueError(f"Unsupported pattern type: {type(pattern)}")
route.pattern = pattern.__class__(arg, pattern.name, is_endpoint=False)
if not route.callback and isinstance(route, URLResolver):
raise ImproperlyConfigured(
......@@ -100,6 +107,15 @@ class URLRouter:
path = scope.get("path_remaining", scope.get("path", None))
if path is None:
raise ValueError("No 'path' key in connection scope, cannot route URLs")
if "path_remaining" not in scope:
# We are the outermost URLRouter, so handle root_path if present.
root_path = scope.get("root_path", "")
if root_path and not path.startswith(root_path):
# If root_path is present, path must start with it.
raise ValueError("No route found for path %r." % path)
path = path[len(root_path) :]
# Remove leading / to match Django's handling
path = path.lstrip("/")
# Run through the routes we have until one matches
......
......@@ -111,7 +111,7 @@ class CookieMiddleware:
# Write out the cookies to the response
for c in cookies.values():
message.setdefault("headers", []).append(
(b"Set-Cookie", bytes(c.output(header=""), encoding="utf-8"))
(b"Set-Cookie", bytes(c.output(header="").strip(), encoding="utf-8"))
)
@classmethod
......
......@@ -12,7 +12,9 @@ class WebsocketCommunicator(ApplicationCommunicator):
(uninstantiated) along with the initial connection parameters.
"""
def __init__(self, application, path, headers=None, subprotocols=None):
def __init__(
self, application, path, headers=None, subprotocols=None, spec_version=None
):
if not isinstance(path, str):
raise TypeError("Expected str, got {}".format(type(path)))
parsed = urlparse(path)
......@@ -23,7 +25,10 @@ class WebsocketCommunicator(ApplicationCommunicator):
"headers": headers or [],
"subprotocols": subprotocols or [],
}
if spec_version:
self.scope["spec_version"] = spec_version
super().__init__(application, self.scope)
self.response_headers = None
async def connect(self, timeout=1):
"""
......@@ -37,6 +42,8 @@ class WebsocketCommunicator(ApplicationCommunicator):
if response["type"] == "websocket.close":
return (False, response.get("code", 1000))
else:
assert response["type"] == "websocket.accept"
self.response_headers = response.get("headers", [])
return (True, response.get("subprotocol", None))
async def send_to(self, text_data=None, bytes_data=None):
......
......@@ -12,13 +12,9 @@ These projects from the community are developed on top of Channels:
* DjangoChannelsJsonRpc_, a wrapper for the JSON-RPC protocol.
* channels-demultiplexer_, a (de)multiplexer for ``AsyncJsonWebsocketConsumer`` consumers.
* channels_postgres_, a Django Channels channel layer that uses PostgreSQL as its backing store.
Community Tutorials
===================
Here are some Channels tutorials from around the community:
* kafka-integration_, a writeup on integrating Kafka with Channels.
* channels-auth-token-middlewares_, Django REST framework token authentication middleware and
SimpleJWT_ middleware, such as QueryStringSimpleJWTAuthTokenMiddleware_ for WebSocket
authentication.
If you'd like to add your project, please submit a PR with a link and brief description.
......@@ -32,3 +28,6 @@ If you'd like to add your project, please submit a PR with a link and brief desc
.. _channels-demultiplexer: https://github.com/csdenboer/channels-demultiplexer
.. _kafka-integration: https://gist.github.com/aryan340/da071d027050cfe0a03df3b500f2f44b
.. _channels_postgres: https://github.com/danidee10/channels_postgres
.. _channels-auth-token-middlewares: https://github.com/YegorDB/django-channels-auth-token-middlewares
.. _SimpleJWT: https://github.com/jazzband/djangorestframework-simplejwt
.. _QueryStringSimpleJWTAuthTokenMiddleware: https://github.com/YegorDB/django-channels-auth-token-middlewares/tree/master/tutorial/drf#querystringsimplejwtauthtokenmiddleware
......@@ -206,7 +206,7 @@ owner settings of the run directory.
$ sudo chown <user>.<group> /run/daphne/
The /run/ folder is cleared on a server reboot. To make the /run/daphne folder
persistant create a file ``/usr/lib/tmpfiles.d/daphne.conf`` with the contents
persistent create a file ``/usr/lib/tmpfiles.d/daphne.conf`` with the contents
below.
.. code-block:: text
......
......@@ -5,7 +5,7 @@ Channels is available on PyPI - to install it run:
.. code-block:: sh
python -m pip install -U channels["daphne"]
python -m pip install -U 'channels[daphne]'
This will install Channels together with the Daphne ASGI application server. If
you wish to use a different application server you can ``pip install channels``,
......
......@@ -265,7 +265,7 @@ point-to-point and broadcast messaging.
.. code-block:: python
#In a consumer
# In a consumer
self.channel_layer.send(
'event',
{
......
......@@ -12,7 +12,7 @@ the top of your ``INSTALLED_APPS`` setting.
First ``pip``::
pip install -U 'channels[dapne]' channels-redis
pip install -U 'channels[daphne]' channels-redis
Then in your Django settings file::
......
4.1.0 Release Notes
===================
Channels 4.1 is maintenance release in the 4.x series.
Python and Django support
-------------------------
* A Python version of 3.8 or higher is required.
* Django 4.2 is now the minimum supported version.
Bugfixes & Small Changes
------------------------
* Exceptions in ``HttpConsumer`` are now correctly propagated.
Thanks to Adam Johnson.
* URLRouter is updated for compatibility with in-development changes in Django.
Thanks to Adam Johnson.
* URLRouter is updated to correctly handle ``root_path``.
Thanks to Alejandro R. Sedeño.
* Websocket consumers are updated for newer ASGI spec versions, adding the
``headers`` parameter for the ``accept`` event, and ``reason`` for the
``close`` event.
Thanks to Kristján Valur Jónsson.
......@@ -4,6 +4,7 @@ Release Notes
.. toctree::
:maxdepth: 1
4.1.0
4.0.0
3.0.5
3.0.4
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment