Repository: encode/httpx
Pull request #811 (2020-02-11 09:14:53)

Don't support broken dict-of-dicts case for data argument.
Noticed this looking in to #777.

We're allowing `data={"a": ["1", "2", "3"]}`, which makes sense, but also `data={"a": {"1": ["ignore", "this"], "2": ["ignore", "this"]}}` which doesn't.


Patch:
diff --git a/.gitignore b/.gitignore
index 22e1fbf..49e14cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ site/
 *.egg-info/
 venv*/
 .python-version
+build/
+dist/
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/js/chat.js b/docs/js/chat.js
new file mode 100644
index 0000000..c824549
--- /dev/null
+++ b/docs/js/chat.js
@@ -0,0 +1,3 @@
+((window.gitter = {}).chat = {}).options = {
+  room: 'encode/community'
+};
diff --git a/docs/js/sidecar-1.5.0.js b/docs/js/sidecar-1.5.0.js
new file mode 100644
index 0000000..44899c4
--- /dev/null
+++ b/docs/js/sidecar-1.5.0.js
@@ -0,0 +1,6 @@
+/*!
+ * Gitter Sidecar v1.5.0
+ * https://sidecar.gitter.im/
+ */
+var sidecar=function(t){function e(r){if(i[r])return i[r].exports;var n=i[r]={exports:{},id:r,loaded:!1};return t[r].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var n=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var r in i)Object.prototype.hasOwnProperty.call(i,r)&&(t[r]=i[r])}return t},o=i(1),a=r(o),u=i(3),M=r(u),c=function(t,e){return t[e]||function(){return t[e]={},t[e]}()},s={Chat:M.default},l=n({},c(window,"gitter"),s),d=new a.default("gitter-sidecar-ready",{detail:s});if(document.dispatchEvent(d),!((l.chat||{}).options||{}).disableDefaultChat){var L=c(l,"chat");L.defaultChat=new M.default(L.options||{})}e.default=s},function(t,e){"use strict";function i(t,e){var i=e.bubbles,r=void 0!==i&&i,n=e.cancelable,o=void 0!==n&&n,a=e.detail,u=void 0===a?void 0:a,M=void 0;try{M=new window.CustomEvent(t,{bubbles:r,cancelable:o,detail:u})}catch(e){M=document.createEvent("CustomEvent"),M.initCustomEvent(t,r,o,u)}return M}Object.defineProperty(e,"__esModule",{value:!0}),e.default=i,i.prototype=window.Event.prototype},function(t,e){"use strict";function i(t){if("function"==typeof Symbol)return Symbol(t);var e=Math.random().toString(36).substr(2,8);return t+e}Object.defineProperty(e,"__esModule",{value:!0}),e.default=i},function(t,e,i){"use strict";function r(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e.default=t,e}function n(t){return t&&t.__esModule?t:{default:t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var a=function(){function t(t,e){for(var i=0;i<e.length;i++){var r=e[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,i,r){return i&&t(e.prototype,i),r&&t(e,r),e}}(),u=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var r in i)Object.prototype.hasOwnProperty.call(i,r)&&(t[r]=i[r])}return t},M=i(2),c=n(M),s=i(1),l=n(s),d=i(5),L=n(d),N=i(7),f=n(N),g=i(4),x=r(g),y=i(6),j=n(y),T=function(t){if(t){var e=t.trim().toLowerCase();if("true"===e||"1"===e)return!0;if("false"===e||"0"===e)return!1}return t},b=function(t,e){if(!e)return t;var i={};return Object.keys(t).forEach(function(t){var r="data-"+t;if(e.hasAttribute(r)){var n=e.getAttribute(r);i[t]=n}}),u({},t,i)},E=32,D=13,z=function(t,e){t=(0,x.default)(t);var i=function(t){if("click"===t.type||"keydown"===t.type&&(t.keyCode===E||t.keyCode===D)){for(var i=arguments.length,r=Array(i>1?i-1:0),n=1;n<i;n++)r[n-1]=arguments[n];e.call.apply(e,[this,t].concat(r))}};return x.on(t,"click keydown",i),function(){x.off(t,"click keydown",i)}},h=function(){var t=new L.default,e=t.createElement("style");return e.type="text/css",e.innerHTML=f.default,x.prependElementTo(e,(0,x.default)("head")[0]),t},w=function(t){var e=new L.default,i=t.targetElement;return i.forEach(function(i){var r=b(t,i);if(r.room){var n=e.createElement("iframe");n.setAttribute("frameborder","0"),n.src=""+t.host+r.room+"/~embed",i.appendChild(n)}else console.error("Gitter Sidecar: No room specified for targetElement",i)}),e},p=function(t){var e=t.options,i=new L.default;return e.targetElement.forEach(function(r){var n=i.createElement("div");n.classList.add("gitter-chat-embed-action-bar"),r.insertBefore(n,r.firstChild);var o=i.createElement("a");o.classList.add("gitter-chat-embed-action-bar-item"),o.classList.add("gitter-chat-embed-action-bar-item-pop-out"),o.setAttribute("aria-label","Open Chat in Gitter.im"),o.setAttribute("href",""+e.host+e.room),o.setAttribute("target","_blank"),o.setAttribute("rel","noopener"),n.appendChild(o);var a=i.createElement("button");a.classList.add("gitter-chat-embed-action-bar-item"),a.classList.add("gitter-chat-embed-action-bar-item-collapse-chat"),a.setAttribute("aria-label","Collapse Gitter Chat"),z(a,function(e){t.toggleChat(!1),e.preventDefault()}),n.appendChild(a)}),i},m=document.body||document.documentElement,v={room:void 0,targetElement:void 0,activationElement:void 0,showChatByDefault:!1,preload:!1,useStyles:!0,layout:"fixed",host:"https://gitter.im/"},I=(0,c.default)("DEFAULTS"),A=(0,c.default)("OPTIONS"),C=(0,c.default)("ELEMENTSTORE"),S=(0,c.default)("EVENTHANDLESTORE"),O=(0,c.default)("INIT"),k=(0,c.default)("ISEMBEDDED"),U=(0,c.default)("EMBEDCHATONCE"),Y=(0,c.default)("TOGGLETARGETELEMENTS"),Q=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};o(this,t),this[C]=new L.default,this[S]=[],this[I]=v,this[A]=u({},this[I],e),this[O]()}return a(t,[{key:O,value:function(){var t=this,e=this[A];e.useStyles&&this[C].add(h()),e.targetElement=(0,x.default)(e.targetElement||function(){var e=t[C].createElement("aside");return e.classList.add("gitter-chat-embed"),e.classList.add("is-collapsed"),m.appendChild(e),e}()),e.targetElement.forEach(function(e){var i=t[C].createElement("div");i.classList.add("gitter-chat-embed-loading-wrapper"),i.innerHTML='\n        <div class="gitter-chat-embed-loading-indicator gitter-icon"></div>\n      ',e.insertBefore(i,e.firstChild)}),p(this),e.preload&&this.toggleChat(!1),e.showChatByDefault?this.toggleChat(!0):(void 0===e.activationElement||e.activationElement===!0?e.activationElement=(0,x.default)(function(){var i=t[C].createElement("a");return i.href=""+e.host+e.room,i.innerHTML="Open Chat",i.classList.add("gitter-open-chat-button"),m.appendChild(i),i}()):e.activationElement&&(e.activationElement=(0,x.default)(e.activationElement)),e.activationElement&&(z(e.activationElement,function(e){t.toggleChat(!0),e.preventDefault()}),e.targetElement.forEach(function(t){x.on(t,"gitter-chat-toggle",function(t){var i=t.detail.state;e.activationElement.forEach(function(t){x.toggleClass(t,"is-collapsed",i)})})})));var i=z((0,x.default)(".js-gitter-toggle-chat-button"),function(e){var i=T(e.target.getAttribute("data-gitter-toggle-chat-state"));t.toggleChat(null!==i?i:"toggle"),e.preventDefault()});this[S].push(i),e.targetElement.forEach(function(e){var i=new l.default("gitter-chat-started",{detail:{chat:t}});e.dispatchEvent(i)});var r=new l.default("gitter-sidecar-instance-started",{detail:{chat:this}});document.dispatchEvent(r)}},{key:U,value:function(){if(!this[k]){var t=this[A],e=w(t);this[C].add(e)}this[k]=!0}},{key:Y,value:function(t){var e=this[A];e.targetElement||console.warn("Gitter Sidecar: No chat embed elements to toggle visibility on");var i=e.targetElement;i.forEach(function(e){"toggle"===t?x.toggleClass(e,"is-collapsed"):x.toggleClass(e,"is-collapsed",!t);var i=new l.default("gitter-chat-toggle",{detail:{state:t}});e.dispatchEvent(i)})}},{key:"toggleChat",value:function(t){var e=this,i=this[A];if(t&&!this[k]){var r=i.targetElement;r.forEach(function(t){t.classList.add("is-loading")}),setTimeout(function(){e[U](),e[Y](t),r.forEach(function(t){t.classList.remove("is-loading")})},300)}else this[U](),this[Y](t)}},{key:"destroy",value:function(){this[S].forEach(function(t){t()}),this[C].destroy()}},{key:"options",get:function(){return(0,j.default)(this[A])}}]),t}();e.default=Q},function(t,e){"use strict";function i(t){if(Array.isArray(t)){for(var e=0,i=Array(t.length);e<t.length;e++)i[e]=t[e];return i}return Array.from(t)}function r(t,e){return M(t).forEach(function(){e&&e.apply(void 0,arguments)}),this}function n(t,e,i){return e.split(/\s/).forEach(function(e){r(t,function(t){t.addEventListener(e,i)})}),this}function o(t,e,i){return e.split(/\s/).forEach(function(e){r(t,function(t){t.removeEventListener(e,i)})}),this}function a(t,e){var i=(e.children||[])[0];i?e.insertBefore(t,i):e.appendChild(t)}function u(t,e,i){return void 0!==i?i?t.classList.add(e):t.classList.remove(e):t.classList.toggle(e),i}Object.defineProperty(e,"__esModule",{value:!0}),e.forEach=r,e.on=n,e.off=o,e.prependElementTo=a,e.toggleClass=u;var M=function(){for(var t=arguments.length,e=Array(t),i=0;i<t;i++)e[i]=arguments[i];return e.reduce(function(t,e){return!e||void 0===e.length||Array.isArray(e)||window&&(!window||e instanceof window.constructor)||(e=Array.prototype.slice.call(e)),t.concat(e)},[])},c=function(){for(var t=arguments.length,e=Array(t),r=0;r<t;r++)e[r]=arguments[r];var n=e;if("string"==typeof e[0]){var o;n=(o=document.querySelectorAll).call.apply(o,[document].concat(e))}return M.apply(void 0,i(n))},s=function(){return c.apply(void 0,arguments)};e.default=s},function(t,e){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var i=0;i<e.length;i++){var r=e[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,i,r){return i&&t(e.prototype,i),r&&t(e,r),e}}(),n=function(t){t&&t.parentElement.removeChild(t)},o=function(){function t(){i(this,t),this.elements=[]}return r(t,[{key:"createElement",value:function(){for(var t=arguments.length,e=Array(t),i=0;i<t;i++)e[i]=arguments[i];var r=document.createElement.apply(document,e);return this.add(r),r}},{key:"add",value:function(){for(var e=arguments.length,i=Array(e),r=0;r<e;r++)i[r]=arguments[r];var n=[].concat(i).reduce(function(e,i){return i?i instanceof t?e.concat(i.elements):e.concat(i):e},[]);this.elements=this.elements.concat(n)}},{key:"destroy",value:function(){this.elements.forEach(function(t){return n(t)}),this.elements=[]}}]),t}();e.default=o},function(t,e){"use strict";function i(t){var e={};return Object.keys(t).forEach(function(i){Object.defineProperty(e,i,{value:t[i],writable:!1,configurable:!1})}),e}Object.defineProperty(e,"__esModule",{value:!0}),e.default=i},function(t,e,i){e=t.exports=i(8)(),e.push([t.id,'.gitter-hidden{box-sizing:border-box;display:none}.gitter-icon{box-sizing:border-box;width:22px;height:22px;fill:currentColor}.gitter-chat-embed{box-sizing:border-box;z-index:100;position:fixed;top:0;left:60%;bottom:0;right:0;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;background-color:#fff;border-left:1px solid #333;box-shadow:-12px 0 18px 0 rgba(50,50,50,.3);transition:transform .3s cubic-bezier(.16,.22,.22,1.7)}@context border-box{.gitter-chat-embed{box-sizing:border-box;background-color:#fff}}.gitter-chat-embed.is-collapsed:not(.is-loading){box-sizing:border-box;transform:translateX(110%)}.gitter-chat-embed:after{box-sizing:border-box;content:"";z-index:-1;position:absolute;top:0;left:100%;bottom:0;right:-100%;background-color:#fff}@context border-box{.gitter-chat-embed:after{box-sizing:border-box;background-color:#fff}}@media(max-width:1150px){.gitter-chat-embed{box-sizing:border-box;left:45%}}@media(max-width:944px){.gitter-chat-embed{box-sizing:border-box;left:30%}}@media(max-width:600px){.gitter-chat-embed{box-sizing:border-box;left:15%}}@media(max-width:500px){.gitter-chat-embed{box-sizing:border-box;left:0;border-left:none}}.gitter-chat-embed>iframe{box-sizing:border-box;-ms-flex:1;flex:1;width:100%;height:100%;border:0}.gitter-chat-embed-loading-wrapper{box-sizing:border-box;position:absolute;top:0;left:0;bottom:0;right:0;display:none;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.is-loading .gitter-chat-embed-loading-wrapper{box-sizing:border-box;display:-ms-flexbox;display:flex}.gitter-chat-embed-loading-indicator{box-sizing:border-box;opacity:.75;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNzkyIDE3OTIiIGZpbGw9IiMzYTMxMzMiPjxwYXRoIGQ9Ik01MjYgMTM5NHEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01MiAwLTkwLTM4dC0zOC05MHEwLTUzIDM3LjUtOTAuNXQ5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptNDk4IDIwNnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41LTkwLjUtMzcuNS0zNy41LTkwLjUgMzcuNS05MC41IDkwLjUtMzcuNSA5MC41IDM3LjUgMzcuNSA5MC41em0tNzA0LTcwNHEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41LTkwLjUtMzcuNS0zNy41LTkwLjUgMzcuNS05MC41IDkwLjUtMzcuNSA5MC41IDM3LjUgMzcuNSA5MC41em0xMjAyIDQ5OHEwIDUyLTM4IDkwdC05MCAzOHEtNTMgMC05MC41LTM3LjV0LTM3LjUtOTAuNSAzNy41LTkwLjUgOTAuNS0zNy41IDkwLjUgMzcuNSAzNy41IDkwLjV6bS05NjQtOTk2cTAgNjYtNDcgMTEzdC0xMTMgNDctMTEzLTQ3LTQ3LTExMyA0Ny0xMTMgMTEzLTQ3IDExMyA0NyA0NyAxMTN6bTExNzAgNDk4cTAgNTMtMzcuNSA5MC41dC05MC41IDM3LjUtOTAuNS0zNy41LTM3LjUtOTAuNSAzNy41LTkwLjUgOTAuNS0zNy41IDkwLjUgMzcuNSAzNy41IDkwLjV6bS02NDAtNzA0cTAgODAtNTYgMTM2dC0xMzYgNTYtMTM2LTU2LTU2LTEzNiA1Ni0xMzYgMTM2LTU2IDEzNiA1NiA1NiAxMzZ6bTUzMCAyMDZxMCA5My02NiAxNTguNXQtMTU4IDY1LjVxLTkzIDAtMTU4LjUtNjUuNXQtNjUuNS0xNTguNXEwLTkyIDY1LjUtMTU4dDE1OC41LTY2cTkyIDAgMTU4IDY2dDY2IDE1OHoiLz48L3N2Zz4=);animation:spin 2s infinite linear}@keyframes spin{0%{box-sizing:border-box;transform:rotate(0deg)}to{box-sizing:border-box;transform:rotate(359.9deg)}}.gitter-chat-embed-action-bar{box-sizing:border-box;position:absolute;top:0;left:0;right:0;display:-ms-flexbox;display:flex;-ms-flex-pack:end;justify-content:flex-end;padding-bottom:.7em;background:linear-gradient(180deg,#fff 0,#fff 50%,hsla(0,0%,100%,0))}.gitter-chat-embed-action-bar-item{box-sizing:border-box;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;width:40px;height:40px;padding-left:0;padding-right:0;opacity:.65;background:none;background-position:50%;background-repeat:no-repeat;background-size:22px 22px;border:0;outline:none;cursor:pointer;cursor:hand;transition:all .2s ease}.gitter-chat-embed-action-bar-item:focus,.gitter-chat-embed-action-bar-item:hover{box-sizing:border-box;opacity:1}.gitter-chat-embed-action-bar-item:active{box-sizing:border-box;filter:hue-rotate(80deg) saturate(150)}.gitter-chat-embed-action-bar-item-pop-out{box-sizing:border-box;margin-right:-4px;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcxLjQyOSIgZmlsbD0iIzNhMzEzMyI+PHBhdGggZD0iTTE1Ny4xNDMsMTAzLjU3MXYzNS43MTRjMCw4Ljg1NC0zLjE0NCwxNi40MjYtOS40MzEsMjIuNzEzcy0xMy44NTgsOS40MzEtMjIuNzEyLDkuNDMxSDMyLjE0MyBjLTguODU0LDAtMTYuNDI1LTMuMTQ0LTIyLjcxMi05LjQzMVMwLDE0OC4xNCwwLDEzOS4yODVWNDYuNDI5YzAtOC44NTQsMy4xNDQtMTYuNDI1LDkuNDMxLTIyLjcxMiBjNi4yODctNi4yODcsMTMuODU4LTkuNDMxLDIyLjcxMi05LjQzMWg3OC41NzJjMS4wNDEsMCwxLjg5NiwwLjMzNSwyLjU2NiwxLjAwNGMwLjY3LDAuNjcsMS4wMDQsMS41MjUsMS4wMDQsMi41NjdWMjUgYzAsMS4wNDItMC4zMzQsMS44OTctMS4wMDQsMi41NjdjLTAuNjcsMC42Ny0xLjUyNSwxLjAwNC0yLjU2NiwxLjAwNEgzMi4xNDNjLTQuOTExLDAtOS4xMTUsMS43NDktMTIuNjEyLDUuMjQ2IHMtNS4yNDYsNy43MDEtNS4yNDYsMTIuNjEydjkyLjg1NmMwLDQuOTExLDEuNzQ5LDkuMTE1LDUuMjQ2LDEyLjYxMnM3LjcwMSw1LjI0NSwxMi42MTIsNS4yNDVIMTI1YzQuOTEsMCw5LjExNS0xLjc0OCwxMi42MTEtNS4yNDUgYzMuNDk3LTMuNDk3LDUuMjQ2LTcuNzAxLDUuMjQ2LTEyLjYxMnYtMzUuNzE0YzAtMS4wNDIsMC4zMzQtMS44OTcsMS4wMDQtMi41NjdjMC42Ny0wLjY2OSwxLjUyNS0xLjAwNCwyLjU2Ny0xLjAwNGg3LjE0MyBjMS4wNDIsMCwxLjg5NywwLjMzNSwyLjU2NywxLjAwNEMxNTYuODA5LDEwMS42NzQsMTU3LjE0MywxMDIuNTI5LDE1Ny4xNDMsMTAzLjU3MXogTTIwMCw3LjE0M3Y1Ny4xNDMgYzAsMS45MzUtMC43MDcsMy42MDktMi4xMjEsNS4wMjJjLTEuNDEzLDEuNDE0LTMuMDg4LDIuMTIxLTUuMDIxLDIuMTIxYy0xLjkzNSwwLTMuNjA5LTAuNzA3LTUuMDIyLTIuMTIxbC0xOS42NDQtMTkuNjQzIGwtNzIuNzY3LDcyLjc2OWMtMC43NDQsMC43NDQtMS42LDEuMTE1LTIuNTY3LDEuMTE1cy0xLjgyMy0wLjM3MS0yLjU2Ny0xLjExNUw3Ny41NjcsMTA5LjcxYy0wLjc0NC0wLjc0NC0xLjExNi0xLjYtMS4xMTYtMi41NjcgYzAtMC45NjcsMC4zNzItMS44MjIsMS4xMTYtMi41NjZsNzIuNzY4LTcyLjc2OGwtMTkuNjQ0LTE5LjY0M2MtMS40MTMtMS40MTQtMi4xMi0zLjA4OC0yLjEyLTUuMDIyYzAtMS45MzUsMC43MDctMy42MDksMi4xMi01LjAyMiBDMTMyLjEwNSwwLjcwNywxMzMuNzc5LDAsMTM1LjcxNSwwaDU3LjE0M2MxLjkzNCwwLDMuNjA4LDAuNzA3LDUuMDIxLDIuMTIxQzE5OS4yOTMsMy41MzQsMjAwLDUuMjA4LDIwMCw3LjE0M3oiLz48L3N2Zz4=)}.gitter-chat-embed-action-bar-item-collapse-chat{box-sizing:border-box;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNzEuNDI5IDE3MS40MjkiIGZpbGw9IiMzYTMxMzMiPjxwYXRoIGQ9Ik0xMjIuNDMzLDEwNi4xMzhsLTE2LjI5NSwxNi4yOTVjLTAuNzQ0LDAuNzQ0LTEuNiwxLjExNi0yLjU2NiwxLjExNmMtMC45NjgsMC0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTUuMjktMTUuMjkgbC0xNS4yOSwxNS4yOWMtMC43NDQsMC43NDQtMS42LDEuMTE2LTIuNTY3LDEuMTE2cy0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTYuMjk0LTE2LjI5NWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY2IGMwLTAuOTY4LDAuMzcyLTEuODIzLDEuMTE2LTIuNTY3bDE1LjI5LTE1LjI5bC0xNS4yOS0xNS4yOWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY3czAuMzcyLTEuODIzLDEuMTE2LTIuNTY3IEw2NS4yOSw0OC45OTZjMC43NDQtMC43NDQsMS42LTEuMTE2LDIuNTY3LTEuMTE2czEuODIzLDAuMzcyLDIuNTY3LDEuMTE2bDE1LjI5LDE1LjI5bDE1LjI5LTE1LjI5IGMwLjc0NC0wLjc0NCwxLjYtMS4xMTYsMi41NjctMS4xMTZjMC45NjcsMCwxLjgyMiwwLjM3MiwyLjU2NiwxLjExNmwxNi4yOTUsMTYuMjk0YzAuNzQ0LDAuNzQ0LDEuMTE2LDEuNiwxLjExNiwyLjU2NyBzLTAuMzcyLDEuODIzLTEuMTE2LDIuNTY3bC0xNS4yOSwxNS4yOWwxNS4yOSwxNS4yOWMwLjc0NCwwLjc0NCwxLjExNiwxLjYsMS4xMTYsMi41NjcgQzEyMy41NDksMTA0LjUzOSwxMjMuMTc3LDEwNS4zOTQsMTIyLjQzMywxMDYuMTM4eiBNMTQ2LjQyOSw4NS43MTRjMC0xMS4wMTItMi43MTctMjEuMTY4LTguMTQ4LTMwLjQ2OSBzLTEyLjc5Ny0xNi42NjctMjIuMDk4LTIyLjA5OFM5Ni43MjYsMjUsODUuNzE0LDI1cy0yMS4xNjgsMi43MTYtMzAuNDY5LDguMTQ3UzM4LjU3OSw0NS45NDUsMzMuMTQ3LDU1LjI0NlMyNSw3NC43MDMsMjUsODUuNzE0IHMyLjcxNiwyMS4xNjgsOC4xNDcsMzAuNDY5czEyLjc5NywxNi42NjYsMjIuMDk4LDIyLjA5OHMxOS40NTcsOC4xNDgsMzAuNDY5LDguMTQ4czIxLjE2OC0yLjcxNywzMC40NjktOC4xNDggczE2LjY2Ni0xMi43OTcsMjIuMDk4LTIyLjA5OFMxNDYuNDI5LDk2LjcyNiwxNDYuNDI5LDg1LjcxNHogTTE3MS40MjksODUuNzE0YzAsMTUuNTUxLTMuODMyLDI5Ljg5My0xMS40OTYsNDMuMDI0IGMtNy42NjQsMTMuMTMzLTE4LjA2MiwyMy41My0zMS4xOTQsMzEuMTk0Yy0xMy4xMzIsNy42NjQtMjcuNDc0LDExLjQ5Ni00My4wMjQsMTEuNDk2cy0yOS44OTItMy44MzItNDMuMDI0LTExLjQ5NiBjLTEzLjEzMy03LjY2NC0yMy41MzEtMTguMDYyLTMxLjE5NC0zMS4xOTRDMy44MzIsMTE1LjYwNywwLDEwMS4yNjUsMCw4NS43MTRTMy44MzIsNTUuODIyLDExLjQ5Niw0Mi42OSBjNy42NjQtMTMuMTMzLDE4LjA2Mi0yMy41MzEsMzEuMTk0LTMxLjE5NEM1NS44MjIsMy44MzIsNzAuMTY0LDAsODUuNzE0LDBzMjkuODkzLDMuODMyLDQzLjAyNCwxMS40OTYgYzEzLjEzMyw3LjY2NCwyMy41MywxOC4wNjIsMzEuMTk0LDMxLjE5NEMxNjcuNTk3LDU1LjgyMiwxNzEuNDI5LDcwLjE2NCwxNzEuNDI5LDg1LjcxNHoiLz48L3N2Zz4=)}.gitter-open-chat-button{z-index:100;position:fixed;bottom:0;right:10px;padding:1em 3em;background-color:#36bc98;border:0;border-top-left-radius:.5em;border-top-right-radius:.5em;font-family:sans-serif;font-size:12px;letter-spacing:1px;text-transform:uppercase;text-align:center;text-decoration:none;cursor:pointer;cursor:hand;transition:all .3s ease}.gitter-open-chat-button,.gitter-open-chat-button:visited{box-sizing:border-box;color:#fff}.gitter-open-chat-button:focus,.gitter-open-chat-button:hover{box-sizing:border-box;background-color:#3ea07f;color:#fff}.gitter-open-chat-button:focus{box-sizing:border-box;box-shadow:0 0 8px rgba(62,160,127,.6);outline:none}.gitter-open-chat-button:active{box-sizing:border-box;color:#eee}.gitter-open-chat-button.is-collapsed{box-sizing:border-box;transform:translateY(120%)}',""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;e<this.length;e++){var i=this[e];i[2]?t.push("@media "+i[2]+"{"+i[1]+"}"):t.push(i[1])}return t.join("")},t.i=function(e,i){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},n=0;n<this.length;n++){var o=this[n][0];"number"==typeof o&&(r[o]=!0)}for(n=0;n<e.length;n++){var a=e[n];"number"==typeof a[0]&&r[a[0]]||(i&&!a[2]?a[2]=i:i&&(a[2]="("+a[2]+") and ("+i+")"),t.push(a))}},t}}]);
+//# sourceMappingURL=sidecar.js.map
diff --git a/httpx/__init__.py b/httpx/__init__.py
index 55f8526..d02c72e 100644
--- a/httpx/__init__.py
+++ b/httpx/__init__.py
@@ -12,12 +12,12 @@ from ._exceptions import (
     DecodingError,
     HTTPError,
     InvalidURL,
+    NetworkError,
     NotRedirectResponse,
     PoolTimeout,
     ProtocolError,
     ProxyError,
     ReadTimeout,
-    RedirectLoop,
     RequestBodyUnavailable,
     RequestNotRead,
     ResponseClosed,
@@ -60,11 +60,11 @@ __all__ = [
     "DecodingError",
     "HTTPError",
     "InvalidURL",
+    "NetworkError",
     "NotRedirectResponse",
     "PoolTimeout",
     "ProtocolError",
     "ReadTimeout",
-    "RedirectLoop",
     "RequestBodyUnavailable",
     "ResponseClosed",
     "ResponseNotRead",
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 6c87858..e9d33a9 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,
@@ -27,13 +26,7 @@ from ._dispatch.connection_pool import ConnectionPool
 from ._dispatch.proxy_http import HTTPProxy
 from ._dispatch.urllib3 import URLLib3Dispatcher
 from ._dispatch.wsgi import WSGIDispatch
-from ._exceptions import (
-    HTTPError,
-    InvalidURL,
-    RedirectLoop,
-    RequestBodyUnavailable,
-    TooManyRedirects,
-)
+from ._exceptions import HTTPError, InvalidURL, RequestBodyUnavailable, TooManyRedirects
 from ._models import (
     URL,
     Cookies,
@@ -615,9 +608,6 @@ class Client(BaseClient):
         while True:
             if len(history) > self.max_redirects:
                 raise TooManyRedirects()
-            urls = ((resp.request.method, resp.url) for resp in history)
-            if (request.method, request.url) in urls:
-                raise RedirectLoop()
 
             response = self.send_handling_auth(
                 request, auth=auth, timeout=timeout, history=history
@@ -871,6 +861,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
@@ -929,9 +921,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.
@@ -954,7 +943,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,
     ):
@@ -978,7 +966,6 @@ class AsyncClient(BaseClient):
             pool_limits=pool_limits,
             dispatch=dispatch,
             app=app,
-            backend=backend,
             trust_env=trust_env,
             uds=uds,
         )
@@ -989,7 +976,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()
@@ -1003,7 +989,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:
@@ -1018,7 +1003,6 @@ class AsyncClient(BaseClient):
             cert=cert,
             http2=http2,
             pool_limits=pool_limits,
-            backend=backend,
             trust_env=trust_env,
             uds=uds,
         )
@@ -1030,7 +1014,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(
@@ -1041,7 +1024,6 @@ class AsyncClient(BaseClient):
             cert=cert,
             http2=http2,
             pool_limits=pool_limits,
-            backend=backend,
             trust_env=trust_env,
         )
 
@@ -1142,9 +1124,6 @@ class AsyncClient(BaseClient):
         while True:
             if len(history) > self.max_redirects:
                 raise TooManyRedirects()
-            urls = ((resp.request.method, resp.url) for resp in history)
-            if (request.method, request.url) in urls:
-                raise RedirectLoop()
 
             response = await self.send_handling_auth(
                 request, auth=auth, timeout=timeout, history=history
@@ -1400,6 +1379,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..1d35167 100644
--- a/httpx/_content_streams.py
+++ b/httpx/_content_streams.py
@@ -291,7 +291,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:
diff --git a/httpx/_exceptions.py b/httpx/_exceptions.py
index 562c2ed..0f0ea06 100644
--- a/httpx/_exceptions.py
+++ b/httpx/_exceptions.py
@@ -101,12 +101,6 @@ class TooManyRedirects(RedirectError):
     """
 
 
-class RedirectLoop(RedirectError):
-    """
-    Infinite redirect loop.
-    """
-
-
 class NotRedirectResponse(RedirectError):
     """
     Response was not a redirect response.
diff --git a/httpx/_models.py b/httpx/_models.py
index 861e85f..2bfa367 100644
--- a/httpx/_models.py
+++ b/httpx/_models.py
@@ -515,8 +515,8 @@ class Headers(typing.MutableMapping[str, str]):
         Set the header `key` to `value`, removing any duplicate entries.
         Retains insertion order.
         """
-        set_key = key.lower().encode(self.encoding)
-        set_value = value.encode(self.encoding)
+        set_key = key.lower().encode(self._encoding or "utf-8")
+        set_value = value.encode(self._encoding or "utf-8")
 
         found_indexes = []
         for idx, (item_key, _) in enumerate(self._list):
diff --git a/mkdocs.yml b/mkdocs.yml
index 3a484c2..852bb82 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -27,3 +27,7 @@ markdown_extensions:
 
 extra_css:
     - css/custom.css
+
+extra_javascript:
+  - 'js/chat.js'
+  - 'js/sidecar-1.5.0.js'


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
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_redirects.py b/tests/client/test_redirects.py
index ad138e5..190ff09 100644
--- a/tests/client/test_redirects.py
+++ b/tests/client/test_redirects.py
@@ -7,7 +7,6 @@ from httpx import (
     URL,
     AsyncClient,
     NotRedirectResponse,
-    RedirectLoop,
     Request,
     RequestBodyUnavailable,
     Response,
@@ -245,20 +244,10 @@ async def test_too_many_redirects_calling_next():
 @pytest.mark.usefixtures("async_environment")
 async def test_redirect_loop():
     client = AsyncClient(dispatch=MockDispatch())
-    with pytest.raises(RedirectLoop):
+    with pytest.raises(TooManyRedirects):
         await client.get("https://example.org/redirect_loop")
 
 
-@pytest.mark.usefixtures("async_environment")
-async def test_redirect_loop_calling_next():
-    client = AsyncClient(dispatch=MockDispatch())
-    url = "https://example.org/redirect_loop"
-    response = await client.get(url, allow_redirects=False)
-    with pytest.raises(RedirectLoop):
-        while response.is_redirect:
-            response = await response.anext()
-
-
 @pytest.mark.usefixtures("async_environment")
 async def test_cross_domain_redirect():
     client = AsyncClient(dispatch=MockDispatch())
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_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"