From: Tom Christie Date: Mon, 13 Sep 2021 12:21:22 +0000 (+0100) Subject: Switch follow redirects default (#1808) X-Git-Tag: 1.0.0.beta0~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=47266d763bdef9dc99f9c9d09d49d441b87802e6;p=thirdparty%2Fhttpx.git 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 --- diff --git a/docs/compatibility.md b/docs/compatibility.md index 7a8dc7ee..78073394 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -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=` 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=` 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=.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`. diff --git a/docs/quickstart.md b/docs/quickstart.md index 4afaff24..23e17652 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -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,35 +408,37 @@ 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.url -URL('https://github.com/') >>> r.status_code -200 +301 >>> r.history -[] +[] +>>> r.next_request + ``` -You can modify the default redirection handling with the allow_redirects parameter: +You can modify the default redirection handling with the `follow_redirects` parameter: ```pycon ->>> r = httpx.get('http://github.com/', allow_redirects=False) +>>> r = httpx.get('http://github.com/', follow_redirects=True) +>>> r.url +URL('https://github.com/') >>> r.status_code -301 +200 >>> 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 HTTPX defaults to including reasonable timeouts for all network operations, diff --git a/httpx/_api.py b/httpx/_api.py index da818538..eb81d464 100644 --- a/httpx/_api.py +++ b/httpx/_api.py @@ -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, diff --git a/httpx/_client.py b/httpx/_client.py index 9afe8132..6e8bb2f3 100644 --- a/httpx/_client.py +++ b/httpx/_client.py @@ -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, ) diff --git a/httpx/_models.py b/httpx/_models.py index f8dd7731..0a54a6fa 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -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 diff --git a/tests/client/test_event_hooks.py b/tests/client/test_event_hooks.py index e73c0d9d..7c7ce55d 100644 --- a/tests/client/test_event_hooks.py +++ b/tests/client/test_event_hooks.py @@ -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")) diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py index 22c5aa0f..87d9cdfa 100644 --- a/tests/client/test_redirects.py +++ b/tests/client/test_redirects.py @@ -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 + ) diff --git a/tests/test_utils.py b/tests/test_utils.py index ae4b3aa9..cf69f957 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -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