Skip to content

Commit

Permalink
Renamed Websocket* types to WebSocket*
Browse files Browse the repository at this point in the history
This matches our usage of the noun elsewhere in specs, and the official spelling.
  • Loading branch information
Kludex committed Jun 27, 2021
1 parent 7c24756 commit 2843f67
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 21 deletions.
61 changes: 61 additions & 0 deletions asgiref/_pep562.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Backport of PEP 562.
https://pypi.org/search/?q=pep562
Licensed under MIT
Copyright (c) 2018 Isaac Muse <isaacmuse@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
"""
import sys
from typing import Any, Callable, List, Optional


class Pep562:
"""
Backport of PEP 562 <https://pypi.org/search/?q=pep562>.
Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`.
The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed.
"""

def __init__(self, name: str) -> None:
"""Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7."""

self._module = sys.modules[name]
self._get_attr = getattr(self._module, "__getattr__", None)
self._get_dir: Optional[Callable[..., List[str]]] = getattr(
self._module, "__dir__", None
)
sys.modules[name] = self # type: ignore[assignment]

def __dir__(self) -> List[str]:
"""Return the overridden `dir` if one was provided, else apply `dir` to the module."""

return self._get_dir() if self._get_dir else dir(self._module)

def __getattr__(self, name: str) -> Any:
"""
Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present.
"""

try:
return getattr(self._module, name)
except AttributeError:
if self._get_attr:
return self._get_attr(name)
raise


def pep562(module_name: str) -> None:
"""Helper function to apply PEP 562."""

if sys.version_info < (3, 7):
Pep562(module_name)
110 changes: 90 additions & 20 deletions asgiref/typing.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,50 @@
import sys
from typing import Awaitable, Callable, Dict, Iterable, Optional, Tuple, Type, Union
import warnings
from typing import Any, Awaitable, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union

from asgiref._pep562 import pep562

if sys.version_info >= (3, 8):
from typing import Literal, Protocol, TypedDict
else:
from typing_extensions import Literal, Protocol, TypedDict

__all__ = (
"ASGIVersions",
"HTTPScope",
"WebSocketScope",
"LifespanScope",
"WWWScope",
"Scope",
"HTTPRequestEvent",
"HTTPResponseStartEvent",
"HTTPResponseBodyEvent",
"HTTPServerPushEvent",
"HTTPDisconnectEvent",
"WebSocketConnectEvent",
"WebSocketAcceptEvent",
"WebSocketReceiveEvent",
"WebSocketSendEvent",
"WebSocketResponseStartEvent",
"WebSocketResponseBodyEvent",
"WebSocketDisconnectEvent",
"WebSocketCloseEvent",
"LifespanStartupEvent",
"LifespanShutdownEvent",
"LifespanStartupCompleteEvent",
"LifespanStartupFailedEvent",
"LifespanShutdownCompleteEvent",
"LifespanShutdownFailedEvent",
"ASGIReceiveEvent",
"ASGISendEvent",
"ASGIReceiveCallable",
"ASGISendCallable",
"ASGI2Protocol",
"ASGI2Application",
"ASGI3Application",
"ASGIApplication",
)


class ASGIVersions(TypedDict):
spec_version: str
Expand All @@ -28,7 +67,7 @@ class HTTPScope(TypedDict):
extensions: Optional[Dict[str, Dict[object, object]]]


class WebsocketScope(TypedDict):
class WebSocketScope(TypedDict):
type: Literal["websocket"]
asgi: ASGIVersions
http_version: str
Expand All @@ -49,8 +88,8 @@ class LifespanScope(TypedDict):
asgi: ASGIVersions


WWWScope = Union[HTTPScope, WebsocketScope]
Scope = Union[HTTPScope, WebsocketScope, LifespanScope]
WWWScope = Union[HTTPScope, WebSocketScope]
Scope = Union[HTTPScope, WebSocketScope, LifespanScope]


class HTTPRequestEvent(TypedDict):
Expand Down Expand Up @@ -81,46 +120,46 @@ class HTTPDisconnectEvent(TypedDict):
type: Literal["http.disconnect"]


class WebsocketConnectEvent(TypedDict):
class WebSocketConnectEvent(TypedDict):
type: Literal["websocket.connect"]


class WebsocketAcceptEvent(TypedDict):
class WebSocketAcceptEvent(TypedDict):
type: Literal["websocket.accept"]
subprotocol: Optional[str]
headers: Iterable[Tuple[bytes, bytes]]


class WebsocketReceiveEvent(TypedDict):
class WebSocketReceiveEvent(TypedDict):
type: Literal["websocket.receive"]
bytes: Optional[bytes]
text: Optional[str]


class WebsocketSendEvent(TypedDict):
class WebSocketSendEvent(TypedDict):
type: Literal["websocket.send"]
bytes: Optional[bytes]
text: Optional[str]


class WebsocketResponseStartEvent(TypedDict):
class WebSocketResponseStartEvent(TypedDict):
type: Literal["websocket.http.response.start"]
status: int
headers: Iterable[Tuple[bytes, bytes]]


class WebsocketResponseBodyEvent(TypedDict):
class WebSocketResponseBodyEvent(TypedDict):
type: Literal["websocket.http.response.body"]
body: bytes
more_body: bool


class WebsocketDisconnectEvent(TypedDict):
class WebSocketDisconnectEvent(TypedDict):
type: Literal["websocket.disconnect"]
code: int


class WebsocketCloseEvent(TypedDict):
class WebSocketCloseEvent(TypedDict):
type: Literal["websocket.close"]
code: int
reason: Optional[str]
Expand Down Expand Up @@ -155,9 +194,9 @@ class LifespanShutdownFailedEvent(TypedDict):
ASGIReceiveEvent = Union[
HTTPRequestEvent,
HTTPDisconnectEvent,
WebsocketConnectEvent,
WebsocketReceiveEvent,
WebsocketDisconnectEvent,
WebSocketConnectEvent,
WebSocketReceiveEvent,
WebSocketDisconnectEvent,
LifespanStartupEvent,
LifespanShutdownEvent,
]
Expand All @@ -168,11 +207,11 @@ class LifespanShutdownFailedEvent(TypedDict):
HTTPResponseBodyEvent,
HTTPServerPushEvent,
HTTPDisconnectEvent,
WebsocketAcceptEvent,
WebsocketSendEvent,
WebsocketResponseStartEvent,
WebsocketResponseBodyEvent,
WebsocketCloseEvent,
WebSocketAcceptEvent,
WebSocketSendEvent,
WebSocketResponseStartEvent,
WebSocketResponseBodyEvent,
WebSocketCloseEvent,
LifespanStartupCompleteEvent,
LifespanStartupFailedEvent,
LifespanShutdownCompleteEvent,
Expand Down Expand Up @@ -204,3 +243,34 @@ async def __call__(
Awaitable[None],
]
ASGIApplication = Union[ASGI2Application, ASGI3Application]

__deprecated__ = {
"WebsocketConnectEvent": WebSocketConnectEvent,
"WebsocketAcceptEvent": WebSocketAcceptEvent,
"WebsocketReceiveEvent": WebSocketReceiveEvent,
"WebsocketSendEvent": WebSocketSendEvent,
"WebsocketResponseStartEvent": WebSocketResponseStartEvent,
"WebsocketResponseBodyEvent": WebSocketResponseBodyEvent,
"WebsocketDisconnectEvent": WebSocketDisconnectEvent,
"WebsocketCloseEvent": WebSocketCloseEvent,
}


def __getattr__(name: str) -> Any:
deprecated = __deprecated__.get(name)
if deprecated:
stacklevel = 3 if sys.version_info >= (3, 7) else 4
warnings.warn(
f"'{name}' is deprecated. Use '{deprecated.__name__}' instead.",
category=DeprecationWarning,
stacklevel=stacklevel,
)
return deprecated
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


def __dir__() -> List[str]:
return sorted(list(__all__) + list(__deprecated__.keys()))


pep562(__name__)
20 changes: 20 additions & 0 deletions tests/test_deprecated_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import importlib

import pytest

from asgiref import typing


@pytest.mark.parametrize("deprecated_type", typing.__deprecated__.keys())
def test_deprecated_types(deprecated_type: str) -> None:
with pytest.warns(DeprecationWarning) as record:
getattr(importlib.import_module("asgiref.typing"), deprecated_type)
assert len(record) == 1
assert deprecated_type in str(record.list[0])


@pytest.mark.parametrize("available_type", typing.__all__)
def test_available_types(available_type: str) -> None:
with pytest.warns(None) as record:
getattr(importlib.import_module("asgiref.typing"), available_type)
assert len(record) == 0
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ commands =
mypy: mypy . {posargs}

[testenv:qa]
skip_install=true
skip_install = true
deps =
pre-commit
commands =
Expand Down

0 comments on commit 2843f67

Please sign in to comment.