Skip to content

Django channels stubs #13939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions pyrightconfig.stricter.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"stubs/braintree",
"stubs/caldav",
"stubs/cffi",
"stubs/channels",
"stubs/click-web",
"stubs/corus",
"stubs/dateparser",
Expand Down
7 changes: 7 additions & 0 deletions stubs/channels/METADATA.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version = "4.*"
upstream_repository = "https://github.com/django/channels"
requires = ["django-stubs>=4.2,<5.3", "asgiref"]

[tool.stubtest]
skip = true # TODO: enable stubtest once Django mypy plugin config is supported
stubtest_requirements = ["daphne"]
2 changes: 2 additions & 0 deletions stubs/channels/channels/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__version__: str
DEFAULT_CHANNEL_LAYER: str
5 changes: 5 additions & 0 deletions stubs/channels/channels/apps.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig

class ChannelsConfig(AppConfig):
name: str = ...
verbose_name: str = ...
32 changes: 32 additions & 0 deletions stubs/channels/channels/auth.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Any

from asgiref.typing import ASGIReceiveCallable, ASGISendCallable
from channels.middleware import BaseMiddleware
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
from django.utils.functional import LazyObject

from .consumer import _ChannelScope, _LazySession
from .db import database_sync_to_async
from .utils import _ChannelApplication

@database_sync_to_async
def get_user(scope: _ChannelScope) -> AbstractBaseUser | AnonymousUser: ...
@database_sync_to_async
def login(scope: _ChannelScope, user: AbstractBaseUser, backend: BaseBackend | None = ...) -> None: ...
@database_sync_to_async
def logout(scope: _ChannelScope) -> None: ...
def _get_user_session_key(session: _LazySession) -> Any: ...

class UserLazyObject(AbstractBaseUser, LazyObject):
def _setup(self) -> None: ...

class AuthMiddleware(BaseMiddleware):
def populate_scope(self, scope: _ChannelScope) -> None: ...
async def resolve_scope(self, scope: _ChannelScope) -> None: ...
async def __call__(
self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable
) -> _ChannelApplication: ...

def AuthMiddlewareStack(inner: _ChannelApplication) -> _ChannelApplication: ...
57 changes: 57 additions & 0 deletions stubs/channels/channels/consumer.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from collections.abc import Awaitable
from typing import Any, ClassVar, Protocol, type_check_only

from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope, WebSocketScope
from channels.auth import UserLazyObject
from channels.db import database_sync_to_async
from channels.layers import BaseChannelLayer
from django.contrib.sessions.backends.base import SessionBase
from django.utils.functional import LazyObject

@type_check_only
class _LazySession(SessionBase, LazyObject): # type: ignore[misc]
_wrapped: SessionBase

# Base ASGI Scope definition
@type_check_only
class _ChannelScope(WebSocketScope, total=False):
# Channel specific
channel: str
url_route: dict[str, Any]
path_remaining: str

# Auth specific
cookies: dict[str, str]
session: _LazySession
user: UserLazyObject | None

def get_handler_name(message: dict[str, Any]) -> str: ...
@type_check_only
class _ASGIApplicationProtocol(Protocol):
consumer_class: Any
consumer_initkwargs: dict[str, Any]

def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> Awaitable[None]: ...

class AsyncConsumer:
_sync: ClassVar[bool] = ...
channel_layer_alias: ClassVar[str] = ...

scope: _ChannelScope
channel_layer: BaseChannelLayer
channel_name: str
channel_receive: ASGIReceiveCallable
base_send: ASGISendCallable

