lonewolf3739: Hello @florimondmanca ,

I have tried to address the issues #794 and #795 with adding the documentation in this PR.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d81336..748ac94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,39 @@ 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.12.1 (March 19th, 2020)
+
+### Fixed
+
+* Resolved packaging issue, where additional files were being included.
+
+## 0.12.0 (March 9th, 2020)
+
+The 0.12 release tightens up the API expectations for `httpx` by switching to private module names to enforce better clarity around public API.
+
+All imports of `httpx` should import from the top-level package only, such as `from httpx import Request`, rather than importing from privately namespaced modules such as `from httpx._models import Request`.
+
+### Added
+
+* Support making response body available to auth classes with `.requires_response_body`. (Pull #803)
+* Export `NetworkError` exception. (Pull #814)
+* Add support for `NO_PROXY` environment variable. (Pull #835)
+
+### Changed
+
+* Switched to private module names. (Pull #785)
+* Drop redirect looping detection and the `RedirectLoop` exception, instead using `TooManyRedirects`. (Pull #819)
+* Drop `backend=...` parameter on `AsyncClient`, in favour of always autodetecting `trio`/`asyncio`. (Pull #791)
+
+### Fixed
+
+* Support basic auth credentials in proxy URLs. (Pull #780)
+* Fix `httpx.Proxy(url, mode="FORWARD_ONLY")` configuration. (Pull #788)
+* Fallback to setting headers as UTF-8 if no encoding is specified. (Pull #820)
+* Close proxy dispatches classes on client close. (Pull #826)
+* Support custom `cert` parameters even if `verify=False`. (Pull #796)
+* Don't support invalid dict-of-dicts form data in `data=...`. (Pull #811)
+
 ## 0.11.1 (January 17th, 2020)
 
 ### Fixed
diff --git a/README.md b/README.md
index 27fd542..0be8618 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
 HTTPX is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.
 
 **Note**: _HTTPX should be considered in beta. We believe we've got the public API to
-a stable point now, but would strongly recommend pinning your dependencies to the `0.11.*`
+a stable point now, but would strongly recommend pinning your dependencies to the `0.12.*`
 release, so that you're able to properly review [API changes between package updates](https://github.com/encode/httpx/blob/master/CHANGELOG.md). A 1.0 release is expected to be issued sometime on or before April 2020._
 
 ---
diff --git a/docs/advanced.md b/docs/advanced.md
index 5d3dba0..8dd7939 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -2,50 +2,86 @@
 
 ## Client Instances
 
-Using a Client instance to make requests will give you HTTP connection pooling,
-will provide cookie persistence, and allows you to apply configuration across
-all outgoing requests.
-
 !!! hint
-    A Client instance is equivalent to a Session instance in `requests`.
+    If you are coming from Requests, `httpx.Client()` is what you can use instead of `requests.Session()`.
 
-!!! note
-    Starting from `httpx==0.10.0`, the default and recommended Client class is `AsyncClient`. This should help prevent breaking changes once sync support is reintroduced.
+### Why use a Client?
+
+!!! note "TL;DR"
+    If you do anything more than experimentation, one-off scripts, or prototypes, then you should use a `Client` instance.
+
+#### More efficient usage of network resources
+
+When you make requests using the top-level API as documented in the [Quickstart](/quickstart) guide, HTTPX has to establish a new connection _for every single request_ (connections are not reused). As the number of requests to a host increases, this quickly becomes inefficient.
+
+On the other hand, a `Client` instance uses [HTTP connection pooling](https://en.wikipedia.org/wiki/HTTP_persistent_connection). This means that when you make several requests to the same host, the `Client` will reuse the underlying TCP connection, instead of recreating one for every single request.
+
+This can bring **significant performance improvements** compared to using the top-level API, including:
+
+- Reduced latency across requests (no handshaking).
+- Reduced CPU usage and round-trips.
+- Reduced network congestion.
 
-    A `Client` synonym remains for compatibility with `httpx==0.9.*` releases, but you are encouraged to migrate to `AsyncClient` as soon as possible.
+#### Extra features
+
+`Client` instances also support features that aren't available at the top-level API, such as:
+
+- Cookie persistance across requests.
+- Applying configuration across all outgoing requests.
+- Sending requests through HTTP proxies.
+- Using [HTTP/2](/http2).
+
+The other sections on this page go into further detail about what you can do with a `Client` instance.
 
 ### Usage
 
 The recommended way to use a `Client` is as a context manager. This will ensure that connections are properly cleaned up when leaving the `with` block:
 
 ```python
->>> with httpx.Client() as client:
-...     r = client.get('https://example.com')
-...
->>> r
-<Response [200 OK]>
+with httpx.Client() as client:
+    ...
 ```
 
 Alternatively, you can explicitly close the connection pool without block-usage using `.close()`:
 
 ```python
->>> client = httpx.Client()
->>> try:
+client = httpx.Client()
+try:
+    ...
+finally:
+    client.close()
+```
+
+### Making requests
+
+Once you have a `Client`, you can send requests using `.get()`, `.post()`, etc. For example:
+
+```python
+>>> with httpx.Client() as client:
 ...     r = client.get('https://example.com')
-... finally:
-...     client.close()
 ...
 >>> r
 <Response [200 OK]>
 ```
 
-Once you have a `Client`, you can use all the features documented in the [Quickstart](/quickstart) guide.
+These methods accept the same arguments as `httpx.get()`, `httpx.post()`, etc. This means that all features documented in the [Quickstart](/quickstart) guide are also available at the client level.
+
+For example, to send a request with custom headers:
+
+```python
+>>> with httpx.Client() as client:
+...     headers = {'X-Custom': 'value'}
+...     r = client.get('https://example.com', headers=headers)
+...
+>>> r.request.headers['X-Custom']
+'value'
+```
 
-### Configuration
+### Sharing configuration across requests
 
 Clients allow you to apply configuration to all outgoing requests by passing parameters to the `Client` constructor.
 
-For example, to apply a set of custom headers on every request:
+For example, to apply a set of custom headers _on every request_:
 
 ```python
 >>> url = 'http://httpbin.org/headers'
@@ -57,15 +93,47 @@ For example, to apply a set of custom headers on every request:
 'my-app/0.0.1'
 ```
 
-!!! note
-    When you provide a parameter at both the client and request levels, one of two things can happen:
+### Merging of configuration
 
-    - For headers, query parameters and cookies, the values are merged into one.
-    - For all other parameters, the request-level value is used.
+When a configuration option is provided at both the client-level and request-level, one of two things can happen:
 
-Additionally, `Client` accepts some parameters that aren't available at the request level.
+- For headers, query parameters and cookies, the values are combined together. For example:
 
-One particularly useful parameter is `base_url`, which allows you to define a base URL to prepend to all outgoing requests:
+```python
+>>> headers = {'X-Auth': 'from-client'}
+>>> params = {'client_id': 'client1'}
+>>> with httpx.Client(headers=headers, params=params) as client:
+...     headers = {'X-Custom': 'from-request'}
+...     params = {'request_id': 'request1'}
+...     r = client.get('https://example.com', headers=headers, params=params)
+...
+>>> r.request.url
+URL('https://example.com?client_id=client1&request_id=request1')
+>>> r.request.headers['X-Auth']
+'from-client'
+>>> r.request.headers['X-Custom']
+'from-request'
+```
+
+- For all other parameters, the request-level value takes priority. For example:
+
+```python
+>>> with httpx.Client(auth=('tom', 'mot123')) as client:
+...     r = client.get('https://example.com', auth=('alice', 'ecila123'))
+...
+>>> _, _, auth = r.request.headers['Authorization'].partition(' ')
+>>> import base64
+>>> base64.b64decode(auth)
+b'alice:ecila123'
+```
+
+If you need finer-grained control on the merging of client-level and request-level parameters, see [Request instances](#request-instances).
+
+### Other Client-only configuration options
+
+Additionally, `Client` accepts some configuration options that aren't available at the request level.
+
+For example, `base_url` allows you to prepend an URL to all outgoing requests:
 
 ```python
 >>> with httpx.Client(base_url='http://httpbin.org') as client:
@@ -75,7 +143,7 @@ One particularly useful parameter is `base_url`, which allows you to define a ba
 URL('http://httpbin.org/headers')
 ```
 
-For a list of all available client-level parameters, see the [`Client` API reference](/api/#client).
+For a list of all available client parameters, see the [`Client`](/api/#client) API reference.
 
 ## Calling into Python Web Apps
 
@@ -120,19 +188,37 @@ with httpx.Client(dispatch=dispatch, base_url="http://testserver") as client:
     ...
 ```
 
-## Build Request
+## Request instances
 
-You can use `Client.build_request()` to build a request and
-make modifications before sending the request.
+For maximum control on what gets sent over the wire, HTTPX supports building explicit [`Request`](/api#request) instances:
 
 ```python
->>> with httpx.Client() as client:
-...     req = client.build_request("OPTIONS", "https://example.com")
-...     req.url.full_path = "*"  # Build an 'OPTIONS *' request for CORS
-...     r = client.send(req)
-...
->>> r
-<Response [200 OK]>
+request = httpx.Request("GET", "https://example.com")
+```
+
+To dispatch a `Request` instance across to the network, create a [`Client` instance](#client-instances) and use `.send()`:
+
+```python
+with httpx.Client() as client:
+    response = client.send(request)
+    ...
+```
+
+If you need to mix client-level and request-level options in a way that is not supported by the default [Merging of parameters](#merging-of-parameters), you can use `.build_request()` and then make arbitrary modifications to the `Request` instance. For example:
+
+```python
+headers = {"X-Api-Key": "...", "X-Client-ID": "ABC123"}
+
+with httpx.Client(headers=headers) as client:
+    request = client.build_request("GET", "https://api.example.com")
+
+    print(request.headers["X-Client-ID"])  # "ABC123"
+
+    # Don't send the API key for this particular request.
+    del request.headers["X-Api-Key"]
+
+    response = client.send(request)
+    ...
 ```
 
 ## .netrc Support
diff --git a/docs/async.md b/docs/async.md
index 81885ee..0e83ebf 100644
--- a/docs/async.md
+++ b/docs/async.md
@@ -95,18 +95,9 @@ await client.post(url, data=upload_bytes())
 
 HTTPX supports either `asyncio` or `trio` as an async environment.
 
-By default it will auto-detect which of those two to use as the backend
+It will auto-detect which of those two to use as the backend
 for socket operations and concurrency primitives.
 
-You can also explicitly select a backend by instantiating a client with the
-`backend` argument...
-
-```python
-client = httpx.AsyncClient(backend='auto')     # Autodetection. The default case.
-client = httpx.AsyncClient(backend='asyncio')  # Use asyncio as the backend.
-client = httpx.AsyncClient(backend='trio')     # Use trio as the backend.
-```
-
 ### [AsyncIO](https://docs.python.org/3/library/asyncio.html)
 
 AsyncIO is Python's [built-in library](https://docs.python.org/3/library/asyncio.html)
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 89235b1..c827f35 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -64,6 +64,12 @@ is generally equivalent to
 client = httpx.Client(**kwargs)
 ```
 
+## Request instantiation
+
+There is no notion of [prepared requests](https://requests.readthedocs.io/en/stable/user/advanced/#prepared-requests) in HTTPX. If you need to customize request instantiation, see [Request instances](/advanced#request-instances).
+
+Besides, `httpx.Request()` does not support the `auth`, `timeout`, `allow_redirects`, `proxies`, `verify` and `cert` parameters. However these are available in `httpx.request`, `httpx.get`, `httpx.post` etc., as well as on [`Client` instances](/advanced#client-instances).
+
 ## Mocking
 
 If you need to mock HTTPX the same way that test utilities like `responses` and `requests-mock` does for `requests`, see [RESPX](https://github.com/lundberg/respx).
diff --git a/docs/index.md b/docs/index.md
index 476f62a..2bc69b5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -30,7 +30,7 @@ HTTPX is a fully featured HTTP client for Python 3, which provides sync and asyn
 !!! note
     HTTPX should currently be considered in beta.
 
-    We believe we've got the public API to a stable point now, but would strongly recommend pinning your dependencies to the `0.11.*` release, so that you're able to properly review [API changes between package updates](https://github.com/encode/httpx/blob/master/CHANGELOG.md).
+    We believe we've got the public API to a stable point now, but would strongly recommend pinning your dependencies to the `0.12.*` release, so that you're able to properly review [API changes between package updates](https://github.com/encode/httpx/blob/master/CHANGELOG.md).
 
     A 1.0 release is expected to be issued sometime on or before April 2020.
 
diff --git a/httpx/__version__.py b/httpx/__version__.py
index 5b763bc..c2a43f6 100644
--- a/httpx/__version__.py
+++ b/httpx/__version__.py
@@ -1,3 +1,3 @@
 __title__ = "httpx"
 __description__ = "A next generation HTTP client, for Python 3."
-__version__ = "0.11.1"
+__version__ = "0.12.1"
diff --git a/httpx/_api.py b/httpx/_api.py
index 19ef229..d42277c 100644
--- a/httpx/_api.py
+++ b/httpx/_api.py
@@ -66,8 +66,6 @@ def request(
     file, key file, password).
     * **trust_env** - *(optional)* Enables or disables usage of environment
     variables for configuration.
-    * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
-    URLs.
 
     **Returns:** `Response`
 
diff --git a/httpx/_client.py b/httpx/_client.py
index 472d455..2a310e2 100644
--- a/httpx/_client.py
+++ b/httpx/_client.py
@@ -5,7 +5,6 @@ from types import TracebackType
 import hstspreload
 
 from ._auth import Auth, AuthTypes, BasicAuth, FunctionAuth
-from ._backends.base import ConcurrencyBackend
 from ._config import (
     DEFAULT_MAX_REDIRECTS,
     DEFAULT_POOL_LIMITS,
@@ -44,7 +43,12 @@ from ._models import (
     URLTypes,
 )
 from ._status_codes import codes
-from ._utils import NetRCInfo, get_environment_proxies, get_logger
+from ._utils import (
+    NetRCInfo,
+    get_environment_proxies,
+    get_logger,
+    should_not_be_proxied,
+)
 
 logger = get_logger(__name__)
 
@@ -518,7 +522,7 @@ class Client(BaseClient):
         Returns the SyncDispatcher instance that should be used for a given URL.
         This will either be the standard connection pool, or a proxy.
         """
-        if self.proxies:
+        if self.proxies and not should_not_be_proxied(url):
             is_default_port = (url.scheme == "http" and url.port == 80) or (
                 url.scheme == "https" and url.port == 443
             )
@@ -862,6 +866,8 @@ class Client(BaseClient):
 
     def close(self) -> None:
         self.dispatch.close()
+        for proxy in self.proxies.values():
+            proxy.close()
 
     def __enter__(self) -> "Client":
         return self
@@ -920,9 +926,6 @@ class AsyncClient(BaseClient):
     over the network.
     * **app** - *(optional)* An ASGI application to send requests to,
     rather than sending actual network requests.
-    * **backend** - *(optional)* A concurrency backend to use when issuing
-    async requests. Either 'auto', 'asyncio', 'trio', or a `ConcurrencyBackend`
-    instance. Defaults to 'auto', for autodetection.
     * **trust_env** - *(optional)* Enables or disables usage of environment
     variables for configuration.
     * **uds** - *(optional)* A path to a Unix domain socket to connect through.
@@ -945,7 +948,6 @@ class AsyncClient(BaseClient):
         base_url: URLTypes = None,
         dispatch: AsyncDispatcher = None,
         app: typing.Callable = None,
-        backend: typing.Union[str, ConcurrencyBackend] = "auto",
         trust_env: bool = True,
         uds: str = None,
     ):
@@ -969,7 +971,6 @@ class AsyncClient(BaseClient):
             pool_limits=pool_limits,
             dispatch=dispatch,
             app=app,
-            backend=backend,
             trust_env=trust_env,
             uds=uds,
         )
@@ -980,7 +981,6 @@ class AsyncClient(BaseClient):
                 cert=cert,
                 http2=http2,
                 pool_limits=pool_limits,
-                backend=backend,
                 trust_env=trust_env,
             )
             for key, proxy in proxy_map.items()
@@ -994,7 +994,6 @@ class AsyncClient(BaseClient):
         pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
         dispatch: AsyncDispatcher = None,
         app: typing.Callable = None,
-        backend: typing.Union[str, ConcurrencyBackend] = "auto",
         trust_env: bool = True,
         uds: str = None,
     ) -> AsyncDispatcher:
@@ -1009,7 +1008,6 @@ class AsyncClient(BaseClient):
             cert=cert,
             http2=http2,
             pool_limits=pool_limits,
-            backend=backend,
             trust_env=trust_env,
             uds=uds,
         )
@@ -1021,7 +1019,6 @@ class AsyncClient(BaseClient):
         cert: CertTypes = None,
         http2: bool = False,
         pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
-        backend: typing.Union[str, ConcurrencyBackend] = "auto",
         trust_env: bool = True,
     ) -> AsyncDispatcher:
         return HTTPProxy(
@@ -1032,7 +1029,6 @@ class AsyncClient(BaseClient):
             cert=cert,
             http2=http2,
             pool_limits=pool_limits,
-            backend=backend,
             trust_env=trust_env,
         )
 
@@ -1041,7 +1037,7 @@ class AsyncClient(BaseClient):
         Returns the AsyncDispatcher instance that should be used for a given URL.
         This will either be the standard connection pool, or a proxy.
         """
-        if self.proxies:
+        if self.proxies and not should_not_be_proxied(url):
             is_default_port = (url.scheme == "http" and url.port == 80) or (
                 url.scheme == "https" and url.port == 443
             )
@@ -1388,6 +1384,8 @@ class AsyncClient(BaseClient):
 
     async def aclose(self) -> None:
         await self.dispatch.close()
+        for proxy in self.proxies.values():
+            await proxy.close()
 
     async def __aenter__(self) -> "AsyncClient":
         return self
diff --git a/httpx/_config.py b/httpx/_config.py
index 1531e2e..ccf0907 100644
--- a/httpx/_config.py
+++ b/httpx/_config.py
@@ -101,6 +101,7 @@ class SSLConfig:
         context = self._create_default_ssl_context()
         context.verify_mode = ssl.CERT_NONE
         context.check_hostname = False
+        self._load_client_certs(context)
         return context
 
     def load_ssl_context_verify(self) -> ssl.SSLContext:
diff --git a/httpx/_content_streams.py b/httpx/_content_streams.py
index ca48326..afffed3 100644
--- a/httpx/_content_streams.py
+++ b/httpx/_content_streams.py
@@ -263,6 +263,7 @@ class MultipartStream(ContentStream):
             return b"".join(parts)
 
         def render_data(self) -> bytes:
+            content: typing.Union[str, bytes]
             if isinstance(self.file, str):
                 content = self.file
             else:
@@ -291,7 +292,7 @@ class MultipartStream(ContentStream):
         self, data: dict, files: dict
     ) -> typing.Iterator[typing.Union["FileField", "DataField"]]:
         for name, value in data.items():
-            if isinstance(value, (list, dict)):
+            if isinstance(value, list):
                 for item in value:
                     yield self.DataField(name=name, value=item)
             else:
@@ -322,7 +323,7 @@ def encode(
     Handles encoding the given `data`, `files`, and `json`, returning
     a `ContentStream` implementation.
     """
-    if data is None:
+    if not data:
         if json is not None:
             return JSONStream(json=json)
         elif files:
@@ -330,7 +331,7 @@ def encode(
         else:
             return ByteStream(body=b"")
     elif isinstance(data, dict):
-        if files is not None:
+        if files:
             return MultipartStream(data=data, files=files, boundary=boundary)
         else:
             return URLEncodedStream(data=data)
diff --git a/setup.cfg b/setup.cfg
index 5b2e962..ed623e6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal = 1
-
 [flake8]
 ignore = W503, E203, B305
 max-line-length = 88

httpx.Client misses the prepare_request method
diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py
index 5943839..269dc26 100644
--- a/tests/client/test_async_client.py
+++ b/tests/client/test_async_client.py
@@ -160,10 +160,3 @@ async def test_uds(uds_server):
     assert response.status_code == 200
     assert response.text == "Hello, world!"
     assert response.encoding == "iso-8859-1"
-
-
-async def test_explicit_backend(server, async_environment):
-    async with httpx.AsyncClient(backend=async_environment) as client:
-        response = await client.get(server.url)
-    assert response.status_code == 200
-    assert response.text == "Hello, world!"
diff --git a/tests/client/test_proxies.py b/tests/client/test_proxies.py
index a100873..e76e520 100644
--- a/tests/client/test_proxies.py
+++ b/tests/client/test_proxies.py
@@ -87,3 +87,32 @@ def test_dispatcher_for_request(url, proxies, expected):
 def test_unsupported_proxy_scheme():
     with pytest.raises(ValueError):
         httpx.AsyncClient(proxies="ftp://127.0.0.1")
+
+
+@pytest.mark.parametrize(
+    ["url", "env", "expected"],
+    [
+        ("http://google.com", {}, None),
+        (
+            "http://google.com",
+            {"HTTP_PROXY": "http://example.com"},
+            "http://example.com",
+        ),
+        (
+            "http://google.com",
+            {"HTTP_PROXY": "http://example.com", "NO_PROXY": "google.com"},
+            None,
+        ),
+    ],
+)
+def test_proxies_environ(monkeypatch, url, env, expected):
+    for name, value in env.items():
+        monkeypatch.setenv(name, value)
+
+    client = httpx.AsyncClient()
+    dispatcher = client.dispatcher_for_url(httpx.URL(url))
+
+    if expected is None:
+        assert dispatcher == client.dispatch
+    else:
+        assert dispatcher.proxy_url == expected
diff --git a/tests/dispatch/test_http2.py b/tests/dispatch/test_http2.py
index 07b0229..b2efcfa 100644
--- a/tests/dispatch/test_http2.py
+++ b/tests/dispatch/test_http2.py
@@ -7,6 +7,7 @@ import pytest
 from h2.settings import SettingCodes
 
 from httpx import AsyncClient, Response, TimeoutException
+from httpx._dispatch.connection_pool import ConnectionPool
 
 from .utils import MockHTTP2Backend
 
@@ -25,8 +26,9 @@ async def app(request):
 @pytest.mark.asyncio
 async def test_http2_get_request():
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         response = await client.get("http://example.org")
 
     assert response.status_code == 200
@@ -36,8 +38,9 @@ async def test_http2_get_request():
 @pytest.mark.asyncio
 async def test_http2_post_request():
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         response = await client.post("http://example.org", data=b"<data>")
 
     assert response.status_code == 200
@@ -51,9 +54,10 @@ async def test_http2_post_request():
 @pytest.mark.asyncio
 async def test_http2_large_post_request():
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
     data = b"a" * 100000
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         response = await client.post("http://example.org", data=data)
     assert response.status_code == 200
     assert json.loads(response.content) == {
@@ -66,8 +70,9 @@ async def test_http2_large_post_request():
 @pytest.mark.asyncio
 async def test_http2_multiple_requests():
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         response_1 = await client.get("http://example.org/1")
         response_2 = await client.get("http://example.org/2")
         response_3 = await client.get("http://example.org/3")
@@ -89,8 +94,9 @@ async def test_http2_reconnect():
     be seemlessly reconnected.
     """
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         response_1 = await client.get("http://example.org/1")
         backend.server.close_connection = True
         response_2 = await client.get("http://example.org/2")
@@ -105,8 +111,9 @@ async def test_http2_reconnect():
 @pytest.mark.asyncio
 async def test_http2_settings_in_handshake():
     backend = MockHTTP2Backend(app=app)
+    dispatch = ConnectionPool(backend=backend, http2=True)
 
-    async with AsyncClient(backend=backend, http2=True) as client:
+    async with AsyncClient(dispatch=dispatch) as client:
         await client.get("http://example.org")
 
     h2_conn = backend.server.conn
diff --git a/tests/dispatch/utils.py b/tests/dispatch/utils.py
index 9939d6d..d2690c9 100644
--- a/tests/dispatch/utils.py
+++ b/tests/dispatch/utils.py
@@ -10,9 +10,9 @@ from httpx._backends.base import BaseSocketStream, lookup_backend
 
 
 class MockHTTP2Backend:
-    def __init__(self, app, backend="auto"):
+    def __init__(self, app):
         self.app = app
-        self.backend = lookup_backend(backend)
+        self.backend = lookup_backend()
         self.server = None
 
     async def open_tcp_stream(
@@ -31,7 +31,7 @@ class MockHTTP2Backend:
 
 
 class MockHTTP2Server(BaseSocketStream):
-    def __init__(self, app, backend):
+    def __init__(self, app, backend: MockHTTP2Backend):
         config = h2.config.H2Configuration(client_side=False)
         self.conn = h2.connection.H2Connection(config=config)
         self.app = app
@@ -151,8 +151,8 @@ class MockHTTP2Server(BaseSocketStream):
 
 
 class MockRawSocketBackend:
-    def __init__(self, data_to_send=b"", backend="auto"):
-        self.backend = lookup_backend(backend)
+    def __init__(self, data_to_send=b""):
+        self.backend = lookup_backend()
         self.data_to_send = data_to_send
         self.received_data = []
         self.stream = MockRawSocketStream(self)
diff --git a/tests/test_content_streams.py b/tests/test_content_streams.py
index 6aa91f4..c5eb961 100644
--- a/tests/test_content_streams.py
+++ b/tests/test_content_streams.py
@@ -189,6 +189,18 @@ async def test_multipart_data_and_files_content():
     )
 
 
+@pytest.mark.asyncio
+async def test_empty_request():
+    stream = encode(data={}, files={})
+    sync_content = b"".join([part for part in stream])
+    async_content = b"".join([part async for part in stream])
+
+    assert stream.can_replay()
+    assert stream.get_headers() == {}
+    assert sync_content == b""
+    assert async_content == b""
+
+
 def test_invalid_argument():
     with pytest.raises(TypeError):
         encode(123)
diff --git a/tests/test_multipart.py b/tests/test_multipart.py
index a08ada8..3df19fe 100644
--- a/tests/test_multipart.py
+++ b/tests/test_multipart.py
@@ -99,8 +99,7 @@ def test_multipart_encode():
         "a": "1",
         "b": b"C",
         "c": ["11", "22", "33"],
-        "d": {"ff": ["1", b"2", "3"], "fff": ["11", b"22", "33"]},
-        "f": "",
+        "d": "",
     }
     files = {"file": ("name.txt", io.BytesIO(b"<file content>"))}
 
@@ -114,9 +113,7 @@ def test_multipart_encode():
             '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n11\r\n'
             '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n22\r\n'
             '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n33\r\n'
-            '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\nff\r\n'
-            '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\nfff\r\n'
-            '--{0}\r\nContent-Disposition: form-data; name="f"\r\n\r\n\r\n'
+            '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\n\r\n'
             '--{0}\r\nContent-Disposition: form-data; name="file";'
             ' filename="name.txt"\r\n'
             "Content-Type: text/plain\r\n\r\n<file content>\r\n"

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.refactor-imports==3.0.2
attrs==25.3.0
autoflake==2.3.1
babel==2.17.0
backrefs==5.8
black==25.1.0
brotlipy==0.7.0
certifi==2025.1.31
cffi==1.17.1
chardet==3.0.4
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
coverage==7.8.0
cryptography==44.0.2
exceptiongroup==1.2.2
flake8==7.2.0
flake8-bugbear==24.12.12
flake8-pie==0.16.0
ghp-import==2.1.0
h11==0.9.0
h2==3.2.0
hpack==3.0.0
hstspreload==2025.1.1
-e git+https://github.com/encode/httpx.git@50d337e807839c21e796fd8b01c67d8a672a9721#egg=httpx
hyperframe==5.2.0
idna==2.10
importlib_metadata==8.6.1
iniconfig==2.1.0
isort==6.0.1
Jinja2==3.1.6
Markdown==3.7
MarkupSafe==3.0.2
mccabe==0.7.0
mergedeep==1.3.4
mkautodoc==0.2.0
mkdocs==1.6.1
mkdocs-get-deps==0.2.0
mkdocs-material==9.6.10
mkdocs-material-extensions==1.3.1
mypy==1.15.0
mypy-extensions==1.0.0
outcome==1.3.0.post0
packaging==24.2
paginate==0.5.7
pathspec==0.12.1
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pycparser==2.22
pyflakes==3.3.2
Pygments==2.19.1
pymdown-extensions==10.14.3
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-trio==0.8.0
python-dateutil==2.9.0.post0
PyYAML==6.0.2
pyyaml_env_tag==0.1
requests==2.32.3
rfc3986==1.5.0
seed-isort-config==2.2.0
six==1.17.0
sniffio==1.3.1
sortedcontainers==2.4.0
tomli==2.2.1
trio==0.29.0
trustme==1.2.1
typing_extensions==4.13.0
urllib3==1.26.20
uvicorn==0.34.0
watchdog==6.0.0
zipp==3.21.0

name: httpx
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aspy-refactor-imports==3.0.2
      - attrs==25.3.0
      - autoflake==2.3.1
      - babel==2.17.0
      - backrefs==5.8
      - black==25.1.0
      - brotlipy==0.7.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==3.0.4
      - charset-normalizer==3.4.1
      - click==8.1.8
      - colorama==0.4.6
      - coverage==7.8.0
      - cryptography==44.0.2
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - flake8-bugbear==24.12.12
      - flake8-pie==0.16.0
      - ghp-import==2.1.0
      - h11==0.9.0
      - h2==3.2.0
      - hpack==3.0.0
      - hstspreload==2025.1.1
      - hyperframe==5.2.0
      - idna==2.10
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isort==6.0.1
      - jinja2==3.1.6
      - markdown==3.7
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mergedeep==1.3.4
      - mkautodoc==0.2.0
      - mkdocs==1.6.1
      - mkdocs-get-deps==0.2.0
      - mkdocs-material==9.6.10
      - mkdocs-material-extensions==1.3.1
      - mypy==1.15.0
      - mypy-extensions==1.0.0
      - outcome==1.3.0.post0
      - packaging==24.2
      - paginate==0.5.7
      - pathspec==0.12.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pyflakes==3.3.2
      - pygments==2.19.1
      - pymdown-extensions==10.14.3
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-trio==0.8.0
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.2
      - pyyaml-env-tag==0.1
      - requests==2.32.3
      - rfc3986==1.5.0
      - seed-isort-config==2.2.0
      - six==1.17.0
      - sniffio==1.3.1
      - sortedcontainers==2.4.0
      - tomli==2.2.1
      - trio==0.29.0
      - trustme==1.2.1
      - typing-extensions==4.13.0
      - urllib3==1.26.20
      - uvicorn==0.34.0
      - watchdog==6.0.0
      - zipp==3.21.0
prefix: /opt/conda/envs/httpx

tests/client/test_proxies.py::test_proxies_environ[http://google.com-env2-None] tests/test_content_streams.py::test_empty_request
tests/client/test_async_client.py::test_get[asyncio] tests/client/test_async_client.py::test_get[trio] tests/client/test_async_client.py::test_build_request[asyncio] tests/client/test_async_client.py::test_build_request[trio] tests/client/test_async_client.py::test_post[asyncio] tests/client/test_async_client.py::test_post[trio] tests/client/test_async_client.py::test_post_json[asyncio] tests/client/test_async_client.py::test_post_json[trio] tests/client/test_async_client.py::test_stream_response[asyncio] tests/client/test_async_client.py::test_stream_response[trio] tests/client/test_async_client.py::test_access_content_stream_response[asyncio] tests/client/test_async_client.py::test_access_content_stream_response[trio] tests/client/test_async_client.py::test_stream_request[asyncio] tests/client/test_async_client.py::test_stream_request[trio] tests/client/test_async_client.py::test_raise_for_status[asyncio] tests/client/test_async_client.py::test_raise_for_status[trio] tests/client/test_async_client.py::test_options[asyncio] tests/client/test_async_client.py::test_options[trio] tests/client/test_async_client.py::test_head[asyncio] tests/client/test_async_client.py::test_head[trio] tests/client/test_async_client.py::test_put[asyncio] tests/client/test_async_client.py::test_put[trio] tests/client/test_async_client.py::test_patch[asyncio] tests/client/test_async_client.py::test_patch[trio] tests/client/test_async_client.py::test_delete[asyncio] tests/client/test_async_client.py::test_delete[trio] tests/client/test_async_client.py::test_100_continue[asyncio] tests/client/test_async_client.py::test_100_continue[trio] tests/client/test_async_client.py::test_uds[asyncio] tests/client/test_async_client.py::test_uds[trio] tests/client/test_proxies.py::test_proxies_parameter[http://127.0.0.1-expected_proxies0] tests/client/test_proxies.py::test_proxies_parameter[proxies1-expected_proxies1] tests/client/test_proxies.py::test_proxies_parameter[proxies2-expected_proxies2] tests/client/test_proxies.py::test_proxies_parameter[proxies3-expected_proxies3] tests/client/test_proxies.py::test_proxies_parameter[proxies4-expected_proxies4] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-None-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies1-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies2-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies3-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com:443-proxies4-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies5-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies6-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies7-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies8-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies9-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies10-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com:8080-proxies11-http://[::1]] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com:8080-proxies12-None] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies13-http://[::1]:4] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies14-http://[::1]:3] tests/client/test_proxies.py::test_dispatcher_for_request[http://example.com-proxies15-http://[::1]:2] tests/client/test_proxies.py::test_unsupported_proxy_scheme tests/client/test_proxies.py::test_proxies_environ[http://google.com-env0-None] tests/client/test_proxies.py::test_proxies_environ[http://google.com-env1-http://example.com] tests/dispatch/test_http2.py::test_http2_get_request tests/dispatch/test_http2.py::test_http2_post_request tests/dispatch/test_http2.py::test_http2_large_post_request tests/dispatch/test_http2.py::test_http2_multiple_requests tests/dispatch/test_http2.py::test_http2_reconnect tests/dispatch/test_http2.py::test_http2_settings_in_handshake tests/dispatch/test_http2.py::test_http2_live_request[asyncio] tests/dispatch/test_http2.py::test_http2_live_request[trio] tests/test_content_streams.py::test_base_content tests/test_content_streams.py::test_empty_content tests/test_content_streams.py::test_bytes_content tests/test_content_streams.py::test_iterator_content tests/test_content_streams.py::test_aiterator_content tests/test_content_streams.py::test_json_content tests/test_content_streams.py::test_urlencoded_content tests/test_content_streams.py::test_multipart_files_content tests/test_content_streams.py::test_multipart_data_and_files_content tests/test_content_streams.py::test_invalid_argument tests/test_multipart.py::test_multipart[abc-abc0] tests/test_multipart.py::test_multipart[abc-abc1] tests/test_multipart.py::test_multipart_invalid_key[abc] tests/test_multipart.py::test_multipart_invalid_key[1] tests/test_multipart.py::test_multipart_invalid_key[2.3] tests/test_multipart.py::test_multipart_invalid_key[None] tests/test_multipart.py::test_multipart_invalid_value[1] tests/test_multipart.py::test_multipart_invalid_value[2.3] tests/test_multipart.py::test_multipart_invalid_value[None] tests/test_multipart.py::test_multipart_invalid_value[value3] tests/test_multipart.py::test_multipart_invalid_value[value4] tests/test_multipart.py::test_multipart_file_tuple tests/test_multipart.py::test_multipart_encode tests/test_multipart.py::test_multipart_encode_files_allows_filenames_as_none tests/test_multipart.py::test_multipart_encode_files_guesses_correct_content_type[example.json-application/json] tests/test_multipart.py::test_multipart_encode_files_guesses_correct_content_type[example.txt-text/plain] tests/test_multipart.py::test_multipart_encode_files_guesses_correct_content_type[no-extension-application/octet-stream] tests/test_multipart.py::test_multipart_encode_files_allows_str_content tests/test_multipart.py::TestHeaderParamHTML5Formatting::test_unicode tests/test_multipart.py::TestHeaderParamHTML5Formatting::test_ascii tests/test_multipart.py::TestHeaderParamHTML5Formatting::test_unicode_escape tests/test_multipart.py::TestHeaderParamHTML5Formatting::test_unicode_with_control_character
docs/index.md docs/async.md docs/compatibility.md httpx/__version__.py CHANGELOG.md httpx/_config.py docs/advanced.md README.md setup.cfg httpx/_api.py httpx/_content_streams.py httpx/_client.py
docs/index.md docs/async.md docs/compatibility.md httpx/__version__.py CHANGELOG.md httpx/_config.py docs/advanced.md README.md setup.cfg httpx/_api.py httpx/_content_streams.py httpx/_client.py