Skip to content

Commit

Permalink
Merge branch 'master' into timeout-nowait
Browse files Browse the repository at this point in the history
  • Loading branch information
Dreamsorcerer authored May 14, 2023
2 parents 6ad70c2 + a5d6418 commit 5b688d9
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 223 deletions.
6 changes: 0 additions & 6 deletions .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,5 @@ ignore_missing_imports = True
[mypy-gunicorn.*]
ignore_missing_imports = True

[mypy-tokio]
ignore_missing_imports = True

[mypy-uvloop]
ignore_missing_imports = True

[mypy-python_on_whales]
ignore_missing_imports = True
2 changes: 2 additions & 0 deletions CHANGES/6594.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Exported ``HTTPMove`` which can be used to catch any redirection request
that has a location -- :user:`dreamsorcerer`.
1 change: 1 addition & 0 deletions CHANGES/7281.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed support for unsupported ``tokio`` event loop -- by :user:`Dreamsorcerer`
22 changes: 17 additions & 5 deletions aiohttp/http_websocket.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""WebSocket protocol versions 13 and 8."""

import asyncio
import collections
import functools
import json
import random
Expand All @@ -10,7 +9,18 @@
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 @@ -80,10 +90,12 @@ class WSMsgType(IntEnum):
DEFAULT_LIMIT: Final[int] = 2**16


_WSMessageBase = collections.namedtuple("_WSMessageBase", ["type", "data", "extra"])

class WSMessage(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):
def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any:
"""Return parsed JSON data.
Expand Down
27 changes: 11 additions & 16 deletions aiohttp/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import contextlib
import inspect
import warnings
from typing import Any, Awaitable, Callable, Dict, Generator, Optional, Type, Union
from typing import Any, Awaitable, Callable, Dict, Iterator, Optional, Type, Union

import pytest

Expand All @@ -22,14 +22,11 @@
try:
import uvloop
except ImportError: # pragma: no cover
uvloop = None

try:
import tokio
except ImportError: # pragma: no cover
tokio = None
uvloop = None # type: ignore[assignment]

AiohttpClient = Callable[[Union[Application, BaseTestServer]], Awaitable[TestClient]]
AiohttpRawServer = Callable[[Application], Awaitable[RawTestServer]]
AiohttpServer = Callable[[Application], Awaitable[TestServer]]


def pytest_addoption(parser): # type: ignore[no-untyped-def]
Expand All @@ -43,7 +40,7 @@ def pytest_addoption(parser): # type: ignore[no-untyped-def]
"--aiohttp-loop",
action="store",
default="pyloop",
help="run tests with specific loop: pyloop, uvloop, tokio or all",
help="run tests with specific loop: pyloop, uvloop or all",
)
parser.addoption(
"--aiohttp-enable-loop-debug",
Expand Down Expand Up @@ -198,16 +195,14 @@ def pytest_generate_tests(metafunc): # type: ignore[no-untyped-def]
return

loops = metafunc.config.option.aiohttp_loop
avail_factories: Dict[str, Type[asyncio.AbstractEventLoopPolicy]]
avail_factories = {"pyloop": asyncio.DefaultEventLoopPolicy}

if uvloop is not None: # pragma: no cover
avail_factories["uvloop"] = uvloop.EventLoopPolicy

if tokio is not None: # pragma: no cover
avail_factories["tokio"] = tokio.EventLoopPolicy

if loops == "all":
loops = "pyloop,uvloop?,tokio?"
loops = "pyloop,uvloop?"

factories = {} # type: ignore[var-annotated]
for name in loops.split(","):
Expand Down Expand Up @@ -250,13 +245,13 @@ def proactor_loop(): # type: ignore[no-untyped-def]


@pytest.fixture
def aiohttp_unused_port(): # type: ignore[no-untyped-def]
def aiohttp_unused_port() -> Callable[[], int]:
"""Return a port that is unused on the current host."""
return _unused_port


@pytest.fixture
def aiohttp_server(loop): # type: ignore[no-untyped-def]
def aiohttp_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpServer]:
"""Factory to create a TestServer instance, given an app.
aiohttp_server(app, **kwargs)
Expand All @@ -279,7 +274,7 @@ async def finalize() -> None:


@pytest.fixture
def aiohttp_raw_server(loop): # type: ignore[no-untyped-def]
def aiohttp_raw_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpRawServer]:
"""Factory to create a RawTestServer instance, given a web handler.
aiohttp_raw_server(handler, **kwargs)
Expand Down Expand Up @@ -331,7 +326,7 @@ def test_login(aiohttp_client):
@pytest.fixture
def aiohttp_client(
loop: asyncio.AbstractEventLoop, aiohttp_client_cls: Type[TestClient]
) -> Generator[AiohttpClient, None, None]:
) -> Iterator[AiohttpClient]:
"""Factory to create a TestClient instance.
aiohttp_client(app, **kwargs)
Expand Down
35 changes: 21 additions & 14 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 StrOrURL
from .web import (
Application,
AppRunner,
Expand Down Expand Up @@ -148,14 +149,14 @@ async def start_server(self, **kwargs: Any) -> None:
async def _make_runner(self, **kwargs: Any) -> BaseRunner:
pass

def make_url(self, path: str) -> URL:
def make_url(self, path: StrOrURL) -> URL:
assert self._root is not None
url = URL(path)
if not self.skip_url_asserts:
assert not url.is_absolute()
return self._root.join(url)
else:
return URL(str(self._root) + path)
return URL(str(self._root) + str(path))

@property
def started(self) -> bool:
Expand Down Expand Up @@ -304,16 +305,20 @@ def session(self) -> ClientSession:
"""
return self._session

