Skip to content
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

Replace dict-like interface with new state dict #5893

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/5864.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added static typing support using ``Application.state`` (replacing the previous dict-like behaviour of ``Application``).
23 changes: 11 additions & 12 deletions aiohttp/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@
from multidict import CIMultiDict
from yarl import URL

from .typedefs import LooseCookies
from .typedefs import LooseCookies, _SafeApplication, _SafeRequest

if TYPE_CHECKING: # pragma: no cover
from .web_app import Application
from .web_exceptions import HTTPException
from .web_request import BaseRequest, Request
from .web_request import BaseRequest
from .web_response import StreamResponse
else:
BaseRequest = Request = Application = StreamResponse = None
BaseRequest = StreamResponse = None
HTTPException = None


class AbstractRouter(ABC):
def __init__(self) -> None:
self._frozen = False

def post_init(self, app: Application) -> None:
def post_init(self, app: _SafeApplication) -> None:
"""Post init stage.

Not an abstract method for sake of backward compatibility,
Expand All @@ -51,19 +50,19 @@ def freeze(self) -> None:
self._frozen = True

@abstractmethod
async def resolve(self, request: Request) -> "AbstractMatchInfo":
async def resolve(self, request: _SafeRequest) -> "AbstractMatchInfo":
"""Return MATCH_INFO for given request"""


class AbstractMatchInfo(ABC):
@property # pragma: no branch
@abstractmethod
def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:
def handler(self) -> Callable[[_SafeRequest], Awaitable[StreamResponse]]:
"""Execute matched request handler"""

@property
@abstractmethod
def expect_handler(self) -> Callable[[Request], Awaitable[None]]:
def expect_handler(self) -> Callable[[_SafeRequest], Awaitable[None]]:
"""Expect handler for 100-continue processing"""

@property # pragma: no branch
Expand All @@ -77,15 +76,15 @@ def get_info(self) -> Dict[str, Any]:

@property # pragma: no branch
@abstractmethod
def apps(self) -> Tuple[Application, ...]:
def apps(self) -> Tuple[_SafeApplication, ...]:
"""Stack of nested applications.

Top level application is left-most element.

"""

@abstractmethod
def add_app(self, app: Application) -> None:
def add_app(self, app: _SafeApplication) -> None:
"""Add application to the nested apps stack."""

@abstractmethod
Expand All @@ -102,11 +101,11 @@ def freeze(self) -> None:
class AbstractView(ABC):
"""Abstract class based view."""

def __init__(self, request: Request) -> None:
def __init__(self, request: _SafeRequest) -> None:
self._request = request

@property
def request(self) -> Request:
def request(self) -> _SafeRequest:
"""Request instance."""
return self._request

Expand Down
20 changes: 17 additions & 3 deletions aiohttp/http_websocket.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
"""WebSocket protocol versions 13 and 8."""

import asyncio
import collections
import json
import random
import re
import sys
import zlib
from enum import IntEnum
from struct import Struct
from typing import Any, Callable, List, Optional, Pattern, Set, Tuple, Union, cast
from typing import (
Any,
Callable,
List,
NamedTuple,
Optional,
Pattern,
Set,
Tuple,
Union,
cast,
)

from typing_extensions import Final

Expand Down Expand Up @@ -78,7 +88,11 @@ class WSMsgType(IntEnum):
DEFAULT_LIMIT: Final[int] = 2 ** 16


_WSMessageBase = collections.namedtuple("_WSMessageBase", ["type", "data", "extra"])
class _WSMessageBase(NamedTuple):
type: WSMsgType
# To type correctly, this would need some kind of tagged union for each type.
data: Any
extra: Optional[str]


class WSMessage(_WSMessageBase):
Expand Down
6 changes: 3 additions & 3 deletions aiohttp/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

import pytest

from aiohttp.web import Application

from .test_utils import (
BaseTestServer,
RawTestServer,
Expand All @@ -18,6 +16,8 @@
teardown_test_loop,
unused_port as _unused_port,
)
from .typedefs import _SafeApplication
from .web import Application

try:
import uvloop
Expand Down Expand Up @@ -342,7 +342,7 @@ def aiohttp_client(
clients = []

async def go(
__param: Union[Application, BaseTestServer],
__param: Union[_SafeApplication, BaseTestServer],
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any
Expand Down
29 changes: 11 additions & 18 deletions aiohttp/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .client_ws import ClientWebSocketResponse
from .helpers import _SENTINEL, PY_38, sentinel
from .http import HttpVersion, RawRequestMessage
from .typedefs import _SafeApplication, _SafeRequest
from .web import (
Application,
AppRunner,
Expand Down Expand Up @@ -205,7 +206,7 @@ async def __aexit__(
class TestServer(BaseTestServer):
def __init__(
self,
app: Application,
app: _SafeApplication,
*,
scheme: Union[str, _SENTINEL] = sentinel,
host: str = "127.0.0.1",
Expand Down Expand Up @@ -286,8 +287,8 @@ def server(self) -> BaseTestServer:
return self._server

@property
def app(self) -> Optional[Application]:
return cast(Optional[Application], getattr(self._server, "app", None))
def app(self) -> Optional[_SafeApplication]:
return cast(Optional[_SafeApplication], getattr(self._server, "app", None))

@property
def session(self) -> ClientSession:
Expand Down Expand Up @@ -410,7 +411,7 @@ class AioHTTPTestCase(TestCase):
execute function on the test client using asynchronous methods.
"""

