Skip to content

Commit

Permalink
Transport API (#1522)
Browse files Browse the repository at this point in the history
* Added httpx.BaseTransport and httpx.AsyncBaseTransport

* Test coverage and default transports to calling .close on __exit__

* BaseTransport documentation

* Use 'handle_request' for the transport API.

* Docs tweaks

* Docs tweaks

* Minor docstring tweak

* Transport API docs

* Drop 'Optional' on Transport API

* Docs tweaks

* Tweak CHANGELOG

* Drop erronous example.py

* Push httpcore exception wrapping out of client into transport (#1524)

* Push httpcore exception wrapping out of client into transport

* Include close/aclose extensions in docstring

* Comment about the request property on RequestError exceptions

* Extensions reason_phrase and http_version as bytes (#1526)

* Extensions reason_phrase and http_version as bytes

* Update BaseTransport docstring

* Neaten up our try...except structure for ensuring responses (#1525)

* Fix CHANGELOG typo

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>

* Fix CHANGELOG typo

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>

* stream: Iterator[bytes] -> stream: Iterable[bytes]

* Use proper bytestream interfaces when calling into httpcore

* Grungy typing workaround due to httpcore using Iterator instead of Iterable in bytestream types

* Update docs/advanced.md

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>

* Consistent typing imports across tranports

* Update docs/advanced.md

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>

Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
  • Loading branch information
tomchristie and florimondmanca committed Mar 24, 2021
1 parent 6cb1672 commit 1a6e254
Show file tree
Hide file tree
Showing 20 changed files with 625 additions and 335 deletions.
21 changes: 19 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,31 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## 0.17.1
## Master

The 0.18.x release series formalises our low-level Transport API, introducing the
base classes `httpx.BaseTransport` and `httpx.AsyncBaseTransport`.

See the "Writing custom transports" documentation and the `httpx.BaseTransport.handle_request()`
docstring for more complete details on implementing custom transports.

Pull request #1522 includes a checklist of differences from the previous `httpcore` transport API,
for developers implementing custom transports.

### Changed

* Transport instances now inherit from `httpx.BaseTransport` or `httpx.AsyncBaseTransport`,
and should implement either the `handle_request` method or `handle_async_request` method.
* The `response.ext` property and `Response(ext=...)` argument are now named `extensions`.

## 0.17.1 (March 15th, 2021)

### Fixed

* Type annotation on `CertTypes` allows `keyfile` and `password` to be optional. (Pull #1503)
* Fix httpcore pinned version. (Pull #1495)

## 0.17.0
## 0.17.0 (February 28th, 2021)

### Added

Expand Down
41 changes: 24 additions & 17 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -1015,31 +1015,39 @@ This [public gist](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165

### Writing custom transports

A transport instance must implement the Transport API defined by
[`httpcore`](https://www.encode.io/httpcore/api/). You
should either subclass `httpcore.AsyncHTTPTransport` to implement a transport to
use with `AsyncClient`, or subclass `httpcore.SyncHTTPTransport` to implement a
transport to use with `Client`.
A transport instance must implement the low-level Transport API, which deals
with sending a single request, and returning a response. You should either
subclass `httpx.BaseTransport` to implement a transport to use with `Client`,
or subclass `httpx.AsyncBaseTransport` to implement a transport to
use with `AsyncClient`.

At the layer of the transport API we're using plain primitives.
No `Request` or `Response` models, no fancy `URL` or `Header` handling.
This strict point of cut-off provides a clear design separation between the
HTTPX API, and the low-level network handling.

See the `handle_request` and `handle_async_request` docstrings for more details
on the specifics of the Transport API.

A complete example of a custom transport implementation would be:

```python
import json
import httpcore
import httpx


class HelloWorldTransport(httpcore.SyncHTTPTransport):
class HelloWorldTransport(httpx.BaseTransport):
"""
A mock transport that always returns a JSON "Hello, world!" response.
"""

def request(self, method, url, headers=None, stream=None, ext=None):
def handle_request(self, method, url, headers, stream, extensions):
message = {"text": "Hello, world!"}
content = json.dumps(message).encode("utf-8")
stream = httpcore.PlainByteStream(content)
stream = [content]
headers = [(b"content-type", b"application/json")]
ext = {"http_version": b"HTTP/1.1"}
return 200, headers, stream, ext
extensions = {}
return 200, headers, stream, extensions
```

Which we can use in the same way:
Expand Down Expand Up @@ -1084,24 +1092,23 @@ which transport an outgoing request should be routed via, with [the same style
used for specifying proxy routing](#routing).

```python
import httpcore
import httpx

class HTTPSRedirectTransport(httpcore.SyncHTTPTransport):
class HTTPSRedirectTransport(httpx.BaseTransport):
"""
A transport that always redirects to HTTPS.
"""

def request(self, method, url, headers=None, stream=None, ext=None):
def handle_request(self, method, url, headers, stream, extensions):
scheme, host, port, path = url
if port is None:
location = b"https://%s%s" % (host, path)
else:
location = b"https://%s:%d%s" % (host, port, path)
stream = httpcore.PlainByteStream(b"")
stream = [b""]
headers = [(b"location", location)]
ext = {"http_version": b"HTTP/1.1"}
return 303, headers, stream, ext
extensions = {}
return 303, headers, stream, extensions


# A client where any `http` requests are always redirected to `https`
Expand Down
3 changes: 3 additions & 0 deletions httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from ._models import URL, Cookies, Headers, QueryParams, Request, Response
from ._status_codes import StatusCode, codes
from ._transports.asgi import ASGITransport
from ._transports.base import AsyncBaseTransport, BaseTransport
from ._transports.default import AsyncHTTPTransport, HTTPTransport
from ._transports.mock import MockTransport
from ._transports.wsgi import WSGITransport
Expand All @@ -45,9 +46,11 @@
"__title__",
"__version__",
"ASGITransport",
"AsyncBaseTransport",
"AsyncClient",
"AsyncHTTPTransport",
"Auth",
"BaseTransport",
"BasicAuth",
"Client",
"CloseError",
Expand Down
Loading

0 comments on commit 1a6e254

Please sign in to comment.