def make_url(self, path: str) -> URL:
def make_url(self, path: StrOrURL) -> URL:
return self._server.make_url(path)

async def _request(self, method: str, path: str, **kwargs: Any) -> ClientResponse:
async def _request(
self, method: str, path: StrOrURL, **kwargs: Any
) -> ClientResponse:
resp = await self._session.request(method, self.make_url(path), **kwargs)
# save it to close later
self._responses.append(resp)
return resp

def request(self, method: str, path: str, **kwargs: Any) -> _RequestContextManager:
def request(
self, method: str, path: StrOrURL, **kwargs: Any
) -> _RequestContextManager:
"""Routes a request to tested http server.
The interface is identical to aiohttp.ClientSession.request,
Expand All @@ -323,43 +328,45 @@ def request(self, method: str, path: str, **kwargs: Any) -> _RequestContextManag
"""
return _RequestContextManager(self._request(method, path, **kwargs))

def get(self, path: str, **kwargs: Any) -> _RequestContextManager:
def get(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP GET request."""
return _RequestContextManager(self._request(hdrs.METH_GET, path, **kwargs))

def post(self, path: str, **kwargs: Any) -> _RequestContextManager:
def post(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP POST request."""
return _RequestContextManager(self._request(hdrs.METH_POST, path, **kwargs))

def options(self, path: str, **kwargs: Any) -> _RequestContextManager:
def options(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP OPTIONS request."""
return _RequestContextManager(self._request(hdrs.METH_OPTIONS, path, **kwargs))

def head(self, path: str, **kwargs: Any) -> _RequestContextManager:
def head(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP HEAD request."""
return _RequestContextManager(self._request(hdrs.METH_HEAD, path, **kwargs))

def put(self, path: str, **kwargs: Any) -> _RequestContextManager:
def put(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP PUT request."""
return _RequestContextManager(self._request(hdrs.METH_PUT, path, **kwargs))

def patch(self, path: str, **kwargs: Any) -> _RequestContextManager:
def patch(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP PATCH request."""
return _RequestContextManager(self._request(hdrs.METH_PATCH, path, **kwargs))

def delete(self, path: str, **kwargs: Any) -> _RequestContextManager:
def delete(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager:
"""Perform an HTTP PATCH request."""
return _RequestContextManager(self._request(hdrs.METH_DELETE, path, **kwargs))

def ws_connect(self, path: str, **kwargs: Any) -> _WSRequestContextManager:
def ws_connect(self, path: StrOrURL, **kwargs: Any) -> _WSRequestContextManager:
"""Initiate websocket connection.
The api corresponds to aiohttp.ClientSession.ws_connect.
"""
return _WSRequestContextManager(self._ws_connect(path, **kwargs))

async def _ws_connect(self, path: str, **kwargs: Any) -> ClientWebSocketResponse:
async def _ws_connect(
self, path: StrOrURL, **kwargs: Any
) -> ClientWebSocketResponse:
ws = await self._session.ws_connect(self.make_url(path), **kwargs)
self._websockets.append(ws)
return ws
Expand Down
2 changes: 2 additions & 0 deletions aiohttp/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
HTTPLengthRequired,
HTTPMethodNotAllowed,
HTTPMisdirectedRequest,
HTTPMove,
HTTPMovedPermanently,
HTTPMultipleChoices,
HTTPNetworkAuthenticationRequired,
Expand Down Expand Up @@ -157,6 +158,7 @@
"HTTPLengthRequired",
"HTTPMethodNotAllowed",
"HTTPMisdirectedRequest",
"HTTPMove",
"HTTPMovedPermanently",
"HTTPMultipleChoices",
"HTTPNetworkAuthenticationRequired",
Expand Down
18 changes: 3 additions & 15 deletions aiohttp/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
SSLContext = object # type: ignore[misc,assignment]


__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker", "GunicornTokioWebWorker")
__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker")


class GunicornWebWorker(base.Worker): # type: ignore[misc,no-any-unimported]
Expand Down Expand Up @@ -185,7 +185,7 @@ def init_signals(self) -> None:
# there is no need to reset it.
signal.signal(signal.SIGCHLD, signal.SIG_DFL)

def handle_quit(self, sig: int, frame: FrameType) -> None:
def handle_quit(self, sig: int, frame: Optional[FrameType]) -> None:
self.alive = False

# worker_int callback
Expand All @@ -194,7 +194,7 @@ def handle_quit(self, sig: int, frame: FrameType) -> None:
# wakeup closing process
self._notify_waiter_done()

def handle_abort(self, sig: int, frame: FrameType) -> None:
def handle_abort(self, sig: int, frame: Optional[FrameType]) -> None:
self.alive = False
self.exit_code = 1
self.cfg.worker_abort(self)
Expand Down Expand Up @@ -243,15 +243,3 @@ def init_process(self) -> None:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

super().init_process()


class GunicornTokioWebWorker(GunicornWebWorker):
def init_process(self) -> None: # pragma: no cover
import tokio

# Setup tokio policy, so that every
# asyncio.get_event_loop() will create an instance
# of tokio event loop.
asyncio.set_event_loop_policy(tokio.EventLoopPolicy())

super().init_process()
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ cchardet==2.1.7; python_version < "3.10" # Unmaintained: aio-libs/aiohttp#6819
charset-normalizer==2.0.12
frozenlist==1.3.1
gunicorn==20.1.0
uvloop==0.14.0; platform_system!="Windows" and implementation_name=="cpython" and python_version<"3.9" # MagicStack/uvloop#14
uvloop==0.17.0; platform_system!="Windows" and implementation_name=="cpython" and python_version<"3.9" # MagicStack/uvloop#14
yarl==1.9.2
2 changes: 1 addition & 1 deletion requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ uritemplate==4.1.1
# via gidgethub
urllib3==1.26.7
# via requests
uvloop==0.14.0 ; platform_system != "Windows" and implementation_name == "cpython" and python_version < "3.9"
uvloop==0.17.0 ; platform_system != "Windows" and implementation_name == "cpython" and python_version < "3.9"
# via -r requirements/base.txt
virtualenv==20.10.0
# via pre-commit
Expand Down
1 change: 1 addition & 0 deletions requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ mypy==0.982; implementation_name=="cpython"
pre-commit==2.17.0
pytest==6.2.5
slotscheck==0.8.0
uvloop==0.17.0; platform_system!="Windows"
7 changes: 0 additions & 7 deletions tests/test_run_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,13 @@
HAS_IPV6 = False


# tokio event loop does not allow to override attributes
def skip_if_no_dict(loop: Any) -> None:
if not hasattr(loop, "__dict__"):
pytest.skip("can not override loop attributes")


def skip_if_on_windows() -> None:
if platform.system() == "Windows":
pytest.skip("the test is not valid for Windows")


@pytest.fixture
def patched_loop(loop: Any):
skip_if_no_dict(loop)
server = mock.Mock()
server.wait_closed = make_mocked_coro(None)
loop.create_server = make_mocked_coro(server)
Expand Down
Loading

0 comments on commit 5b688d9

Please sign in to comment.