Switch follow redirects default (#1808)

* Switch default on allow_redirects to False

* allow_redirects -> follow_redirects

* Update follow_redirects default in top-level API

* Update docs on follow_redirects
This commit is contained in:
Tom Christie 2021-09-13 13:21:22 +01:00 committed by GitHub
parent 0b4a83257b
commit 47266d763b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 161 deletions

View File

@ -4,11 +4,68 @@ HTTPX aims to be broadly compatible with the `requests` API.
This documentation outlines places where the API differs...
## Client instances
The HTTPX equivalent of `requests.Session` is `httpx.Client`.
```python
session = requests.Session(**kwargs)
```
is generally equivalent to
```python
client = httpx.Client(**kwargs)
```
## Request URLs
Accessing `response.url` will return a `URL` instance, rather than a string.
Use `str(response.url)` if you need a string instance.
## Redirects
Unlike `requests`, HTTPX does **not follow redirects by default**.
We differ in behaviour here [because auto-redirects can easily mask unnecessary network
calls being made](https://github.com/encode/httpx/discussions/1785).
You can still enable behaviour to automatically follow redirects, but you need to
do so explicitly...
```python
respose = client.get(url, follow_redirects=True)
```
Or else instantiate a client, with redirect following enabled by default...
```python
client = httpx.Client(follow_redirects=True)
```
## Determining the next redirect request
The `requests` library exposes an attribute `response.next`, which can be used to obtain the next redirect request.
```python
session = requests.Session()
request = requests.Request("GET", ...).prepare()
while request is not None:
response = session.send(request, allow_redirects=False)
request = response.next
```
In HTTPX, this attribute is instead named `response.next_request`. For example:
```python
client = httpx.Client()
request = client.build_request("GET", ...)
while request is not None:
response = client.send(request)
request = response.next_request
```
## Request Content
For uploading raw text or binary content we prefer to use a `content` parameter,
@ -31,6 +88,12 @@ httpx.post(..., data={"message": "Hello, world"})
Using the `data=<text/byte content>` will raise a deprecation warning,
and is expected to be fully removed with the HTTPX 1.0 release.
## Upload files
HTTPX strictly enforces that upload files must be opened in binary mode, in order
to avoid character encoding issues that can result from attempting to upload files
opened in text mode.
## Content encoding
HTTPX uses `utf-8` for encoding `str` request bodies. For example, when using `content=<str>` the request body will be encoded to `utf-8` before being sent over the wire. This differs from Requests which uses `latin1`. If you need an explicit encoding, pass encoded bytes explictly, e.g. `content=<str>.encode("latin1")`.
@ -110,25 +173,11 @@ If you really do need to send request data using these http methods you should u
We don't support `response.is_ok` since the naming is ambiguous there, and might incorrectly imply an equivalence to `response.status_code == codes.OK`. Instead we provide the `response.is_error` property. Use `if not response.is_error:` instead of `if response.is_ok:`.
## Client instances
The HTTPX equivalent of `requests.Session` is `httpx.Client`.
```python
session = requests.Session(**kwargs)
```
is generally equivalent to
```python
client = httpx.Client(**kwargs)
```
## Request instantiation
There is no notion of [prepared requests](https://requests.readthedocs.io/en/stable/user/advanced/#prepared-requests) in HTTPX. If you need to customize request instantiation, see [Request instances](advanced.md#request-instances).
Besides, `httpx.Request()` does not support the `auth`, `timeout`, `allow_redirects`, `proxies`, `verify` and `cert` parameters. However these are available in `httpx.request`, `httpx.get`, `httpx.post` etc., as well as on [`Client` instances](advanced.md#client-instances).
Besides, `httpx.Request()` does not support the `auth`, `timeout`, `follow_redirects`, `proxies`, `verify` and `cert` parameters. However these are available in `httpx.request`, `httpx.get`, `httpx.post` etc., as well as on [`Client` instances](advanced.md#client-instances).
## Mocking
@ -144,25 +193,6 @@ On the other hand, HTTPX uses [HTTPCore](https://github.com/encode/httpcore) as
`requests` omits `params` whose values are `None` (e.g. `requests.get(..., params={"foo": None})`). This is not supported by HTTPX.
## HEAD redirection
In `requests`, all top-level API follow redirects by default except `HEAD`.
In consideration of consistency, we make `HEAD` follow redirects by default in HTTPX.
## Determining the next redirect request
When using `allow_redirects=False`, the `requests` library exposes an attribute `response.next`, which can be used to obtain the next redirect request.
In HTTPX, this attribute is instead named `response.next_request`. For example:
```python
client = httpx.Client()
request = client.build_request("GET", ...)
while request is not None:
response = client.send(request, allow_redirects=False)
request = response.next_request
```
## Event Hooks
`requests` allows event hooks to mutate `Request` and `Response` objects. See [examples](https://requests.readthedocs.io/en/master/user/advanced/#event-hooks) given in the documentation for `requests`.

View File

@ -128,7 +128,7 @@ Often Web API responses will be encoded as JSON.
To include additional headers in the outgoing request, use the `headers` keyword argument:
```pycon
>>> url = 'http://httpbin.org/headers'
>>> url = 'https://httpbin.org/headers'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = httpx.get(url, headers=headers)
```
@ -380,7 +380,7 @@ If you're using streaming responses in any of these ways then the `response.cont
Any cookies that are set on the response can be easily accessed:
```pycon
>>> r = httpx.get('http://httpbin.org/cookies/set?chocolate=chip', allow_redirects=False)
>>> r = httpx.get('https://httpbin.org/cookies/set?chocolate=chip')
>>> r.cookies['chocolate']
'chip'
```
@ -389,7 +389,7 @@ To include cookies in an outgoing request, use the `cookies` parameter:
```pycon
>>> cookies = {"peanut": "butter"}
>>> r = httpx.get('http://httpbin.org/cookies', cookies=cookies)
>>> r = httpx.get('https://httpbin.org/cookies', cookies=cookies)
>>> r.json()
{'cookies': {'peanut': 'butter'}}
```
@ -408,17 +408,25 @@ with additional API for accessing cookies by their domain or path.
## Redirection and History
By default, HTTPX will follow redirects for all HTTP methods.
The `history` property of the response can be used to inspect any followed redirects.
It contains a list of any redirect responses that were followed, in the order
in which they were made.
By default, HTTPX will **not** follow redirects for all HTTP methods, although
this can be explicitly enabled.
For example, GitHub redirects all HTTP requests to HTTPS.
```pycon
>>> r = httpx.get('http://github.com/')
>>> r.status_code
301
>>> r.history
[]
>>> r.next_request
<Request('GET', 'https://github.com/')>
```
You can modify the default redirection handling with the `follow_redirects` parameter:
```pycon
>>> r = httpx.get('http://github.com/', follow_redirects=True)
>>> r.url
URL('https://github.com/')
>>> r.status_code
@ -427,15 +435,9 @@ URL('https://github.com/')
[<Response [301 Moved Permanently]>]
```
You can modify the default redirection handling with the allow_redirects parameter:
```pycon
>>> r = httpx.get('http://github.com/', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]
```
The `history` property of the response can be used to inspect any followed redirects.
It contains a list of any redirect responses that were followed, in the order
in which they were made.
## Timeouts

View File

@ -34,7 +34,7 @@ def request(
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
allow_redirects: bool = True,
follow_redirects: bool = False,
verify: VerifyTypes = True,
cert: CertTypes = None,
trust_env: bool = True,
@ -66,7 +66,7 @@ def request(
* **proxies** - *(optional)* A dictionary mapping proxy keys to proxy URLs.
* **timeout** - *(optional)* The timeout configuration to use when sending
the request.
* **allow_redirects** - *(optional)* Enables or disables HTTP redirects.
* **follow_redirects** - *(optional)* Enables or disables HTTP redirects.
* **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
verify the identity of requested hosts. Either `True` (default CA bundle),
a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
@ -107,7 +107,7 @@ def request(
params=params,
headers=headers,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
)
@ -126,7 +126,7 @@ def stream(
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
allow_redirects: bool = True,
follow_redirects: bool = False,
verify: VerifyTypes = True,
cert: CertTypes = None,
trust_env: bool = True,
@ -159,7 +159,7 @@ def stream(
params=params,
headers=headers,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
) as response:
yield response
@ -172,7 +172,7 @@ def get(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -194,7 +194,7 @@ def get(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -210,7 +210,7 @@ def options(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -232,7 +232,7 @@ def options(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -248,7 +248,7 @@ def head(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -270,7 +270,7 @@ def head(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -290,7 +290,7 @@ def post(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -313,7 +313,7 @@ def post(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -333,7 +333,7 @@ def put(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -356,7 +356,7 @@ def put(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -376,7 +376,7 @@ def patch(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -399,7 +399,7 @@ def patch(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,
@ -415,7 +415,7 @@ def delete(
cookies: CookieTypes = None,
auth: AuthTypes = None,
proxies: ProxiesTypes = None,
allow_redirects: bool = True,
follow_redirects: bool = False,
cert: CertTypes = None,
verify: VerifyTypes = True,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
@ -437,7 +437,7 @@ def delete(
cookies=cookies,
auth=auth,
proxies=proxies,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
cert=cert,
verify=verify,
timeout=timeout,

View File

@ -165,7 +165,7 @@ class BaseClient:
headers: HeaderTypes = None,
cookies: CookieTypes = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
allow_redirects: bool = True,
follow_redirects: bool = False,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
event_hooks: typing.Mapping[str, typing.List[typing.Callable]] = None,
base_url: URLTypes = "",
@ -180,7 +180,7 @@ class BaseClient:
self.headers = Headers(headers)
self._cookies = Cookies(cookies)
self._timeout = Timeout(timeout)
self.allow_redirects = allow_redirects
self.follow_redirects = follow_redirects
self.max_redirects = max_redirects
self._event_hooks = {
"request": list(event_hooks.get("request", [])),
@ -613,6 +613,7 @@ class Client(BaseClient):
proxies: ProxiesTypes = None,
mounts: typing.Mapping[str, BaseTransport] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
limits: Limits = DEFAULT_LIMITS,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
event_hooks: typing.Mapping[str, typing.List[typing.Callable]] = None,
@ -627,6 +628,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
timeout=timeout,
follow_redirects=follow_redirects,
max_redirects=max_redirects,
event_hooks=event_hooks,
base_url=base_url,
@ -746,7 +748,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -785,7 +787,7 @@ class Client(BaseClient):
cookies=cookies,
)
return self.send(
request, auth=auth, allow_redirects=allow_redirects, timeout=timeout
request, auth=auth, follow_redirects=follow_redirects, timeout=timeout
)
@contextmanager
@ -802,7 +804,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> typing.Iterator[Response]:
"""
@ -829,7 +831,7 @@ class Client(BaseClient):
response = self.send(
request=request,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
stream=True,
)
@ -844,7 +846,7 @@ class Client(BaseClient):
*,
stream: bool = False,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -867,10 +869,10 @@ class Client(BaseClient):
timeout = (
self.timeout if isinstance(timeout, UseClientDefault) else Timeout(timeout)
)
allow_redirects = (
self.allow_redirects
if isinstance(allow_redirects, UseClientDefault)
else allow_redirects
follow_redirects = (
self.follow_redirects
if isinstance(follow_redirects, UseClientDefault)
else follow_redirects
)
auth = self._build_request_auth(request, auth)
@ -879,7 +881,7 @@ class Client(BaseClient):
request,
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
history=[],
)
try:
@ -897,7 +899,7 @@ class Client(BaseClient):
request: Request,
auth: Auth,
timeout: Timeout,
allow_redirects: bool,
follow_redirects: bool,
history: typing.List[Response],
) -> Response:
auth_flow = auth.sync_auth_flow(request)
@ -908,7 +910,7 @@ class Client(BaseClient):
response = self._send_handling_redirects(
request,
timeout=timeout,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
history=history,
)
try:
@ -932,7 +934,7 @@ class Client(BaseClient):
self,
request: Request,
timeout: Timeout,
allow_redirects: bool,
follow_redirects: bool,
history: typing.List[Response],
) -> Response:
while True:
@ -956,7 +958,7 @@ class Client(BaseClient):
request = self._build_redirect_request(request, response)
history = history + [response]
if allow_redirects:
if follow_redirects:
response.read()
else:
response.next_request = request
@ -1013,7 +1015,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1028,7 +1030,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1040,7 +1042,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1055,7 +1057,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1067,7 +1069,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1082,7 +1084,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1098,7 +1100,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1117,7 +1119,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1133,7 +1135,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1152,7 +1154,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1168,7 +1170,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1187,7 +1189,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1199,7 +1201,7 @@ class Client(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1214,7 +1216,7 @@ class Client(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1329,6 +1331,7 @@ class AsyncClient(BaseClient):
proxies: ProxiesTypes = None,
mounts: typing.Mapping[str, AsyncBaseTransport] = None,
timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
follow_redirects: bool = False,
limits: Limits = DEFAULT_LIMITS,
max_redirects: int = DEFAULT_MAX_REDIRECTS,
event_hooks: typing.Mapping[str, typing.List[typing.Callable]] = None,
@ -1343,6 +1346,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
timeout=timeout,
follow_redirects=follow_redirects,
max_redirects=max_redirects,
event_hooks=event_hooks,
base_url=base_url,
@ -1461,7 +1465,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1492,7 +1496,7 @@ class AsyncClient(BaseClient):
cookies=cookies,
)
response = await self.send(
request, auth=auth, allow_redirects=allow_redirects, timeout=timeout
request, auth=auth, follow_redirects=follow_redirects, timeout=timeout
)
return response
@ -1510,7 +1514,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> typing.AsyncIterator[Response]:
"""
@ -1537,7 +1541,7 @@ class AsyncClient(BaseClient):
response = await self.send(
request=request,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
stream=True,
)
@ -1552,7 +1556,7 @@ class AsyncClient(BaseClient):
*,
stream: bool = False,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1575,10 +1579,10 @@ class AsyncClient(BaseClient):
timeout = (
self.timeout if isinstance(timeout, UseClientDefault) else Timeout(timeout)
)
allow_redirects = (
self.allow_redirects
if isinstance(allow_redirects, UseClientDefault)
else allow_redirects
follow_redirects = (
self.follow_redirects
if isinstance(follow_redirects, UseClientDefault)
else follow_redirects
)
auth = self._build_request_auth(request, auth)
@ -1587,7 +1591,7 @@ class AsyncClient(BaseClient):
request,
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
history=[],
)
try:
@ -1605,7 +1609,7 @@ class AsyncClient(BaseClient):
request: Request,
auth: Auth,
timeout: Timeout,
allow_redirects: bool,
follow_redirects: bool,
history: typing.List[Response],
) -> Response:
auth_flow = auth.async_auth_flow(request)
@ -1616,7 +1620,7 @@ class AsyncClient(BaseClient):
response = await self._send_handling_redirects(
request,
timeout=timeout,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
history=history,
)
try:
@ -1640,7 +1644,7 @@ class AsyncClient(BaseClient):
self,
request: Request,
timeout: Timeout,
allow_redirects: bool,
follow_redirects: bool,
history: typing.List[Response],
) -> Response:
while True:
@ -1665,7 +1669,7 @@ class AsyncClient(BaseClient):
request = self._build_redirect_request(request, response)
history = history + [response]
if allow_redirects:
if follow_redirects:
await response.aread()
else:
response.next_request = request
@ -1729,7 +1733,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1744,7 +1748,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1756,7 +1760,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1771,7 +1775,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1783,7 +1787,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1798,7 +1802,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1814,7 +1818,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1833,7 +1837,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1849,7 +1853,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1868,7 +1872,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1884,7 +1888,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1903,7 +1907,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)
@ -1915,7 +1919,7 @@ class AsyncClient(BaseClient):
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
allow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
follow_redirects: typing.Union[bool, UseClientDefault] = USE_CLIENT_DEFAULT,
timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,
) -> Response:
"""
@ -1930,7 +1934,7 @@ class AsyncClient(BaseClient):
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
follow_redirects=follow_redirects,
timeout=timeout,
)

View File

@ -1209,7 +1209,7 @@ class Response:
self._request: typing.Optional[Request] = request
# When allow_redirects=False and a redirect is received,
# When follow_redirects=False and a redirect is received,
# the client will set `response.next_request`.
self.next_request: typing.Optional[Request] = None

View File

@ -131,7 +131,9 @@ def test_event_hooks_with_redirect():
event_hooks = {"request": [on_request], "response": [on_response]}
with httpx.Client(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
event_hooks=event_hooks,
transport=httpx.MockTransport(app),
follow_redirects=True,
) as http:
http.get("http://127.0.0.1:8000/redirect", auth=("username", "password"))
@ -186,7 +188,9 @@ async def test_async_event_hooks_with_redirect():
event_hooks = {"request": [on_request], "response": [on_response]}
async with httpx.AsyncClient(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
event_hooks=event_hooks,
transport=httpx.MockTransport(app),
follow_redirects=True,
) as http:
await http.get("http://127.0.0.1:8000/redirect", auth=("username", "password"))

View File

@ -113,7 +113,7 @@ def redirects(request: httpx.Request) -> httpx.Response:
def test_redirect_301():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.post("https://example.org/redirect_301")
response = client.post("https://example.org/redirect_301", follow_redirects=True)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert len(response.history) == 1
@ -121,7 +121,7 @@ def test_redirect_301():
def test_redirect_302():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.post("https://example.org/redirect_302")
response = client.post("https://example.org/redirect_302", follow_redirects=True)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert len(response.history) == 1
@ -129,7 +129,7 @@ def test_redirect_302():
def test_redirect_303():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("https://example.org/redirect_303")
response = client.get("https://example.org/redirect_303", follow_redirects=True)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert len(response.history) == 1
@ -138,12 +138,12 @@ def test_redirect_303():
def test_next_request():
client = httpx.Client(transport=httpx.MockTransport(redirects))
request = client.build_request("POST", "https://example.org/redirect_303")
response = client.send(request, allow_redirects=False)
response = client.send(request, follow_redirects=False)
assert response.status_code == httpx.codes.SEE_OTHER
assert response.url == "https://example.org/redirect_303"
assert response.next_request is not None
response = client.send(response.next_request, allow_redirects=False)
response = client.send(response.next_request, follow_redirects=False)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert response.next_request is None
@ -153,12 +153,12 @@ def test_next_request():
async def test_async_next_request():
async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:
request = client.build_request("POST", "https://example.org/redirect_303")
response = await client.send(request, allow_redirects=False)
response = await client.send(request, follow_redirects=False)
assert response.status_code == httpx.codes.SEE_OTHER
assert response.url == "https://example.org/redirect_303"
assert response.next_request is not None
response = await client.send(response.next_request, allow_redirects=False)
response = await client.send(response.next_request, follow_redirects=False)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert response.next_request is None
@ -169,7 +169,7 @@ def test_head_redirect():
Contrary to Requests, redirects remain enabled by default for HEAD requests.
"""
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.head("https://example.org/redirect_302")
response = client.head("https://example.org/redirect_302", follow_redirects=True)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert response.request.method == "HEAD"
@ -179,7 +179,9 @@ def test_head_redirect():
def test_relative_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("https://example.org/relative_redirect")
response = client.get(
"https://example.org/relative_redirect", follow_redirects=True
)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert len(response.history) == 1
@ -188,7 +190,9 @@ def test_relative_redirect():
def test_malformed_redirect():
# https://github.com/encode/httpx/issues/771
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("http://example.org/malformed_redirect")
response = client.get(
"http://example.org/malformed_redirect", follow_redirects=True
)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org:443/"
assert len(response.history) == 1
@ -197,12 +201,14 @@ def test_malformed_redirect():
def test_invalid_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
with pytest.raises(httpx.RemoteProtocolError):
client.get("http://example.org/invalid_redirect")
client.get("http://example.org/invalid_redirect", follow_redirects=True)
def test_no_scheme_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("https://example.org/no_scheme_redirect")
response = client.get(
"https://example.org/no_scheme_redirect", follow_redirects=True
)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/"
assert len(response.history) == 1
@ -210,7 +216,9 @@ def test_no_scheme_redirect():
def test_fragment_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("https://example.org/relative_redirect#fragment")
response = client.get(
"https://example.org/relative_redirect#fragment", follow_redirects=True
)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/#fragment"
assert len(response.history) == 1
@ -218,7 +226,9 @@ def test_fragment_redirect():
def test_multiple_redirects():
client = httpx.Client(transport=httpx.MockTransport(redirects))
response = client.get("https://example.org/multiple_redirects?count=20")
response = client.get(
"https://example.org/multiple_redirects?count=20", follow_redirects=True
)
assert response.status_code == httpx.codes.OK
assert response.url == "https://example.org/multiple_redirects"
assert len(response.history) == 20
@ -232,26 +242,30 @@ def test_multiple_redirects():
async def test_async_too_many_redirects():
async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:
with pytest.raises(httpx.TooManyRedirects):
await client.get("https://example.org/multiple_redirects?count=21")
await client.get(
"https://example.org/multiple_redirects?count=21", follow_redirects=True
)
def test_sync_too_many_redirects():
client = httpx.Client(transport=httpx.MockTransport(redirects))
with pytest.raises(httpx.TooManyRedirects):
client.get("https://example.org/multiple_redirects?count=21")
client.get(
"https://example.org/multiple_redirects?count=21", follow_redirects=True
)
def test_redirect_loop():
client = httpx.Client(transport=httpx.MockTransport(redirects))
with pytest.raises(httpx.TooManyRedirects):
client.get("https://example.org/redirect_loop")
client.get("https://example.org/redirect_loop", follow_redirects=True)
def test_cross_domain_redirect_with_auth_header():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.com/cross_domain"
headers = {"Authorization": "abc"}
response = client.get(url, headers=headers)
response = client.get(url, headers=headers, follow_redirects=True)
assert response.url == "https://example.org/cross_domain_target"
assert "authorization" not in response.json()["headers"]
@ -259,7 +273,7 @@ def test_cross_domain_redirect_with_auth_header():
def test_cross_domain_redirect_with_auth():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.com/cross_domain"
response = client.get(url, auth=("user", "pass"))
response = client.get(url, auth=("user", "pass"), follow_redirects=True)
assert response.url == "https://example.org/cross_domain_target"
assert "authorization" not in response.json()["headers"]
@ -268,7 +282,7 @@ def test_same_domain_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.org/cross_domain"
headers = {"Authorization": "abc"}
response = client.get(url, headers=headers)
response = client.get(url, headers=headers, follow_redirects=True)
assert response.url == "https://example.org/cross_domain_target"
assert response.json()["headers"]["authorization"] == "abc"
@ -280,7 +294,7 @@ def test_body_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.org/redirect_body"
content = b"Example request body"
response = client.post(url, content=content)
response = client.post(url, content=content, follow_redirects=True)
assert response.url == "https://example.org/redirect_body_target"
assert response.json()["body"] == "Example request body"
assert "content-length" in response.json()["headers"]
@ -293,7 +307,7 @@ def test_no_body_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.org/redirect_no_body"
content = b"Example request body"
response = client.post(url, content=content)
response = client.post(url, content=content, follow_redirects=True)
assert response.url == "https://example.org/redirect_body_target"
assert response.json()["body"] == ""
assert "content-length" not in response.json()["headers"]
@ -302,7 +316,7 @@ def test_no_body_redirect():
def test_can_stream_if_no_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.org/redirect_301"
with client.stream("GET", url, allow_redirects=False) as response:
with client.stream("GET", url, follow_redirects=False) as response:
assert not response.is_closed
assert response.status_code == httpx.codes.MOVED_PERMANENTLY
assert response.headers["location"] == "https://example.org/"
@ -316,13 +330,13 @@ def test_cannot_redirect_streaming_body():
yield b"Example request body" # pragma: nocover
with pytest.raises(httpx.StreamConsumed):
client.post(url, content=streaming_body())
client.post(url, content=streaming_body(), follow_redirects=True)
def test_cross_subdomain_redirect():
client = httpx.Client(transport=httpx.MockTransport(redirects))
url = "https://example.com/cross_subdomain"
response = client.get(url)
response = client.get(url, follow_redirects=True)
assert response.url == "https://www.example.org/cross_subdomain"
@ -360,7 +374,9 @@ def cookie_sessions(request: httpx.Request) -> httpx.Response:
def test_redirect_cookie_behavior():
client = httpx.Client(transport=httpx.MockTransport(cookie_sessions))
client = httpx.Client(
transport=httpx.MockTransport(cookie_sessions), follow_redirects=True
)
# The client is not logged in.
response = client.get("https://example.com/")
@ -391,7 +407,7 @@ def test_redirect_cookie_behavior():
def test_redirect_custom_scheme():
client = httpx.Client(transport=httpx.MockTransport(redirects))
with pytest.raises(httpx.UnsupportedProtocol) as e:
client.post("https://example.org/redirect_custom_scheme")
client.post("https://example.org/redirect_custom_scheme", follow_redirects=True)
assert str(e.value) == "Scheme 'market' not supported."
@ -399,4 +415,6 @@ def test_redirect_custom_scheme():
async def test_async_invalid_redirect():
async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:
with pytest.raises(httpx.RemoteProtocolError):
await client.get("http://example.org/invalid_redirect")
await client.get(
"http://example.org/invalid_redirect", follow_redirects=True
)

View File

@ -120,7 +120,7 @@ async def test_logs_trace(server, capsys):
@pytest.mark.asyncio
async def test_logs_redirect_chain(server, capsys):
with override_log_level("debug"):
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(follow_redirects=True) as client:
response = await client.get(server.url.copy_with(path="/redirect_301"))
assert response.status_code == 200