async def __call__(self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ...
async def dispatch(self, message: dict[str, Any]) -> None: ...
async def send(self, message: dict[str, Any]) -> None: ...
@classmethod
def as_asgi(cls, **initkwargs: Any) -> _ASGIApplicationProtocol: ...

class SyncConsumer(AsyncConsumer):
_sync: ClassVar[bool] = ...

@database_sync_to_async
def dispatch(self, message: dict[str, Any]) -> None: ... # type: ignore[override]
def send(self, message: dict[str, Any]) -> None: ... # type: ignore[override]
15 changes: 15 additions & 0 deletions stubs/channels/channels/db.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from asyncio import BaseEventLoop
from collections.abc import Callable, Coroutine
from typing import Any, TypeVar
from typing_extensions import ParamSpec

from asgiref.sync import SyncToAsync

_P = ParamSpec("_P")
_R = TypeVar("_R")

class DatabaseSyncToAsync(SyncToAsync[_P, _R]):
def thread_handler(self, loop: BaseEventLoop, *args: Any, **kwargs: Any) -> Any: ...

def database_sync_to_async(func: Callable[_P, _R]) -> Callable[_P, Coroutine[Any, Any, _R]]: ...
async def aclose_old_connections() -> None: ...
8 changes: 8 additions & 0 deletions stubs/channels/channels/exceptions.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class RequestAborted(Exception): ...
class RequestTimeout(RequestAborted): ...
class InvalidChannelLayerError(ValueError): ...
class AcceptConnection(Exception): ...
class DenyConnection(Exception): ...
class ChannelFull(Exception): ...
class MessageTooLarge(Exception): ...
class StopConsumer(Exception): ...
Empty file.
19 changes: 19 additions & 0 deletions stubs/channels/channels/generic/http.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from collections.abc import Iterable
from typing import Any

from asgiref.typing import HTTPDisconnectEvent, HTTPRequestEvent, HTTPScope
from channels.consumer import AsyncConsumer

class AsyncHttpConsumer(AsyncConsumer):
body: list[bytes]
scope: HTTPScope # type: ignore[assignment]

def __init__(self, *args: Any, **kwargs: Any) -> None: ...
async def send_headers(self, *, status: int = ..., headers: Iterable[tuple[bytes, bytes]] | None = ...) -> None: ...
async def send_body(self, body: bytes, *, more_body: bool = ...) -> None: ...
async def send_response(self, status: int, body: bytes, **kwargs: Any) -> None: ...
async def handle(self, body: bytes) -> None: ...
async def disconnect(self) -> None: ...
async def http_request(self, message: HTTPRequestEvent) -> None: ...
async def http_disconnect(self, message: HTTPDisconnectEvent) -> None: ...
async def send(self, message: dict[str, Any]) -> None: ... # type: ignore[override]
65 changes: 65 additions & 0 deletions stubs/channels/channels/generic/websocket.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from typing import Any

from asgiref.typing import WebSocketConnectEvent, WebSocketDisconnectEvent, WebSocketReceiveEvent
from channels.consumer import AsyncConsumer, SyncConsumer, _ChannelScope
from channels.layers import BaseChannelLayer

class WebsocketConsumer(SyncConsumer):
groups: list[str] | None
scope: _ChannelScope
channel_name: str
channel_layer: BaseChannelLayer
channel_receive: Any
base_send: Any

def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def websocket_connect(self, message: WebSocketConnectEvent) -> None: ...
def connect(self) -> None: ...
def accept(self, subprotocol: str | None = ..., headers: list[tuple[str, str]] | None = ...) -> None: ...
def websocket_receive(self, message: WebSocketReceiveEvent) -> None: ...
def receive(self, text_data: str | None = ..., bytes_data: bytes | None = ...) -> None: ...
def send( # type: ignore[override]
self, text_data: str | None = ..., bytes_data: bytes | None = ..., close: bool = ...
) -> None: ...
def close(self, code: int | bool | None = ..., reason: str | None = ...) -> None: ...
def websocket_disconnect(self, message: WebSocketDisconnectEvent) -> None: ...
def disconnect(self, code: int) -> None: ...

class JsonWebsocketConsumer(WebsocketConsumer):
def receive(self, text_data: str | None = ..., bytes_data: bytes | None = ..., **kwargs: Any) -> None: ...
def receive_json(self, content: Any, **kwargs: Any) -> None: ...
def send_json(self, content: Any, close: bool = ...) -> None: ...
@classmethod
def decode_json(cls, text_data: str) -> Any: ...
@classmethod
def encode_json(cls, content: Any) -> str: ...

class AsyncWebsocketConsumer(AsyncConsumer):
groups: list[str] | None
scope: _ChannelScope
channel_name: str
channel_layer: BaseChannelLayer
channel_receive: Any
base_send: Any

def __init__(self, *args: Any, **kwargs: Any) -> None: ...
async def websocket_connect(self, message: WebSocketConnectEvent) -> None: ...
async def connect(self) -> None: ...
async def accept(self, subprotocol: str | None = ..., headers: list[tuple[str, str]] | None = ...) -> None: ...
async def websocket_receive(self, message: WebSocketReceiveEvent) -> None: ...
async def receive(self, text_data: str | None = ..., bytes_data: bytes | None = ...) -> None: ...
async def send( # type: ignore[override]
self, text_data: str | None = ..., bytes_data: bytes | None = ..., close: bool = ...
) -> None: ...
async def close(self, code: int | bool | None = ..., reason: str | None = ...) -> None: ...
async def websocket_disconnect(self, message: WebSocketDisconnectEvent) -> None: ...
async def disconnect(self, code: int) -> None: ...

class AsyncJsonWebsocketConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data: str | None = ..., bytes_data: bytes | None = ..., **kwargs: Any) -> None: ...
async def receive_json(self, content: Any, **kwargs: Any) -> None: ...
async def send_json(self, content: Any, close: bool = ...) -> None: ...
@classmethod
async def decode_json(cls, text_data: str) -> Any: ...
@classmethod
async def encode_json(cls, content: Any) -> str: ...
96 changes: 96 additions & 0 deletions stubs/channels/channels/layers.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import asyncio
from re import Pattern
from typing import Any, overload
from typing_extensions import TypeAlias, deprecated

