From: Dobrovolsky Bogdan Date: Sun, 8 Sep 2019 13:03:59 +0000 (+0300) Subject: Add `build_request` to `Client` (#319) X-Git-Tag: 0.7.3~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=08edfac37dbe94faf5b99f09d62cadf608d010e0;p=thirdparty%2Fhttpx.git Add `build_request` to `Client` (#319) * Update documentation * Update documentation * Update tests Rename `_send` -> `_get_response` Update documentation * Code format with black * Change documentation example to OPTIONS * * Update documentation Small code reformat * `echo_headers` return json * Simplify test * Fix typo --- diff --git a/docs/advanced.md b/docs/advanced.md index 424d397c..20122f20 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -57,6 +57,19 @@ dispatch = httpx.WSGIDispatch(app=app, remote_addr="1.2.3.4") client = httpx.Client(dispatch=dispatch) ``` +## Build Request + +You can use `Client.build_request()` to build a request and +make modifications before sending the request. + +```python +>>> client = httpx.Client() +>>> req = client.build_request("OPTIONS", "https://example.com") +>>> req.url.full_path = "*" # Build an 'OPTIONS *' request for CORS +>>> client.send(r) + +``` + ## .netrc Support HTTPX supports .netrc file. In `trust_env=True` cases, if auth parameter is diff --git a/docs/api.md b/docs/api.md index ba26e204..14cc4be9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -16,6 +16,7 @@ * `patch(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` * `delete(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` * `request(method, url, [data], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `build_request(method, url, [data], [files], [json], [params], [headers], [cookies])` ## `Client` @@ -37,6 +38,7 @@ * `def .patch(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` * `def .delete(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` * `def .request(method, url, [data], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .build_request(method, url, [data], [files], [json], [params], [headers], [cookies])` * `def .send(request, [stream], [allow_redirects], [verify], [cert], [timeout])` * `def .close()` diff --git a/httpx/client.py b/httpx/client.py index 0e493e19..b3eaff88 100644 --- a/httpx/client.py +++ b/httpx/client.py @@ -151,7 +151,7 @@ class BaseClient: return merged_headers return headers - async def send( + async def _get_response( self, request: AsyncRequest, *, @@ -235,6 +235,33 @@ class BaseClient: return None + def build_request( + self, + method: str, + url: URLTypes, + *, + data: AsyncRequestData = None, + files: RequestFiles = None, + json: typing.Any = None, + params: QueryParamTypes = None, + headers: HeaderTypes = None, + cookies: CookieTypes = None, + ) -> AsyncRequest: + url = self.merge_url(url) + headers = self.merge_headers(headers) + cookies = self.merge_cookies(cookies) + request = AsyncRequest( + method, + url, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + ) + return request + class AsyncClient(BaseClient): async def get( @@ -490,12 +517,9 @@ class AsyncClient(BaseClient): timeout: TimeoutTypes = None, trust_env: bool = None, ) -> AsyncResponse: - url = self.merge_url(url) - headers = self.merge_headers(headers) - cookies = self.merge_cookies(cookies) - request = AsyncRequest( - method, - url, + request = self.build_request( + method=method, + url=url, data=data, files=files, json=json, @@ -515,6 +539,29 @@ class AsyncClient(BaseClient): ) return response + async def send( + self, + request: AsyncRequest, + *, + stream: bool = False, + auth: AuthTypes = None, + allow_redirects: bool = True, + verify: VerifyTypes = None, + cert: CertTypes = None, + timeout: TimeoutTypes = None, + trust_env: bool = None, + ) -> AsyncResponse: + return await self._get_response( + request=request, + stream=stream, + auth=auth, + allow_redirects=allow_redirects, + verify=verify, + cert=cert, + timeout=timeout, + trust_env=trust_env, + ) + async def close(self) -> None: await self.dispatch.close() @@ -592,12 +639,9 @@ class Client(BaseClient): timeout: TimeoutTypes = None, trust_env: bool = None, ) -> Response: - url = self.merge_url(url) - headers = self.merge_headers(headers) - cookies = self.merge_cookies(cookies) - request = AsyncRequest( - method, - url, + request = self.build_request( + method=method, + url=url, data=self._async_request_data(data), files=files, json=json, @@ -605,9 +649,33 @@ class Client(BaseClient): headers=headers, cookies=cookies, ) + response = self.send( + request, + stream=stream, + auth=auth, + allow_redirects=allow_redirects, + verify=verify, + cert=cert, + timeout=timeout, + trust_env=trust_env, + ) + return response + + def send( + self, + request: AsyncRequest, + *, + stream: bool = False, + auth: AuthTypes = None, + allow_redirects: bool = True, + verify: VerifyTypes = None, + cert: CertTypes = None, + timeout: TimeoutTypes = None, + trust_env: bool = None, + ) -> Response: concurrency_backend = self.concurrency_backend - coroutine = self.send + coroutine = self._get_response args = [request] kwargs = { "stream": True, diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py index c028c1b3..472b013b 100644 --- a/tests/client/test_async_client.py +++ b/tests/client/test_async_client.py @@ -14,6 +14,20 @@ async def test_get(server, backend): assert repr(response) == "" +async def test_build_request(server, backend): + url = server.url.copy_with(path="/echo_headers") + headers = {"Custom-header": "value"} + async with httpx.AsyncClient(backend=backend) as client: + request = client.build_request("GET", url) + request.headers.update(headers) + response = await client.send(request) + + assert response.status_code == 200 + assert response.url == url + + assert response.json()["Custom-header"] == "value" + + @pytest.mark.asyncio async def test_get_no_backend(server): """ diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 73979209..90e47ee3 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -19,6 +19,21 @@ def test_get(server): assert repr(response) == "" +def test_build_request(server): + url = server.url.copy_with(path="/echo_headers") + headers = {"Custom-header": "value"} + + with httpx.Client() as http: + request = http.build_request("GET", url) + request.headers.update(headers) + response = http.send(request) + + assert response.status_code == 200 + assert response.url == url + + assert response.json()["Custom-header"] == "value" + + def test_post(server): with httpx.Client() as http: response = http.post(server.url, data=b"Hello, world!") diff --git a/tests/conftest.py b/tests/conftest.py index bcef1595..4f8a11a5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import asyncio +import json import os import threading import time @@ -53,6 +54,8 @@ async def app(scope, receive, send): await status_code(scope, receive, send) elif scope["path"].startswith("/echo_body"): await echo_body(scope, receive, send) + elif scope["path"].startswith("/echo_headers"): + await echo_headers(scope, receive, send) else: await hello_world(scope, receive, send) @@ -111,6 +114,21 @@ async def echo_body(scope, receive, send): await send({"type": "http.response.body", "body": body}) +async def echo_headers(scope, receive, send): + body = {} + for name, value in scope.get("headers", []): + body[name.capitalize().decode()] = value.decode() + + await send( + { + "type": "http.response.start", + "status": 200, + "headers": [[b"content-type", b"application/json"]], + } + ) + await send({"type": "http.response.body", "body": json.dumps(body).encode()}) + + class CAWithPKEncryption(trustme.CA): """Implementation of trustme.CA() that can emit private keys that are encrypted with a password.