Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 144 additions & 22 deletions docs/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,68 @@

HTTP requests are sent by using a `Client` instance. Client instances are thread safe interfaces that maintain a pool of HTTP connections.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> cli = httpx.Client()
>>> cli
<Client [0 active]>
```

```{ .python .ahttpx .hidden }
>>> cli = ahttpx.Client()
>>> cli
<Client [0 active]>
```

The client representation provides an indication of how many connections are currently in the pool.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> r = cli.get("https://www.example.com")
>>> r = cli.get("https://www.wikipedia.com")
>>> r = cli.get("https://www.theguardian.com")
>>> cli
<Client [0 active, 3 idle]>
```

```{ .python .ahttpx .hidden }
>>> r = await cli.get("https://www.example.com")
>>> r = await cli.get("https://www.wikipedia.com")
>>> r = await cli.get("https://www.theguardian.com")
>>> cli
<Client [0 active, 3 idle]>
```

The connections in the pool can be explicitly closed, using the `close()` method...

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> cli.close()
>>> cli
<Client [0 active]>
```

```{ .python .ahttpx .hidden }
>>> await cli.close()
>>> cli
<Client [0 active]>
```

Client instances support being used in a context managed scope. You can use this style to enforce properly scoped resources, ensuring that the connection pool is cleanly closed when no longer required.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> with httpx.Client() as cli:
... cli.get("https://www.example.com")
... r = cli.get("https://www.example.com")
```

```{ .python .ahttpx .hidden }
>>> async with ahttpx.Client() as cli:
... r = await cli.get("https://www.example.com")
```

It is important to scope the use of client instances as widely as possible.
Expand All @@ -43,13 +76,24 @@ The recommened usage is to *either* a have single global instance created at imp

Client instances can be configured with a base URL that is used when constructing requests...

```python
>>> cli = httpx.Client(url="https://www.httpbin.org")
>>> r = cli.get("/json")
>>> r
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> with httpx.Client(url="https://www.httpbin.org") as cli:
>>> r = cli.get("/json")
>>> print(r)
<Response [200 OK]>
>>> r.request.url
'https://www.httpbin.org/json'
>>> print(r.request.url)
<URL 'https://www.httpbin.org/json'>
```

```{ .python .ahttpx .hidden }
>>> async with ahttpx.Client(url="https://www.httpbin.org") as cli:
>>> r = cli.get("/json")
>>> print(r)
<Response [200 OK]>
>>> print(r.request.url)
<URL 'https://www.httpbin.org/json'>
```

## Setting client headers
Expand All @@ -65,25 +109,46 @@ The default headers are:

You can override this behavior by explicitly specifying the default headers...

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> headers = {"User-Agent": "dev", "Accept-Encoding": "gzip"}
>>> cli = httpx.Client(headers=headers)
>>> r = cli.get("https://www.example.com/")
>>> with httpx.Client(headers=headers) as cli:
>>> r = cli.get("https://www.example.com/")
```

```{ .python .ahttpx .hidden }
>>> headers = {"User-Agent": "dev", "Accept-Encoding": "gzip"}
>>> async with ahttpx.Client(headers=headers) as cli:
>>> r = await cli.get("https://www.example.com/")
```

## Configuring the connection pool

The connection pool used by the client can be configured in order to customise the SSL context, the maximum number of concurrent connections, or the network backend.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> # Setup an SSL context to allow connecting to improperly configured SSL.
>>> no_verify = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> no_verify.check_hostname = False
>>> no_verify.verify_mode = ssl.CERT_NONE
>>> # Instantiate a client with our custom SSL context.
>>> with httpx.ConnectionPool(ssl_context=no_verify) as pool:
>>> with httpx.Client(transport=pool) as cli:
>>> ...
>>> pool = httpx.ConnectionPool(ssl_context=no_verify):
>>> with httpx.Client(transport=pool) as cli:
>>> ...
```