class ChannelLayerManager:
backends: dict[str, BaseChannelLayer]

def __init__(self) -> None: ...
def _reset_backends(self, setting: str, **kwargs: Any) -> None: ...
@property
def configs(self) -> dict[str, Any]: ...
def make_backend(self, name: str) -> BaseChannelLayer: ...
def make_test_backend(self, name: str) -> Any: ...
def _make_backend(self, name: str, config: dict[str, Any]) -> BaseChannelLayer: ...
def __getitem__(self, key: str) -> BaseChannelLayer: ...
def __contains__(self, key: str) -> bool: ...
def set(self, key: str, layer: BaseChannelLayer) -> BaseChannelLayer | None: ...

_ChannelCapacityPattern: TypeAlias = Pattern[str] | str
_ChannelCapacityDict: TypeAlias = dict[_ChannelCapacityPattern, int]
_CompiledChannelCapacity: TypeAlias = list[tuple[Pattern[str], int]]

class BaseChannelLayer:
MAX_NAME_LENGTH: int = ...
expiry: int
capacity: int
channel_capacity: _ChannelCapacityDict
channel_name_regex: Pattern[str]
group_name_regex: Pattern[str]
invalid_name_error: str

def __init__(self, expiry: int = ..., capacity: int = ..., channel_capacity: _ChannelCapacityDict | None = ...) -> None: ...
def compile_capacities(self, channel_capacity: _ChannelCapacityDict) -> _CompiledChannelCapacity: ...
def get_capacity(self, channel: str) -> int: ...
@overload
def match_type_and_length(self, name: str) -> bool: ...
@overload
def match_type_and_length(self, name: Any) -> bool: ...
@overload
def require_valid_channel_name(self, name: str, receive: bool = ...) -> bool: ...
@overload
def require_valid_channel_name(self, name: Any, receive: bool = ...) -> bool: ...
@overload
def require_valid_group_name(self, name: str) -> bool: ...
@overload
def require_valid_group_name(self, name: Any) -> bool: ...
@overload
def valid_channel_names(self, names: list[str], receive: bool = ...) -> bool: ...
@overload
def valid_channel_names(self, names: list[Any], receive: bool = ...) -> bool: ...
def non_local_name(self, name: str) -> str: ...
async def send(self, channel: str, message: dict[str, Any]) -> None: ...
async def receive(self, channel: str) -> dict[str, Any]: ...
async def new_channel(self) -> str: ...
async def flush(self) -> None: ...
async def group_add(self, group: str, channel: str) -> None: ...
async def group_discard(self, group: str, channel: str) -> None: ...
async def group_send(self, group: str, message: dict[str, Any]) -> None: ...
@deprecated("Use require_valid_channel_name instead.")
def valid_channel_name(self, channel_name: str, receive: bool = ...) -> bool: ...
@deprecated("Use require_valid_group_name instead.")
def valid_group_name(self, group_name: str) -> bool: ...

