]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Add `build_request` to `Client` (#319)
authorDobrovolsky Bogdan <bogdan.gm24@gmail.com>
Sun, 8 Sep 2019 13:03:59 +0000 (16:03 +0300)
committerSeth Michael Larson <sethmichaellarson@gmail.com>
Sun, 8 Sep 2019 13:03:59 +0000 (08:03 -0500)
* 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

docs/advanced.md
docs/api.md
httpx/client.py
tests/client/test_async_client.py
tests/client/test_client.py
tests/conftest.py

index 424d397cf5455ec64c136e451bb482a2201a1620..20122f205e4221b2a67d78228f7e859c70e3332f 100644 (file)
@@ -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)
+<Response [200 OK]>
+```
+
 ## .netrc Support
 
 HTTPX supports .netrc file. In `trust_env=True` cases, if auth parameter is
index ba26e204f0e348571bc1408a4356b0305b0e7b1d..14cc4be9b59dc9b47a0a4b25a55280376b875cd3 100644 (file)
@@ -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()`
 
index 0e493e1993ccbf0e6c804e2b741298e61d3c0725..b3eaff883ed92e5909c12f18fb5bf0a54a401ba7 100644 (file)
@@ -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,
index c028c1b3fb099e22fc09fab4e4f162b62bc98569..472b013bdf6780ab0aaaf37863ad968e177f6e2c 100644 (file)
@@ -14,6 +14,20 @@ async def test_get(server, backend):
     assert repr(response) == "<Response [200 OK]>"
 
 
+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):
     """
index 73979209af3a484fbd2503ed144d3fa081b6583a..90e47ee3ad03896dd39e5b758aa40ff2eb4c01fe 100644 (file)
@@ -19,6 +19,21 @@ def test_get(server):
     assert repr(response) == "<Response [200 OK]>"
 
 
+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!")
index bcef1595264e254b194c7aa9374a49eacfe2b864..4f8a11a57b38844f7db968088dcdf820d6a49383 100644 (file)
@@ -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.