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
This commit is contained in:
Dobrovolsky Bogdan 2019-09-08 16:03:59 +03:00 committed by Seth Michael Larson
parent 71648ece09
commit 08edfac37d
6 changed files with 144 additions and 14 deletions

View 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

View 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()`

View 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,

View 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):
"""

View 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!")

View 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.