_InMemoryQueueData: TypeAlias = tuple[float, dict[str, Any]]

class InMemoryChannelLayer(BaseChannelLayer):
channels: dict[str, asyncio.Queue[_InMemoryQueueData]]
groups: dict[str, dict[str, float]]
group_expiry: int

def __init__(
self,
expiry: int = ...,
group_expiry: int = ...,
capacity: int = ...,
channel_capacity: _ChannelCapacityDict | None = ...,
**kwargs: Any,
) -> None: ...

extensions: list[str]

async def send(self, channel: str, message: dict[str, Any]) -> None: ...
async def receive(self, channel: str) -> dict[str, Any]: ...
async def new_channel(self, prefix: str = ...) -> str: ...
def _clean_expired(self) -> None: ...
async def flush(self) -> None: ...
async def close(self) -> None: ...
def _remove_from_groups(self, channel: str) -> None: ...
async def group_add(self, group: str, channel: str) -> None: ...
async def group_discard(self, group: str, channel: str) -> None: ...
async def group_send(self, group: str, message: dict[str, Any]) -> None: ...

def get_channel_layer(alias: str = ...) -> BaseChannelLayer | None: ...

channel_layers: ChannelLayerManager
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions stubs/channels/channels/management/commands/runworker.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging
from argparse import ArgumentParser
from typing import Any

from channels.layers import BaseChannelLayer
from channels.worker import Worker
from django.core.management.base import BaseCommand

logger: logging.Logger

class Command(BaseCommand):
leave_locale_alone: bool = ...
worker_class: type[Worker] = ...
verbosity: int
channel_layer: BaseChannelLayer

def add_arguments(self, parser: ArgumentParser) -> None: ...
def handle(
self, *args: Any, application_path: str | None = ..., channels: list[str] | None = ..., layer: str = ..., **options: Any
) -> None: ...
12 changes: 12 additions & 0 deletions stubs/channels/channels/middleware.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from asgiref.typing import ASGIReceiveCallable, ASGISendCallable

from .consumer import _ChannelScope
from .utils import _ChannelApplication

class BaseMiddleware:
inner: _ChannelApplication

def __init__(self, inner: _ChannelApplication) -> None: ...
async def __call__(
self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable
) -> _ChannelApplication: ...
32 changes: 32 additions & 0 deletions stubs/channels/channels/routing.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Any, type_check_only

from asgiref.typing import ASGIReceiveCallable, ASGISendCallable
from django.urls.resolvers import URLPattern

from .consumer import _ASGIApplicationProtocol, _ChannelScope
from .utils import _ChannelApplication

def get_default_application() -> ProtocolTypeRouter: ...

class ProtocolTypeRouter:
application_mapping: dict[str, _ChannelApplication]

def __init__(self, application_mapping: dict[str, Any]) -> None: ...
async def __call__(self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ...

@type_check_only
class _ExtendedURLPattern(URLPattern):
callback: _ASGIApplicationProtocol | URLRouter

class URLRouter:
_path_routing: bool = ...
routes: list[_ExtendedURLPattern | URLRouter]

def __init__(self, routes: list[_ExtendedURLPattern | URLRouter]) -> None: ...
async def __call__(self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ...

class ChannelNameRouter:
application_mapping: dict[str, _ChannelApplication]

def __init__(self, application_mapping: dict[str, _ChannelApplication]) -> None: ...
async def __call__(self, scope: _ChannelScope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ...
Empty file.
Loading