|
| 1 | +from collections.abc import Awaitable |
| 2 | +from typing import Any, ClassVar, Protocol, TypedDict, type_check_only |
| 3 | + |
| 4 | +from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope, WebSocketScope |
| 5 | +from channels.auth import UserLazyObject |
| 6 | +from channels.db import database_sync_to_async |
| 7 | +from channels.layers import BaseChannelLayer |
| 8 | +from django.contrib.sessions.backends.base import SessionBase |
| 9 | +from django.utils.functional import LazyObject |
| 10 | + |
| 11 | +# _LazySession is a LazyObject that wraps a SessionBase instance. |
| 12 | +# We subclass both for type checking purposes to expose SessionBase attributes, |
| 13 | +# and suppress mypy's "misc" error with `# type: ignore[misc]`. |
| 14 | +@type_check_only |
| 15 | +class _LazySession(SessionBase, LazyObject): # type: ignore[misc] |
| 16 | + _wrapped: SessionBase |
| 17 | + |
| 18 | +@type_check_only |
| 19 | +class _URLRoute(TypedDict): |
| 20 | + # Values extracted from Django's URLPattern matching, |
| 21 | + # passed through ASGI scope routing. |
| 22 | + # `args` and `kwargs` are the result of pattern matching against the URL path. |
| 23 | + args: tuple[Any, ...] |
| 24 | + kwargs: dict[str, Any] |
| 25 | + |
| 26 | +# Channel Scope definition |
| 27 | +@type_check_only |
| 28 | +class _ChannelScope(WebSocketScope, total=False): |
| 29 | + # Channels specific |
| 30 | + channel: str |
| 31 | + url_route: _URLRoute |
| 32 | + path_remaining: str |
| 33 | + |
| 34 | + # Auth specific |
| 35 | + cookies: dict[str, str] |
| 36 | + session: _LazySession |
| 37 | + user: UserLazyObject | None |
| 38 | + |
| 39 | +# Accepts any ASGI message dict with a required "type" key (str), |
| 40 | +# but allows additional arbitrary keys for flexibility. |
| 41 | +def get_handler_name(message: dict[str, Any]) -> str: ... |
| 42 | +@type_check_only |
| 43 | +class _ASGIApplicationProtocol(Protocol): |
| 44 | + consumer_class: AsyncConsumer |
| 45 | + |
| 46 | + # Accepts any initialization kwargs passed to the consumer class. |
| 47 | + # Typed as `Any` to allow flexibility in subclass-specific arguments. |
| 48 | + consumer_initkwargs: Any |
| 49 | + |
| 50 | + def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> Awaitable[None]: ... |
| 51 | + |
| 52 | +class AsyncConsumer: |
| 53 | + channel_layer_alias: ClassVar[str] |
| 54 | + |
| 55 | + scope: _ChannelScope |
| 56 | + channel_layer: BaseChannelLayer |
| 57 | + channel_name: str |
| 58 | + channel_receive: ASGIReceiveCallable |
| 59 | + base_send: ASGISendCallable |
| 60 | + |
| 61 | + async def __call__(self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... |
| 62 | + async def dispatch(self, message: dict[str, Any]) -> None: ... |
| 63 | + async def send(self, message: dict[str, Any]) -> None: ... |
| 64 | + |
| 65 | + # initkwargs will be used to instantiate the consumer instance. |
| 66 | + @classmethod |
| 67 | + def as_asgi(cls, **initkwargs: Any) -> _ASGIApplicationProtocol: ... |
| 68 | + |
| 69 | +class SyncConsumer(AsyncConsumer): |
| 70 | + |
| 71 | + # Since we're overriding asynchronous methods with synchronous ones, |
| 72 | + # we need to use `# type: ignore[override]` to suppress mypy errors. |
| 73 | + @database_sync_to_async |
| 74 | + def dispatch(self, message: dict[str, Any]) -> None: ... # type: ignore[override] |
| 75 | + def send(self, message: dict[str, Any]) -> None: ... # type: ignore[override] |
0 commit comments