```{ .python .ahttpx .hidden }
>>> # Setup an SSL context to allow connecting to improperly configured SSL.
>>> no_verify = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> no_verify.check_hostname = False
>>> no_verify.verify_mode = ssl.CERT_NONE
>>> # Instantiate a client with our custom SSL context.
>>> pool = ahttpx.ConnectionPool(ssl_context=no_verify)
>>> async with ahttpx.Client(transport=pool) as cli:
>>> ...
```

## Sending requests
Expand All @@ -106,7 +171,9 @@ By default requests are sent using the `ConnectionPool` class. Alternative imple

For example, a mock transport class that doesn't make any network requests and instead always returns a fixed response.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
class MockTransport(httpx.Transport):
def __init__(self, response):
self._response = response
Expand All @@ -120,8 +187,28 @@ class MockTransport(httpx.Transport):

response = httpx.Response(200, content=httpx.Text('Hello, world'))
transport = MockTransport(response=response)
cli = httpx.Client(transport=transport)
print(cli.get('https://www.example.com'))
with httpx.Client(transport=transport) as cli:
r = cli.get('https://www.example.com')
print(r)
```

```{ .python .ahttpx .hidden }
class MockTransport(ahttpx.Transport):
def __init__(self, response):
self._response = response

@contextlib.contextmanager
def send(self, request):
yield response

def close(self):
pass

response = ahttpx.Response(200, content=httpx.Text('Hello, world'))
transport = MockTransport(response=response)
async with ahttpx.Client(transport=transport) as cli:
r = await cli.get('https://www.example.com')
print(r)
```

---
Expand Down Expand Up @@ -151,7 +238,9 @@ If you're working with a large codebase you might want to create a custom client

The following example demonstrates a custom API client that only exposes `GET` and `POST` requests, and always uses JSON payloads.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
class APIClient:
def __init__(self):
self.url = httpx.URL('https://www.example.com')
Expand Down Expand Up @@ -184,6 +273,39 @@ class APIClient:
return response
```

```{ .python .ahttpx .hidden }
class APIClient:
def __init__(self):
self.url = ahttpx.URL('https://www.example.com')
self.headers = ahttpx.Headers({
'Accept-Encoding': 'gzip',
'Connection': 'keep-alive',
'User-Agent': 'dev'
})
self.via = ahttpx.RedirectMiddleware(ahttpx.ConnectionPool())

async def get(self, path: str) -> Response:
request = ahttpx.Request(
method="GET",
url=self.url.join(path),
headers=self.headers,
)
async with self.via.send(request) as response:
await response.read()
return response

async def post(self, path: str, payload: Any) -> ahttpx.Response:
request = ahttpx.Request(
method="POST",
url=self.url.join(path),
headers=self.headers,
content=httpx.JSON(payload),
)
async with self.via.send(request) as response:
await response.read()
return response
```

You can expand on this pattern to provide behavior such as request or response schema validation, consistent timeouts, or standardised logging and exception handling.

---
Expand Down
12 changes: 11 additions & 1 deletion docs/headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@

The `Headers` class provides an immutable case-insensitive multidict interface for accessing HTTP headers.

```python
<div class="tabs"><a onclick="httpx()" class="httpx">httpx</a> <a onclick="ahttpx()" class="ahttpx hidden">ahttpx</a></div>

```{ .python .httpx }
>>> headers = httpx.Headers({"Accept": "*/*"})
>>> headers
<Headers {"Accept": "*/*"}>
>>> headers['accept']
'*/*'
```

```{ .python .ahttpx .hidden }
>>> headers = ahttpx.Headers({"Accept": "*/*"})
>>> headers
<Headers {"Accept": "*/*"}>
>>> headers['accept']
'*/*'
```

Header values should always be printable ASCII strings. Attempting to set invalid header name or value strings will raise a `ValueError`.

### Accessing headers
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ $ pip install --pre ahttpx
200
>>> r.headers['content-type']
'text/html; charset=UTF-8'
>>> r.text
>>> r.text()
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
```

Expand All @@ -46,7 +46,7 @@ $ pip install --pre ahttpx
200
>>> r.headers['content-type']
'text/html; charset=UTF-8'
>>> r.text
>>> await r.text()
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
```

Expand Down
Loading