Skip to content

Commit 3d43466

Browse files
Move body logic to Auth, add sync_auth_flow, add NoAuth
1 parent be809fb commit 3d43466

File tree

2 files changed

+56
-22
lines changed

2 files changed

+56
-22
lines changed

httpx/_auth.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ class Auth:
1818
To implement a custom authentication scheme, subclass `Auth` and override
1919
the `.auth_flow()` method.
2020
21-
If the authentication scheme does I/O, such as disk access or network calls, or uses
22-
synchronization primitives such as locks, you should override `.async_auth_flow()`
23-
to provide an async-friendly implementation that will be used by the `AsyncClient`.
24-
Usage of sync I/O within an async codebase would block the event loop, and could
25-
cause performance issues.
21+
If the authentication scheme does I/O such as disk access or network calls, or uses
22+
synchronization primitives such as locks, you should override `.sync_auth_flow()`
23+
and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized
24+
implementations that will be used by `Client` and `AsyncClient` respectively.
2625
"""
2726

2827
requires_request_body = False
@@ -50,7 +49,34 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non
5049
5150
You can dispatch as many requests as is necessary.
5251
"""
53-
yield request
52+
raise NotImplementedError(
53+
"Override 'auth_flow' to implement a custom auth class."
54+
) # pragma: no cover
55+
56+
def sync_auth_flow(
57+
self, request: Request
58+
) -> typing.Generator[Request, Response, None]:
59+
"""
60+
Execute the authentication flow synchronously.
61+
62+
By default, this defers to `.auth_flow()`. You should override this method
63+
when the authentication scheme does I/O and/or uses concurrency primitives.
64+
"""
65+
if self.requires_request_body:
66+
request.read()
67+
68+
flow = self.auth_flow(request)
69+
request = next(flow)
70+
71+
while True:
72+
response = yield request
73+
if self.requires_response_body:
74+
response.read()
75+
76+
try:
77+
request = flow.send(response)
78+
except StopIteration:
79+
break
5480

5581
async def async_auth_flow(
5682
self, request: Request
@@ -59,20 +85,34 @@ async def async_auth_flow(
5985
Execute the authentication flow asynchronously.
6086
6187
By default, this defers to `.auth_flow()`. You should override this method
62-
when the authentication scheme does I/O, such as disk access or network calls,
63-
or uses concurrency primitives such as locks.
88+
when the authentication scheme does I/O and/or uses concurrency primitives.
6489
"""
90+
if self.requires_request_body:
91+
await request.aread()
92+
6593
flow = self.auth_flow(request)
6694
request = next(flow)
6795

6896
while True:
6997
response = yield request
98+
if self.requires_response_body:
99+
await response.aread()
100+
70101
try:
71102
request = flow.send(response)
72103
except StopIteration:
73104
break
74105

75106

107+
class NoAuth(Auth):
108+
"""
109+
A 'do nothing' scheme for use when no authentication should be performed.
110+
"""
111+
112+
def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:
113+
yield request
114+
115+
76116
class FunctionAuth(Auth):
77117
"""
78118
Allows the 'auth' argument to be passed as a simple callable function,

httpx/_client.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import httpcore
66

7-
from ._auth import Auth, BasicAuth, FunctionAuth
7+
from ._auth import Auth, BasicAuth, FunctionAuth, NoAuth
88
from ._config import (
99
DEFAULT_LIMITS,
1010
DEFAULT_MAX_REDIRECTS,
@@ -338,7 +338,7 @@ def _build_request_auth(
338338
if credentials is not None:
339339
return BasicAuth(username=credentials[0], password=credentials[1])
340340

341-
return Auth()
341+
return NoAuth()
342342

343343
def _build_redirect_request(self, request: Request, response: Response) -> Request:
344344
"""
@@ -760,15 +760,12 @@ def _send_handling_auth(
760760
auth: Auth,
761761
timeout: Timeout,
762762
) -> Response:
763-
if auth.requires_request_body:
764-
request.read()
763+
auth_flow = auth.sync_auth_flow(request)
764+
request = auth_flow.send(None) # type: ignore
765765

766-
auth_flow = auth.auth_flow(request)
767-
request = next(auth_flow)
768766
while True:
769767
response = self._send_single_request(request, timeout)
770-
if auth.requires_response_body:
771-
response.read()
768+
772769
try:
773770
next_request = auth_flow.send(response)
774771
except StopIteration:
@@ -1362,15 +1359,12 @@ async def _send_handling_auth(
13621359
auth: Auth,
13631360
timeout: Timeout,
13641361
) -> Response:
1365-
if auth.requires_request_body:
1366-
await request.aread()
1367-
13681362
auth_flow = auth.async_auth_flow(request)
1369-
request = await auth_flow.__anext__()
1363+
request = await auth_flow.asend(None) # type: ignore
1364+
13701365
while True:
13711366
response = await self._send_single_request(request, timeout)
1372-
if auth.requires_response_body:
1373-
await response.aread()
1367+
13741368
try:
13751369
next_request = await auth_flow.asend(response)
13761370
except StopAsyncIteration:

0 commit comments

Comments
 (0)