Skip to content

Commit

Permalink
Use the async sessions api if it exists
Browse files Browse the repository at this point in the history
  • Loading branch information
bigfootjon committed Jul 11, 2024
1 parent 8d90b07 commit 6cfca1e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 8 deletions.
14 changes: 8 additions & 6 deletions channels/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
from importlib import import_module

import django
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
Expand Down Expand Up @@ -163,9 +164,7 @@ def __init__(self, scope, send):

async def resolve_session(self):
session_key = self.scope["cookies"].get(self.cookie_name)
self.scope["session"]._wrapped = await database_sync_to_async(
self.session_store
)(session_key)
self.scope["session"]._wrapped = self.session_store(session_key)

async def send(self, message):
"""
Expand All @@ -183,7 +182,7 @@ async def send(self, message):
and message.get("status", 200) != 500
and (modified or settings.SESSION_SAVE_EVERY_REQUEST)
):
await database_sync_to_async(self.save_session)()
await self.save_session()
# If this is a message type that can transport cookies back to the
# client, then do so.
if message["type"] in self.cookie_response_message_types:
Expand Down Expand Up @@ -221,12 +220,15 @@ async def send(self, message):
# Pass up the send
return await self.real_send(message)

def save_session(self):
async def save_session(self):
"""
Saves the current session.
"""
try:
self.scope["session"].save()
if django.VERSION >= (5, 1):
await self.scope["session"].asave()
else:
await database_sync_to_async(self.scope["session"].save)()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
Expand Down
3 changes: 2 additions & 1 deletion docs/topics/sessions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ whenever the session is modified.

If you are in a WebSocket consumer, however, the session is populated
**but will never be saved automatically** - you must call
``scope["session"].save()`` yourself whenever you want to persist a session
``scope["session"].save()`` (or the asynchronous version,
``scope["session"].asave()``) yourself whenever you want to persist a session
to your session store. If you don't save, the session will still work correctly
inside the consumer (as it's stored as an instance variable), but other
connections or HTTP views won't be able to see the changes.
Expand Down
52 changes: 51 additions & 1 deletion tests/test_http.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import re
from importlib import import_module

import django
import pytest
from django.conf import settings

from channels.consumer import AsyncConsumer
from channels.db import database_sync_to_async
Expand Down Expand Up @@ -93,7 +96,7 @@ async def test_session_samesite_invalid(samesite_invalid):

@pytest.mark.django_db(transaction=True)
@pytest.mark.asyncio
async def test_muliple_sessions():
async def test_multiple_sessions():
"""
Create two application instances and test then out of order to verify that
separate scopes are used.
Expand Down Expand Up @@ -123,3 +126,50 @@ async def http_request(self, event):

first_response = await first_communicator.get_response()
assert first_response["body"] == b"/first/"


@pytest.mark.django_db(transaction=True)
@pytest.mark.asyncio
async def test_session_saves():
"""
Saves information to a session and validates that it actually saves to the backend
"""

async def inner(scope, receive, send):
send(scope["path"])

class SimpleHttpApp(AsyncConsumer):
@database_sync_to_async
def set_fav_color(self):
self.scope["session"]["fav_color"] = "blue"

async def http_request(self, event):
if django.VERSION >= (5, 1):
await self.scope["session"].aset("fav_color", "blue")
else:
await self.set_fav_color()
await self.send(
{"type": "http.response.start", "status": 200, "headers": []}
)
await self.send(
{
"type": "http.response.body",
"body": self.scope["session"].session_key.encode(),
}
)

app = SessionMiddlewareStack(SimpleHttpApp.as_asgi())

communicator = HttpCommunicator(app, "GET", "/first/")

response = await communicator.get_response()
session_key = response["body"].decode()

SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
session = SessionStore(session_key=session_key)
if django.VERSION >= (5, 1):
session_fav_color = await session.aget("fav_color")
else:
session_fav_color = await database_sync_to_async(session.get)("fav_color")

assert session_fav_color == "blue"

0 comments on commit 6cfca1e

Please sign in to comment.