async def get_application(self) -> Application:
async def get_application(self) -> _SafeApplication:
"""
This method should be overridden
to return the aiohttp.web.Application
Expand All @@ -419,7 +420,7 @@ async def get_application(self) -> Application:
"""
return self.get_app()

def get_app(self) -> Application:
def get_app(self) -> _SafeApplication:
"""Obsolete method used to constructing web application.

Use .get_application() coroutine instead
Expand All @@ -446,7 +447,7 @@ def tearDown(self) -> None:
async def tearDownAsync(self) -> None:
await self.client.close()

async def get_server(self, app: Application) -> TestServer:
async def get_server(self, app: _SafeApplication) -> TestServer:
"""Return a TestServer instance."""
return TestServer(app)

Expand Down Expand Up @@ -524,16 +525,8 @@ def teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> N


def _create_app_mock() -> mock.MagicMock:
def get_dict(app: Any, key: str) -> Any:
return app.__app_dict[key]

def set_dict(app: Any, key: str, value: Any) -> None:
app.__app_dict[key] = value

app = mock.MagicMock()
app.__app_dict = {}
app.__getitem__ = get_dict
app.__setitem__ = set_dict
app = mock.MagicMock(spec_set=Application)
app.state = {}

app.on_response_prepare = Signal(app)
app.on_response_prepare.freeze()
Expand Down Expand Up @@ -569,7 +562,7 @@ def make_mocked_request(
sslcontext: Optional[SSLContext] = None,
client_max_size: int = 1024 ** 2,
loop: Any = ...,
) -> Request:
) -> _SafeRequest:
"""Creates mocked web.Request testing purposes.

Useful in unit tests, when spinning full web server is overkill or
Expand Down Expand Up @@ -632,7 +625,7 @@ def make_mocked_request(
if payload is sentinel:
payload = mock.Mock()

req = Request(
req: _SafeRequest = Request(
message, payload, protocol, writer, task, loop, client_max_size=client_max_size
)

Expand Down
12 changes: 9 additions & 3 deletions aiohttp/typedefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Any,
Awaitable,
Callable,
Dict,
Iterable,
Mapping,
Tuple,
Expand All @@ -18,18 +19,23 @@
DEFAULT_JSON_DECODER = json.loads

if TYPE_CHECKING: # pragma: no cover
from http.cookies import BaseCookie, Morsel

from .web import Application, Request, StreamResponse

_CIMultiDict = CIMultiDict[str]
_CIMultiDictProxy = CIMultiDictProxy[str]
_MultiDict = MultiDict[str]
_MultiDictProxy = MultiDictProxy[str]
from http.cookies import BaseCookie, Morsel

from .web import Request, StreamResponse
_SafeApplication = Application[Dict[str, object]]
_SafeRequest = Request[Dict[str, object]]
else:
_CIMultiDict = CIMultiDict
_CIMultiDictProxy = CIMultiDictProxy
_MultiDict = MultiDict
_MultiDictProxy = MultiDictProxy
_SafeApplication = "Application"
_SafeRequest = "Request"

Byteish = Union[bytes, bytearray, memoryview]
JSONEncoder = Callable[[Any], str]
Expand Down
7 changes: 4 additions & 3 deletions aiohttp/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from .abc import AbstractAccessLogger
from .log import access_logger
from .typedefs import _SafeApplication
from .web_app import Application as Application, CleanupError as CleanupError
from .web_exceptions import (
HTTPAccepted as HTTPAccepted,
Expand Down Expand Up @@ -284,7 +285,7 @@


async def _run_app(
app: Union[Application, Awaitable[Application]],
app: Union[_SafeApplication, Awaitable[_SafeApplication]],
*,
host: Optional[Union[str, HostSequence]] = None,
port: Optional[int] = None,
Expand All @@ -306,7 +307,7 @@ async def _run_app(
if asyncio.iscoroutine(app):
app = await app # type: ignore[misc]

app = cast(Application, app)
app = cast(_SafeApplication, app)

runner = AppRunner(
app,
Expand Down Expand Up @@ -459,7 +460,7 @@ def _cancel_tasks(


def run_app(
app: Union[Application, Awaitable[Application]],
app: Union[Application[Any], Awaitable[Application[Any]]],
*,
debug: bool = False,
host: Optional[Union[str, HostSequence]] = None,
Expand Down