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

implement response check #3892

Merged
merged 8 commits into from Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all 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/3892.feature
@@ -0,0 +1 @@
allow ``raise_for_status`` to be a coroutine
13 changes: 10 additions & 3 deletions aiohttp/client.py
Expand Up @@ -11,6 +11,8 @@
from types import SimpleNamespace, TracebackType
from typing import ( # noqa
Any,
Awaitable,
Callable,
Coroutine,
Generator,
Generic,
Expand Down Expand Up @@ -192,7 +194,7 @@ def __init__(self, *, connector: Optional[BaseConnector]=None,
version: HttpVersion=http.HttpVersion11,
cookie_jar: Optional[AbstractCookieJar]=None,
connector_owner: bool=True,
raise_for_status: bool=False,
raise_for_status: Union[bool, Callable[[ClientResponse], Awaitable[None]]]=False, # noqa
timeout: Union[object, ClientTimeout]=sentinel,
auto_decompress: bool=True,
trust_env: bool=False,
Expand Down Expand Up @@ -299,7 +301,7 @@ async def _request(
compress: Optional[str]=None,
chunked: Optional[bool]=None,
expect100: bool=False,
raise_for_status: Optional[bool]=None,
raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]=None, # noqa
read_until_eof: bool=True,
proxy: Optional[StrOrURL]=None,
proxy_auth: Optional[BasicAuth]=None,
Expand Down Expand Up @@ -542,7 +544,12 @@ async def _request(
# check response status
if raise_for_status is None:
raise_for_status = self._raise_for_status
if raise_for_status:

if raise_for_status is None:
pass
elif callable(raise_for_status):
await raise_for_status(resp)
elif raise_for_status:
resp.raise_for_status()

# register connection
Expand Down
16 changes: 16 additions & 0 deletions docs/client_reference.rst
Expand Up @@ -115,6 +115,22 @@ The client session supports the context manager protocol for self closing.
requests where you need to handle responses with status 400 or
higher.

You can also provide a coroutine which takes the response as an
argument and can raise an exception based on custom logic, e.g.::

async def custom_check(response):
if response.status not in {201, 202}:
raise RuntimeError('expected either 201 or 202')
text = await response.text()
if 'apple pie' not in text:
raise RuntimeError('I wanted to see "apple pie" in response')

client_session = aiohttp.ClientSession(raise_for_status=custom_check)
...

As with boolean values, you're free to set this on the session and/or
overwrite it on a per-request basis.

:param timeout: a :class:`ClientTimeout` settings structure, 5min
total timeout by default.

Expand Down
50 changes: 50 additions & 0 deletions tests/test_client_functional.py
Expand Up @@ -2152,6 +2152,56 @@ async def handler(request):
assert False, "never executed" # pragma: no cover


async def test_session_raise_for_status_coro(aiohttp_client) -> None:

async def handle(request):
return web.Response(text='ok')

app = web.Application()
app.router.add_route('GET', '/', handle)

raise_for_status_called = 0

async def custom_r4s(response):
nonlocal raise_for_status_called
raise_for_status_called += 1
assert response.status == 200
assert response.request_info.method == 'GET'

client = await aiohttp_client(app, raise_for_status=custom_r4s)
await client.get('/')
assert raise_for_status_called == 1
await client.get('/', raise_for_status=True)
assert raise_for_status_called == 1 # custom_r4s not called again
samuelcolvin marked this conversation as resolved.
Show resolved Hide resolved
await client.get('/', raise_for_status=False)
assert raise_for_status_called == 1 # custom_r4s not called again


async def test_request_raise_for_status_coro(aiohttp_client) -> None:

async def handle(request):
return web.Response(text='ok')

app = web.Application()
app.router.add_route('GET', '/', handle)

raise_for_status_called = 0

async def custom_r4s(response):
nonlocal raise_for_status_called
raise_for_status_called += 1
assert response.status == 200
assert response.request_info.method == 'GET'

client = await aiohttp_client(app)
await client.get('/', raise_for_status=custom_r4s)
assert raise_for_status_called == 1
await client.get('/', raise_for_status=True)
assert raise_for_status_called == 1 # custom_r4s not called again
await client.get('/', raise_for_status=False)
assert raise_for_status_called == 1 # custom_r4s not called again


async def test_invalid_idna() -> None:
session = aiohttp.ClientSession()
try:
Expand Down