]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Drop sync (#544)
authorTom Christie <tom@tomchristie.com>
Wed, 27 Nov 2019 10:43:42 +0000 (10:43 +0000)
committerGitHub <noreply@github.com>
Wed, 27 Nov 2019 10:43:42 +0000 (10:43 +0000)
Drop sync client

52 files changed:
README.md
docs/advanced.md
docs/api.md
docs/async.md [deleted file]
docs/compatibility.md
docs/index.md
docs/parallel.md [deleted file]
docs/quickstart.md
httpx/__init__.py
httpx/api.py
httpx/client.py
httpx/dispatch/__init__.py
httpx/dispatch/asgi.py
httpx/dispatch/base.py
httpx/dispatch/connection.py
httpx/dispatch/connection_pool.py
httpx/dispatch/http11.py
httpx/dispatch/http2.py
httpx/dispatch/proxy_http.py
httpx/dispatch/threaded.py [deleted file]
httpx/dispatch/wsgi.py [deleted file]
httpx/exceptions.py
httpx/middleware/base.py
httpx/middleware/basic_auth.py
httpx/middleware/custom_auth.py
httpx/middleware/digest_auth.py
httpx/middleware/redirect.py
httpx/models.py
mkdocs.yml
tests/client/test_async_client.py
tests/client/test_auth.py
tests/client/test_client.py
tests/client/test_cookies.py
tests/client/test_headers.py
tests/client/test_proxies.py
tests/client/test_queryparams.py
tests/client/test_redirects.py
tests/conftest.py
tests/dispatch/test_http2.py
tests/dispatch/test_threaded.py [deleted file]
tests/models/test_requests.py
tests/models/test_responses.py
tests/requests_compat/__init__.py [deleted file]
tests/requests_compat/conftest.py [deleted file]
tests/requests_compat/test_api.py [deleted file]
tests/test_api.py
tests/test_asgi.py
tests/test_decoders.py
tests/test_multipart.py
tests/test_timeouts.py
tests/test_utils.py
tests/test_wsgi.py [deleted file]

index 1c19c2f83be3b1590222fd387b244ed130b1030b..66393ff909f04090034ebe17292b9689ee37b2d8 100644 (file)
--- a/README.md
+++ b/README.md
 </a>
 </p>
 
-**Note**: *This project should be considered as an "alpha" release. It is substantially API complete, but there are still some areas that need more work.*
+HTTPX is an asynchronous HTTP client, that supports HTTP/2 and HTTP/1.1.
+
+It can be used in high-performance async web frameworks, using either asyncio
+or trio, and is able to support making large numbers of requests concurrently.
+
+**Note**: *The 0.8 release switched HTTPX into focusing exclusively on the async
+client. It is possible that we'll look at re-introducing a sync API at a
+later date.*
 
 ---
 
 Let's get started...
 
+*The standard Python REPL does not allow top-level async statements.*
+
+*To run async examples directly you'll probably want to either use `ipython`,
+or use Python 3.8 with `python -m asyncio`.*
+
 ```python
 >>> import httpx
->>> r = httpx.get('https://www.example.org/')
+>>> r = await httpx.get('https://www.example.org/')
 >>> r
 <Response [200 OK]>
 >>> r.status_code
@@ -41,11 +53,9 @@ Let's get started...
 
 HTTPX builds on the well-established usability of `requests`, and gives you:
 
-* A requests-compatible API.
+* A requests-compatible API wherever possible.
 * HTTP/2 and HTTP/1.1 support.
-* Support for [issuing HTTP requests in parallel](https://www.encode.io/httpx/parallel/). *(Coming soon)*
-* Standard synchronous interface, but [with `async`/`await` support if you need it](https://www.encode.io/httpx/async/).
-* Ability to [make requests directly to WSGI or ASGI applications](https://www.encode.io/httpx/advanced/#calling-into-python-web-apps).
+* Ability to [make requests directly to ASGI applications](https://www.encode.io/httpx/advanced/#calling-into-python-web-apps).
 * Strict timeouts everywhere.
 * Fully type annotated.
 * 100% test coverage.
@@ -62,7 +72,7 @@ Plus all the standard features of `requests`...
 * Automatic Content Decoding
 * Unicode Response Bodies
 * Multipart File Uploads
-* HTTP(S) Proxy Support *(TODO)*
+* HTTP(S) Proxy Support
 * Connection Timeouts
 * Streaming Downloads
 * .netrc Support
@@ -84,9 +94,7 @@ Project documentation is available at [www.encode.io/httpx/](https://www.encode.
 
 For a run-through of all the basics, head over to the [QuickStart](https://www.encode.io/httpx/quickstart/).
 
-For more advanced topics, see the [Advanced Usage](https://www.encode.io/httpx/advanced/) section, or
-the specific topics on making [Parallel Requests](https://www.encode.io/httpx/parallel/) or using the
-[Async Client](https://www.encode.io/httpx/async/).
+For more advanced topics, see the [Advanced Usage](https://www.encode.io/httpx/advanced/) section.
 
 The [Developer Interface](https://www.encode.io/httpx/api/) provides a comprehensive API reference.
 
index 1365645d4d7c9c5e83b047a171a09d9c89a684e1..5b891841e2ec10436079f3f98ba4cb0ac0aa444c 100644 (file)
@@ -14,9 +14,9 @@ all outgoing requests.
 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')
-... 
+>>> async with httpx.Client() as client:
+...     r = await client.get('https://example.com')
+...
 >>> r
 <Response [200 OK]>
 ```
@@ -26,10 +26,10 @@ Alternatively, you can explicitly close the connection pool without block-usage
 ```python
 >>> client = httpx.Client()
 >>> try:
-...     r = client.get('https://example.com')
+...     r = await client.get('https://example.com')
 ... finally:
-...     client.close()
-... 
+...     await client.close()
+...
 >>> r
 <Response [200 OK]>
 ```
@@ -45,8 +45,8 @@ For example, to apply a set of custom headers on every request:
 ```python
 >>> url = 'http://httpbin.org/headers'
 >>> headers = {'user-agent': 'my-app/0.0.1'}
->>> with httpx.Client(headers=headers) as client:
-...     r = client.get(url)
+>>> async with httpx.Client(headers=headers) as client:
+...     r = await client.get(url)
 ...
 >>> r.json()['headers']['User-Agent']
 'my-app/0.0.1'
@@ -54,7 +54,7 @@ For example, to apply a set of custom headers on every request:
 
 !!! note
     When you provide a parameter at both the client and request levels, one of two things can happen:
-    
+
     - For headers, query parameters and cookies, the values are merged into one.
     - For all other parameters, the request-level value is used.
 
@@ -63,9 +63,9 @@ Additionally, `Client` constructor accepts some parameters that aren't available
 One particularly useful parameter is `base_url`, which allows you to define a base URL to prepend to all outgoing requests:
 
 ```python
->>> with httpx.Client(base_url='http://httpbin.org') as client:
-...     r = client.get('/headers')
-... 
+>>> async with httpx.Client(base_url='http://httpbin.org') as client:
+...     r = await client.get('/headers')
+...
 >>> r.request.url
 URL('http://httpbin.org/headers')
 ```
@@ -75,45 +75,51 @@ For a list of all available client-level parameters, see the [`Client` API refer
 ## Calling into Python Web Apps
 
 You can configure an `httpx` client to call directly into a Python web
-application using either the WSGI or ASGI protocol.
+application using either the ASGI protocol.
 
 This is particularly useful for two main use-cases:
 
 * Using `httpx` as a client inside test cases.
 * Mocking out external services during tests or in dev/staging environments.
 
-Here's an example of integrating against a Flask application:
+Let's take this Starlette application as an example:
 
 ```python
-from flask import Flask
-import httpx
+from starlette.applications import Starlette
+from starlette.responses import HTMLResponse
+from starlette.routing import Route
+
 
+async def hello():
+    return HTMLResponse("Hello World!")
 
-app = Flask(__name__)
 
-@app.route("/")
-def hello():
-    return "Hello World!"
+app = Starlette(routes=[Route("/", hello)])
+```
+
+We can make requests directly against the application, like so:
 
-with httpx.Client(app=app) as client:
-    r = client.get('http://example/')
-    assert r.status_code == 200
-    assert r.text == "Hello World!"
+```python
+>>> import httpx
+>>> async with httpx.Client(app=app) as client:
+...     r = client.get('http://example/')
+...     assert r.status_code == 200
+...     assert r.text == "Hello World!"
 ```
 
 For some more complex cases you might need to customize the WSGI or ASGI
 dispatch. This allows you to:
 
 * Inspect 500 error responses rather than raise exceptions by setting `raise_app_exceptions=False`.
-* Mount the WSGI or ASGI application at a subpath by setting `script_name` (WSGI) or `root_path` (ASGI).
-* Use a given client address for requests by setting `remote_addr` (WSGI) or `client` (ASGI).
+* Mount the WSGI or ASGI application at a subpath by setting `root_path`.
+* Use a given client address for requests by setting `client`.
 
 For example:
 
 ```python
 # Instantiate a client that makes WSGI requests with a client IP of "1.2.3.4".
-dispatch = httpx.dispatch.WSGIDispatch(app=app, remote_addr="1.2.3.4")
-with httpx.Client(dispatch=dispatch) as client:
+dispatch = httpx.dispatch.ASGIDispatch(app=app, remote_addr="1.2.3.4")
+async with httpx.Client(dispatch=dispatch) as client:
     ...
 ```
 
@@ -123,10 +129,10 @@ You can use `Client.build_request()` to build a request and
 make modifications before sending the request.
 
 ```python
->>> with httpx.Client() as client:
+>>> async 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 = await client.send(req)
 ...
 >>> r
 <Response [200 OK]>
@@ -139,11 +145,11 @@ One can set the version of the HTTP protocol for the client in case you want to
 For example:
 
 ```python
-with httpx.Client(http_versions=["HTTP/1.1"]) as h11_client:
-    h11_response = h11_client.get("https://myserver.com")
+async with httpx.Client(http_versions=["HTTP/1.1"]) as h11_client:
+    h11_response = await h11_client.get("https://myserver.com")
 
-with httpx.Client(http_versions=["HTTP/2"]) as h2_client:
-    h2_response = h2_client.get("https://myserver.com")
+async with httpx.Client(http_versions=["HTTP/2"]) as h2_client:
+    h2_response = await h2_client.get("https://myserver.com")
 ```
 
 ## .netrc Support
@@ -158,7 +164,7 @@ not defined, HTTPX tries to add auth into request's header from .netrc file.
 
 As default `trust_env` is true. To set false:
 ```python
->>> httpx.get('https://example.org/', trust_env=False)
+>>> await httpx.get('https://example.org/', trust_env=False)
 ```
 
 If `NETRC` environment is empty, HTTPX tries to use default files.
@@ -189,9 +195,9 @@ Here's an example requesting the Docker Engine API:
 import httpx
 
 
-with httpx.Client(uds="/var/run/docker.sock") as client:
+async with httpx.Client(uds="/var/run/docker.sock") as client:
     # This request will connect through the socket file.
-    resp = client.get("http://localhost/version")
+    resp = await client.get("http://localhost/version")
 ```
 
 ## HTTP Proxying
@@ -205,7 +211,7 @@ to `http://127.0.0.1:3081` your `proxies` config would look like this:
 ...     "http": "http://127.0.0.1:3080",
 ...     "https": "http://127.0.0.1:3081"
 ... }
->>> with httpx.Client(proxies=proxies) as client:
+>>> async with httpx.Client(proxies=proxies) as client:
 ...     ...
 ```
 
@@ -220,11 +226,11 @@ to use for a given request this same order is used.
 ...     "http": "...",  # Scheme
 ...     "all": "...",  # All
 ... }
->>> with httpx.Client(proxies=proxies) as client:
+>>> async with httpx.Client(proxies=proxies) as client:
 ...     ...
-... 
+...
 >>> proxy = "..."  # Shortcut for {'all': '...'}
->>> with httpx.Client(proxies=proxy) as client:
+>>> async with httpx.Client(proxies=proxy) as client:
 ...     ...
 ```
 
@@ -244,9 +250,9 @@ proxy = httpx.HTTPProxy(
     proxy_url="https://127.0.0.1",
     proxy_mode=httpx.HTTPProxyMode.TUNNEL_ONLY
 )
-with httpx.Client(proxies=proxy) as client:
+async with httpx.Client(proxies=proxy) as client:
     # This request will be tunneled instead of forwarded.
-    r = client.get("http://example.com")
+    r = await client.get("http://example.com")
 ```
 
 !!! note
@@ -278,18 +284,18 @@ You can set timeouts on two levels:
 
 ```python
 # Using top-level API
-httpx.get('http://example.com/api/v1/example', timeout=5)
+await httpx.get('http://example.com/api/v1/example', timeout=5)
 
 # Or, with a client:
-with httpx.Client() as client:
-    client.get("http://example.com/api/v1/example", timeout=5)
+async with httpx.Client() as client:
+    await client.get("http://example.com/api/v1/example", timeout=5)
 ```
 
 - On a client instance, which results in the given `timeout` being used as a default for requests made with this client:
 
 ```python
-with httpx.Client(timeout=5) as client:
-    client.get('http://example.com/api/v1/example')
+async with httpx.Client(timeout=5) as client:
+    await client.get('http://example.com/api/v1/example')
 ```
 
 Besides, you can pass timeouts in two forms:
@@ -304,7 +310,7 @@ timeout = httpx.TimeoutConfig(
     write_timeout=15
 )
 
-resp = httpx.get('http://example.com/api/v1/example', timeout=timeout)
+resp = await httpx.get('http://example.com/api/v1/example', timeout=timeout)
 ```
 
 ### Default timeouts
@@ -319,11 +325,11 @@ Note that currently this is not supported by the top-level API.
 ```python
 url = "http://example.com/api/v1/delay/10"
 
-httpx.get(url, timeout=None)  # Times out after 5s
+await httpx.get(url, timeout=None)  # Times out after 5s
 
 
-with httpx.Client(timeout=None) as client:
-    client.get(url)  # Does not timeout, returns after 10s
+async with httpx.Client(timeout=None) as client:
+    await client.get(url)  # Does not timeout, returns after 10s
 
 
 timeout = httpx.TimeoutConfig(
@@ -331,7 +337,7 @@ timeout = httpx.TimeoutConfig(
     read_timeout=None,
     write_timeout=5
 )
-httpx.get(url, timeout=timeout) # Does not timeout, returns after 10s
+await httpx.get(url, timeout=timeout) # Does not timeout, returns after 10s
 ```
 
 ## Multipart file encoding
@@ -342,7 +348,7 @@ name of the payloads as keys and a tuple of elements as values.
 
 ```python
 >>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
->>> r = httpx.post("https://httpbin.org/post", files=files)
+>>> r = await httpx.post("https://httpbin.org/post", files=files)
 >>> print(r.text)
 {
   ...
@@ -366,7 +372,7 @@ is set to `None` or it cannot be inferred from it, HTTPX will default to
 
 ```python
 >>> files = {'upload-file': (None, 'text content', 'text/plain')}
->>> r = httpx.post("https://httpbin.org/post", files=files)
+>>> r = await httpx.post("https://httpbin.org/post", files=files)
 >>> print(r.text)
 {
   ...
@@ -393,7 +399,7 @@ If you'd like to use a custom CA bundle, you can use the `verify` parameter that
 ```python
 import httpx
 
-r = httpx.get("https://example.org", verify="path/to/client.pem")
+r = await httpx.get("https://example.org", verify="path/to/client.pem")
 ```
 
 ### Making HTTPS requests to a local server
@@ -408,7 +414,32 @@ If you do need to make HTTPS connections to a local server, for example to test
 
 ```python
 >>> import httpx
->>> r = httpx.get("https://localhost:8000", verify="/tmp/client.pem")
+>>> r = await httpx.get("https://localhost:8000", verify="/tmp/client.pem")
 >>> r
 Response <200 OK>
 ```
+
+## Support async environments
+
+### [asyncio](https://docs.python.org/3/library/asyncio.html) (Default)
+
+By default, `AsyncClient` uses `asyncio` to perform asynchronous operations and I/O calls.
+
+### [trio](https://github.com/python-trio/trio)
+
+To make asynchronous requests in `trio` programs, pass a `TrioBackend` to the `AsyncClient`:
+
+```python
+import trio
+import httpx
+from httpx.concurrency.trio import TrioBackend
+
+async def main():
+    async with httpx.AsyncClient(backend=TrioBackend()) as client:
+        ...
+
+trio.run(main)
+```
+
+!!! important
+    `trio` must be installed to import and use the `TrioBackend`.
index 12b790af96fef2bfd7948305dfad4ad98cb3c437..b4e567086740c4b4a6c5cc045d2caad2513a6f0a 100644 (file)
@@ -36,7 +36,7 @@
 
 ::: httpx.Client
     :docstring:
-    :members: headers cookies params request get head options post put patch delete build_request send close 
+    :members: headers cookies params request get head options post put patch delete build_request send close
 
 ## `Response`
 
@@ -75,7 +75,7 @@ what gets sent over the wire.*
 
 ```python
 >>> request = httpx.Request("GET", "https://example.org", headers={'host': 'example.org'})
->>> response = client.send(request)
+>>> response = await client.send(request)
 ```
 
 * `def __init__(method, url, [params], [data], [json], [headers], [cookies])`
diff --git a/docs/async.md b/docs/async.md
deleted file mode 100644 (file)
index c631d54..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-# Async Client
-
-HTTPX offers a standard synchronous API by default, but also gives you
-the option of an async client if you need it.
-
-Async is a concurrency model that is far more efficient than multi-threading,
-and can provide significant performance benefits and enable the use of
-long-lived network connections such as WebSockets.
-
-If you're working with an async web framework then you'll also want to use an
-async client for sending outgoing HTTP requests.
-
-## Making Async requests
-
-To make asynchronous requests, you'll need an `AsyncClient`.
-
-```python
->>> async with httpx.AsyncClient() as client:
->>>     r = await client.get('https://www.example.com/')
->>> r
-<Response [200 OK]>
-```
-
-!!! tip
-    Use [IPython](https://ipython.readthedocs.io/en/stable/) to try this code interactively, as it supports executing `async`/`await` expressions in the console.
-
-!!! note
-    The `async with` syntax ensures that all active connections are closed on exit.
-
-    It is safe to access response content (e.g. `r.text`) both inside and outside the `async with` block, unless you are using response streaming. In that case, you should `.read()`, `.stream()`, or `.close()` the response *inside* the `async with` block.
-
-## API Differences
-
-If you're using streaming responses then there are a few bits of API that
-use async methods:
-
-```python
->>> async with httpx.AsyncClient() as client:
->>>     r = await client.get('https://www.example.com/', stream=True)
->>>     async for chunk in r.stream():
->>>         ...
-```
-
-The async response methods are:
-
-* `.read()`
-* `.stream()`
-* `.raw()`
-* `.close()`
-
-If you're making [parallel requests](parallel.md), then you'll also need to use an async API:
-
-```python
->>> async with httpx.AsyncClient() as client:
->>>     async with client.parallel() as parallel:
->>>         pending_one = parallel.get('https://example.com/1')
->>>         pending_two = parallel.get('https://example.com/2')
->>>         response_one = await pending_one.get_response()
->>>         response_two = await pending_two.get_response()
-```
-
-The async parallel methods are:
-
-* `.parallel()` *Used as an "async with" context manager.*
-* `.get_response()`
-* `.next_response()`
-
-## Supported async libraries
-
-You can use `AsyncClient` with any of the following async libraries.
-
-!!! tip
-    You will typically be using `AsyncClient` in async programs that run on `asyncio`. If that's the case, or if you're not sure what this is all about, you can safely ignore this section.
-
-### [asyncio](https://docs.python.org/3/library/asyncio.html) (Default)
-
-By default, `AsyncClient` uses `asyncio` to perform asynchronous operations and I/O calls.
-
-```python
-import asyncio
-import httpx
-
-async def main():
-    async with httpx.AsyncClient() as client:
-        ...
-
-asyncio.run(main())
-```
-
-### [trio](https://github.com/python-trio/trio)
-
-To make asynchronous requests in `trio` programs, pass a `TrioBackend` to the `AsyncClient`:
-
-```python
-import trio
-import httpx
-from httpx.concurrency.trio import TrioBackend
-
-async def main():
-    async with httpx.AsyncClient(backend=TrioBackend()) as client:
-        ...
-
-trio.run(main)
-```
-
-!!! important
-    `trio` must be installed to import and use the `TrioBackend`.
-
-## FAQ
-
-### When should I use an `AsyncClient`?
-
-You should use an `AsyncClient` whenever you are inside an *async environment*.
-
-In particular, using `httpx.get()` or `httpx.Client()` in an async environment is **not** supported. There are several technical reasons to this, but the rationale is that you shouldn't be doing blocking-style network calls in the context of an event loop (for more discussion, see [#179](https://github.com/encode/httpx/issues/179)).
-
-For example, this won't work:
-
-```python
-import asyncio
-import httpx
-
-async def main():
-    r = httpx.get("https://example.org")
-
-asyncio.run(main())
-```
-
-Instead, you should use an `AsyncClient`:
-
-```python
-import asyncio
-import httpx
-
-async def main():
-    async with httpx.AsyncClient() as client:
-        r = await client.get("https://example.org")
-
-asyncio.run(main())
-```
-
-If you *need* to make synchronous requests, or otherwise run into issues related to sync usage, you should probably consider using a regular threaded client such as [Requests](https://github.com/psf/requests) instead.
index 7b2b3911922a1db366ff69338dd8f58961e30175..a95c12cdb573257f08b1f04f21d42f409e6d3d45 100644 (file)
@@ -18,17 +18,17 @@ but also provide lower-cased versions for API compatibility with `requests`.
 ## Advanced Usage
 
 ### requests.Session
+
 The HTTPX equivalent of `requests.Session` is `httpx.Client`.
 
 ```python
 session = requests.Session(**kwargs)
 ```
 
-is equivalent to
+is generally equivalent to
 
 ```python
-with httpx.Client(**kwargs) as client:
-    ...
+client = httpx.Client(**kwargs)
 ```
 
 More detailed documentation and usage of `Client` can be found in [Advanced Usage](advanced.md).
index b09394b1396eb13aab0ae54fa2138395a3c0a341..b0f98a0ab29568ca770e0864d4c779b9ff560f0d 100644 (file)
@@ -24,17 +24,29 @@ HTTPX
 <em>A next-generation HTTP client for Python.</em>
 </div>
 
-!!! warning
-    This project should be considered as an "alpha" release. It is substantially
-    API complete, but there are still some areas that need more work.
+HTTPX is an asynchronous HTTP client, that supports HTTP/2 and HTTP/1.1.
+
+It can be used in high-performance async web frameworks, using either asyncio
+or trio, and is able to support making large numbers of requests concurrently.
+
+!!! note
+    The 0.8 release switched HTTPX into focusing exclusively on the async
+    client. It is possible that we'll look at re-introducing a sync API at a
+    later date.
 
 ---
 
 Let's get started...
 
+!!! note
+    The standard Python REPL does not allow top-level async statements.
+
+    To run async examples directly you'll probably want to either use `ipython`,
+    or use Python 3.8 with `python -m asyncio`.
+
 ```python
 >>> import httpx
->>> r = httpx.get('https://www.example.org/')
+>>> r = await httpx.get('https://www.example.org/')
 >>> r
 <Response [200 OK]>
 >>> r.status_code
@@ -49,13 +61,12 @@ Let's get started...
 
 ## Features
 
-HTTPX builds on the well-established usability of `requests`, and gives you:
+HTTPX is a high performance asynchronous HTTP client, that builds on the
+well-established usability of `requests`, and gives you:
 
-* A requests-compatible API.
+* A broadly requests-compatible API.
 * HTTP/2 and HTTP/1.1 support.
-* Support for [issuing HTTP requests in parallel](parallel.md). *(Coming soon)*
-* Standard synchronous interface, but [with `async`/`await` support if you need it](async.md).
-* Ability to [make requests directly to WSGI or ASGI applications](advanced.md#calling-into-python-web-apps).
+* Ability to [make requests directly to ASGI applications](advanced.md#calling-into-python-web-apps).
 * Strict timeouts everywhere.
 * Fully type annotated.
 * 100% test coverage.
@@ -72,7 +83,7 @@ Plus all the standard features of `requests`...
 * Automatic Content Decoding
 * Unicode Response Bodies
 * Multipart File Uploads
-* HTTP(S) Proxy Support *(TODO)*
+* HTTP(S) Proxy Support
 * Connection Timeouts
 * Streaming Downloads
 * .netrc Support
@@ -82,9 +93,7 @@ Plus all the standard features of `requests`...
 
 For a run-through of all the basics, head over to the [QuickStart](quickstart.md).
 
-For more advanced topics, see the [Advanced Usage](advanced.md) section, or
-the specific topics on making [Parallel Requests](parallel.md) or using the
-[Async Client](async.md).
+For more advanced topics, see the [Advanced Usage](advanced.md) section.
 
 The [Developer Interface](api.md) provides a comprehensive API reference.
 
diff --git a/docs/parallel.md b/docs/parallel.md
deleted file mode 100644 (file)
index 511ac24..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-# Parallel Requests
-
-!!! warning
-    This page documents some proposed functionality that is not yet released.
-    See [pull request #52](https://github.com/encode/httpx/pull/52) for the
-    first-pass of an implementation.
-
-HTTPX allows you to make HTTP requests in parallel in a highly efficient way,
-using async under the hood, while still presenting a standard threaded interface.
-
-This has the huge benefit of allowing you to efficiently make parallel HTTP
-requests without having to switch out to using async all the way through.
-
-## Making Parallel Requests
-
-Let's make two outgoing HTTP requests in parallel:
-
-```python
->>> with httpx.parallel() as parallel:
->>>     pending_one = parallel.get('https://example.com/1')
->>>     pending_two = parallel.get('https://example.com/2')
->>>     response_one = pending_one.get_response()
->>>     response_two = pending_two.get_response()
-```
-
-If we're making lots of outgoing requests, we might not want to deal with the
-responses sequentially, but rather deal with each response that comes back
-as soon as it's available:
-
-```python
->>> with httpx.parallel() as parallel:
->>>     for counter in range(1, 10):
->>>         parallel.get(f'https://example.com/{counter}')
->>>     while parallel.has_pending_responses:
->>>         r = parallel.next_response()
-```
-
-## Exceptions and Cancellations
-
-The style of using `parallel` blocks ensures that you'll always have a well-defined exception and cancellation behaviors. Request exceptions are only ever
-raised when calling either `get_response` or `next_response` and any pending
-requests are canceled on exiting the block.
-
-## Parallel requests with a Client
-
-You can also call `parallel()` from a client instance, which allows you to
-control the authentication or dispatch behavior for all requests within the
-block.
-
-```python
->>> with httpx.Client() as client:
-...     with client.parallel() as parallel:
-...         ...
-```
-
-## Async parallel requests
-
-If you're working within an async framework, then you'll want to use a fully
-async API for making requests.
-
-```python
->>> async with httpx.AsyncClient() as client:
-...     async with client.parallel() as parallel:
-...         pending_one = await parallel.get('https://example.com/1')
-...         pending_two = await parallel.get('https://example.com/2')
-...         response_one = await pending_one.get_response()
-...         response_two = await pending_two.get_response()
-```
-
-See [the Async Client documentation](async.md) for more details.
index 9e72cdcb43c9a8854989d98f78deb5929d48b3d1..f1b9be83918a63af43c9d01c500bf48bc9742c8a 100644 (file)
@@ -1,9 +1,10 @@
 # QuickStart
 
 !!! note
-    This page closely follows the layout of the `requests` QuickStart documentation.
-    The `httpx` library is designed to be API compatible with `requests` wherever
-    possible.
+    The standard Python REPL does not allow top-level async statements.
+
+    To run async examples directly you'll probably want to either use `ipython`,
+    or use Python 3.8 with `python -m asyncio`.
 
 First, start by importing HTTPX:
 
@@ -14,7 +15,7 @@ First, start by importing HTTPX:
 Now, let’s try to get a webpage.
 
 ```python
->>> r = httpx.get('https://httpbin.org/get')
+>>> r = await httpx.get('https://httpbin.org/get')
 >>> r
 <Response [200 OK]>
 ```
@@ -22,16 +23,16 @@ Now, let’s try to get a webpage.
 Similarly, to make an HTTP POST request:
 
 ```python
->>> r = httpx.post('https://httpbin.org/post', data={'key': 'value'})
+>>> r = await httpx.post('https://httpbin.org/post', data={'key': 'value'})
 ```
 
 The PUT, DELETE, HEAD, and OPTIONS requests all follow the same style:
 
 ```python
->>> r = httpx.put('https://httpbin.org/put', data={'key': 'value'})
->>> r = httpx.delete('https://httpbin.org/delete')
->>> r = httpx.head('https://httpbin.org/get')
->>> r = httpx.options('https://httpbin.org/get')
+>>> r = await httpx.put('https://httpbin.org/put', data={'key': 'value'})
+>>> r = await httpx.delete('https://httpbin.org/delete')
+>>> r = await httpx.head('https://httpbin.org/get')
+>>> r = await httpx.options('https://httpbin.org/get')
 ```
 
 ## Passing Parameters in URLs
@@ -40,7 +41,7 @@ To include URL query parameters in the request, use the `params` keyword:
 
 ```python
 >>> params = {'key1': 'value1', 'key2': 'value2'}
->>> r = httpx.get('https://httpbin.org/get', params=params)
+>>> r = await httpx.get('https://httpbin.org/get', params=params)
 ```
 
 To see how the values get encoding into the URL string, we can inspect the
@@ -55,7 +56,7 @@ You can also pass a list of items as a value:
 
 ```python
 >>> params = {'key1': 'value1', 'key2': ['value2', 'value3']}
->>> r = httpx.get('https://httpbin.org/get', params=params)
+>>> r = await httpx.get('https://httpbin.org/get', params=params)
 >>> r.url
 URL('https://httpbin.org/get?key1=value1&key2=value2&key2=value3')
 ```
@@ -65,7 +66,7 @@ URL('https://httpbin.org/get?key1=value1&key2=value2&key2=value3')
 HTTPX will automatically handle decoding the response content into Unicode text.
 
 ```python
->>> r = httpx.get('https://www.example.org/')
+>>> r = await httpx.get('https://www.example.org/')
 >>> r.text
 '<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
 ```
@@ -110,7 +111,7 @@ For example, to create an image from binary data returned by a request, you can
 Often Web API responses will be encoded as JSON.
 
 ```python
->>> r = httpx.get('https://api.github.com/events')
+>>> r = await httpx.get('https://api.github.com/events')
 >>> r.json()
 [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...' ...  }}]
 ```
@@ -122,7 +123,7 @@ To include additional headers in the outgoing request, use the `headers` keyword
 ```python
 >>> url = 'http://httpbin.org/headers'
 >>> headers = {'user-agent': 'my-app/0.0.1'}
->>> r = httpx.get(url, headers=headers)
+>>> r = await httpx.get(url, headers=headers)
 ```
 
 ## Sending Form Encoded Data
@@ -133,7 +134,7 @@ which is used for HTML forms.
 
 ```python
 >>> data = {'key1': 'value1', 'key2': 'value2'}
->>> r = httpx.post("https://httpbin.org/post", data=data)
+>>> r = await httpx.post("https://httpbin.org/post", data=data)
 >>> print(r.text)
 {
   ...
@@ -149,7 +150,7 @@ Form encoded data can also include multiple values form a given key.
 
 ```python
 >>> data = {'key1': ['value1', 'value2']}
->>> r = httpx.post("https://httpbin.org/post", data=data)
+>>> r = await httpx.post("https://httpbin.org/post", data=data)
 >>> print(r.text)
 {
   ...
@@ -169,7 +170,7 @@ You can also upload files, using HTTP multipart encoding:
 
 ```python
 >>> files = {'upload-file': open('report.xls', 'rb')}
->>> r = httpx.post("https://httpbin.org/post", files=files)
+>>> r = await httpx.post("https://httpbin.org/post", files=files)
 >>> print(r.text)
 {
   ...
@@ -185,7 +186,7 @@ of items for the file value:
 
 ```python
 >>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
->>> r = httpx.post("https://httpbin.org/post", files=files)
+>>> r = await httpx.post("https://httpbin.org/post", files=files)
 >>> print(r.text)
 {
   ...
@@ -203,7 +204,7 @@ For more complicated data structures you'll often want to use JSON encoding inst
 
 ```python
 >>> data = {'integer': 123, 'boolean': True, 'list': ['a', 'b', 'c']}
->>> r = httpx.post("https://httpbin.org/post", json=data)
+>>> r = await httpx.post("https://httpbin.org/post", json=data)
 >>> print(r.text)
 {
   ...
@@ -233,7 +234,7 @@ binary data.
 We can inspect the HTTP status code of the response:
 
 ```python
->>> r = httpx.get('https://httpbin.org/get')
+>>> r = await httpx.get('https://httpbin.org/get')
 >>> r.status_code
 200
 ```
@@ -248,7 +249,7 @@ True
 We can raise an exception for any Client or Server error responses (4xx or 5xx status codes):
 
 ```python
->>> not_found = httpx.get('https://httpbin.org/status/404')
+>>> not_found = await httpx.get('https://httpbin.org/status/404')
 >>> not_found.status_code
 404
 >>> not_found.raise_for_status()
@@ -301,7 +302,7 @@ value, as per [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2):
 Any cookies that are set on the response can be easily accessed:
 
 ```python
->>> r = httpx.get('http://httpbin.org/cookies/set?chocolate=chip', allow_redirects=False)
+>>> r = await httpx.get('http://httpbin.org/cookies/set?chocolate=chip', allow_redirects=False)
 >>> r.cookies['chocolate']
 'chip'
 ```
@@ -310,7 +311,7 @@ To include cookies in an outgoing request, use the `cookies` parameter:
 
 ```python
 >>> cookies = {"peanut": "butter"}
->>> r = httpx.get('http://httpbin.org/cookies', cookies=cookies)
+>>> r = await httpx.get('http://httpbin.org/cookies', cookies=cookies)
 >>> r.json()
 {'cookies': {'peanut': 'butter'}}
 ```
@@ -322,7 +323,7 @@ with additional API for accessing cookies by their domain or path.
 >>> cookies = httpx.Cookies()
 >>> cookies.set('cookie_on_domain', 'hello, there!', domain='httpbin.org')
 >>> cookies.set('cookie_off_domain', 'nope.', domain='example.org')
->>> r = httpx.get('http://httpbin.org/cookies', cookies=cookies)
+>>> r = await httpx.get('http://httpbin.org/cookies', cookies=cookies)
 >>> r.json()
 {'cookies': {'cookie_on_domain': 'hello, there!'}}
 ```
@@ -338,7 +339,7 @@ in which they were made.
 For example, GitHub redirects all HTTP requests to HTTPS.
 
 ```python
->>> r = httpx.get('http://github.com/')
+>>> r = await httpx.get('http://github.com/')
 >>> r.url
 URL('https://github.com/')
 >>> r.status_code
@@ -350,7 +351,7 @@ URL('https://github.com/')
 You can modify the default redirection handling with the allow_redirects parameter:
 
 ```python
->>> r = httpx.get('http://github.com/', allow_redirects=False)
+>>> r = await httpx.get('http://github.com/', allow_redirects=False)
 >>> r.status_code
 301
 >>> r.history
@@ -360,7 +361,7 @@ You can modify the default redirection handling with the allow_redirects paramet
 If you’re making a `HEAD` request, you can use this to enable redirection:
 
 ```python
->>> r = httpx.head('http://github.com/', allow_redirects=True)
+>>> r = await httpx.head('http://github.com/', allow_redirects=True)
 >>> r.url
 'https://github.com/'
 >>> r.history
@@ -377,7 +378,7 @@ The default timeout for network inactivity is five seconds. You can modify the
 value to be more or less strict:
 
 ```python
->>> httpx.get('https://github.com/', timeout=0.001)
+>>> await httpx.get('https://github.com/', timeout=0.001)
 ```
 
 For advanced timeout management, see [Timeout fine-tuning](https://www.encode.io/httpx/advanced/#timeout-fine-tuning).
@@ -391,7 +392,7 @@ plaintext `str` or `bytes` objects as the `auth` argument to the request
 functions:
 
 ```python
->>> httpx.get("https://example.com", auth=("my_user", "password123"))
+>>> await httpx.get("https://example.com", auth=("my_user", "password123"))
 ```
 
 To provide credentials for Digest authentication you'll need to instantiate
@@ -401,6 +402,6 @@ as above:
 
 ```python
 >>> auth = httpx.DigestAuth("my_user", "password123")
->>> httpx.get("https://example.com", auth=auth)
+>>> await httpx.get("https://example.com", auth=auth)
 <Response [200 OK]>
 ```
index 5eb85162c5c3a1358f9dd0035653465e5762000a..9dec3af8cef076931b38dd975ee113151529ce83 100644 (file)
@@ -1,6 +1,6 @@
 from .__version__ import __description__, __title__, __version__
 from .api import delete, get, head, options, patch, post, put, request
-from .client import AsyncClient, Client
+from .client import Client
 from .concurrency.asyncio import AsyncioBackend
 from .concurrency.base import (
     BaseBackgroundManager,
@@ -19,7 +19,7 @@ from .config import (
     TimeoutTypes,
     VerifyTypes,
 )
-from .dispatch.base import AsyncDispatcher, Dispatcher
+from .dispatch.base import Dispatcher
 from .dispatch.connection import HTTPConnection
 from .dispatch.connection_pool import ConnectionPool
 from .dispatch.proxy_http import HTTPProxy, HTTPProxyMode
@@ -46,10 +46,6 @@ from .exceptions import (
 from .middleware.digest_auth import DigestAuth
 from .models import (
     URL,
-    AsyncRequest,
-    AsyncRequestData,
-    AsyncResponse,
-    AsyncResponseContent,
     AuthTypes,
     Cookies,
     CookieTypes,
@@ -80,7 +76,6 @@ __all__ = [
     "patch",
     "put",
     "request",
-    "AsyncClient",
     "Client",
     "AsyncioBackend",
     "USER_AGENT",
@@ -113,7 +108,6 @@ __all__ = [
     "Timeout",
     "TooManyRedirects",
     "WriteTimeout",
-    "AsyncDispatcher",
     "BaseSocketStream",
     "ConcurrencyBackend",
     "Dispatcher",
@@ -124,10 +118,6 @@ __all__ = [
     "TimeoutTypes",
     "HTTPVersionTypes",
     "HTTPVersionConfig",
-    "AsyncRequest",
-    "AsyncRequestData",
-    "AsyncResponse",
-    "AsyncResponseContent",
     "AuthTypes",
     "Cookies",
     "CookieTypes",
index 1e01983c7243bfb42ca52302e6910b1e9be5c1ae..2ea1be62bbba2447808feeeb59c5c91fc6660541 100644 (file)
@@ -1,7 +1,7 @@
 import typing
 
 from .client import Client
-from .config import CertTypes, TimeoutTypes, VerifyTypes
+from .config import DEFAULT_TIMEOUT_CONFIG, CertTypes, TimeoutTypes, VerifyTypes
 from .models import (
     AuthTypes,
     CookieTypes,
@@ -14,7 +14,7 @@ from .models import (
 )
 
 
-def request(
+async def request(
     method: str,
     url: URLTypes,
     *,
@@ -25,12 +25,12 @@ def request(
     headers: HeaderTypes = None,
     cookies: CookieTypes = None,
     auth: AuthTypes = None,
-    timeout: TimeoutTypes = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
     allow_redirects: bool = True,
     verify: VerifyTypes = True,
     cert: CertTypes = None,
     stream: bool = False,
-    trust_env: bool = None,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends an HTTP request.
@@ -75,13 +75,19 @@ def request(
 
     ```
     >>> import httpx
-    >>> response = httpx.request('GET', 'https://httpbin.org/get')
+    >>> response = await httpx.request('GET', 'https://httpbin.org/get')
     >>> response
     <Response [200 OK]>
     ```
     """
-    with Client(http_versions=["HTTP/1.1"]) as client:
-        return client.request(
+    async with Client(
+        http_versions=["HTTP/1.1"],
+        cert=cert,
+        verify=verify,
+        timeout=timeout,
+        trust_env=trust_env,
+    ) as client:
+        return await client.request(
             method=method,
             url=url,
             data=data,
@@ -93,14 +99,10 @@ def request(
             stream=stream,
             auth=auth,
             allow_redirects=allow_redirects,
-            cert=cert,
-            verify=verify,
-            timeout=timeout,
-            trust_env=trust_env,
         )
 
 
-def get(
+async def get(
     url: URLTypes,
     *,
     params: QueryParamTypes = None,
@@ -111,8 +113,8 @@ def get(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `GET` request.
@@ -122,7 +124,7 @@ def get(
     Note that the `data`, `files`, and `json` parameters are not available on
     this function, as `GET` requests should not include a request body.
     """
-    return request(
+    return await request(
         "GET",
         url,
         params=params,
@@ -138,7 +140,7 @@ def get(
     )
 
 
-def options(
+async def options(
     url: URLTypes,
     *,
     params: QueryParamTypes = None,
@@ -149,8 +151,8 @@ def options(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends an `OPTIONS` request.
@@ -160,7 +162,7 @@ def options(
     Note that the `data`, `files`, and `json` parameters are not available on
     this function, as `OPTIONS` requests should not include a request body.
     """
-    return request(
+    return await request(
         "OPTIONS",
         url,
         params=params,
@@ -176,7 +178,7 @@ def options(
     )
 
 
-def head(
+async def head(
     url: URLTypes,
     *,
     params: QueryParamTypes = None,
@@ -184,11 +186,11 @@ def head(
     cookies: CookieTypes = None,
     stream: bool = False,
     auth: AuthTypes = None,
-    allow_redirects: bool = False,  #  Note: Differs to usual default.
+    allow_redirects: bool = False,  # Note: Differs to usual default.
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `HEAD` request.
@@ -200,7 +202,7 @@ def head(
     `HEAD` method also differs from the other cases in that `allow_redirects`
     defaults to `False`.
     """
-    return request(
+    return await request(
         "HEAD",
         url,
         params=params,
@@ -216,7 +218,7 @@ def head(
     )
 
 
-def post(
+async def post(
     url: URLTypes,
     *,
     data: RequestData = None,
@@ -230,15 +232,15 @@ def post(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `POST` request.
 
     **Parameters**: See `httpx.request`.
     """
-    return request(
+    return await request(
         "POST",
         url,
         data=data,
@@ -257,7 +259,7 @@ def post(
     )
 
 
-def put(
+async def put(
     url: URLTypes,
     *,
     data: RequestData = None,
@@ -271,15 +273,15 @@ def put(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `PUT` request.
 
     **Parameters**: See `httpx.request`.
     """
-    return request(
+    return await request(
         "PUT",
         url,
         data=data,
@@ -298,7 +300,7 @@ def put(
     )
 
 
-def patch(
+async def patch(
     url: URLTypes,
     *,
     data: RequestData = None,
@@ -312,15 +314,15 @@ def patch(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `PATCH` request.
 
     **Parameters**: See `httpx.request`.
     """
-    return request(
+    return await request(
         "PATCH",
         url,
         data=data,
@@ -339,7 +341,7 @@ def patch(
     )
 
 
-def delete(
+async def delete(
     url: URLTypes,
     *,
     params: QueryParamTypes = None,
@@ -350,8 +352,8 @@ def delete(
     allow_redirects: bool = True,
     cert: CertTypes = None,
     verify: VerifyTypes = True,
-    timeout: TimeoutTypes = None,
-    trust_env: bool = None,
+    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
+    trust_env: bool = True,
 ) -> Response:
     """
     Sends a `DELETE` request.
@@ -361,7 +363,7 @@ def delete(
     Note that the `data`, `files`, and `json` parameters are not available on
     this function, as `DELETE` requests should not include a request body.
     """
-    return request(
+    return await request(
         "DELETE",
         url,
         params=params,
index c327c2db003f1d66b904d535d578bc055be1695d..50b33f782da29d76b7b33da411c9b004d27cde40 100644 (file)
@@ -1,5 +1,4 @@
 import functools
-import inspect
 import netrc
 import typing
 from types import TracebackType
@@ -19,11 +18,9 @@ from .config import (
     VerifyTypes,
 )
 from .dispatch.asgi import ASGIDispatch
-from .dispatch.base import AsyncDispatcher, Dispatcher
+from .dispatch.base import Dispatcher
 from .dispatch.connection_pool import ConnectionPool
 from .dispatch.proxy_http import HTTPProxy
-from .dispatch.threaded import ThreadedDispatcher
-from .dispatch.wsgi import WSGIDispatch
 from .exceptions import HTTPError, InvalidURL
 from .middleware.base import BaseMiddleware
 from .middleware.basic_auth import BasicAuthMiddleware
@@ -31,10 +28,6 @@ from .middleware.custom_auth import CustomAuthMiddleware
 from .middleware.redirect import RedirectMiddleware
 from .models import (
     URL,
-    AsyncRequest,
-    AsyncRequestData,
-    AsyncResponse,
-    AsyncResponseContent,
     AuthTypes,
     Cookies,
     CookieTypes,
@@ -43,10 +36,10 @@ from .models import (
     ProxiesTypes,
     QueryParams,
     QueryParamTypes,
+    Request,
     RequestData,
     RequestFiles,
     Response,
-    ResponseContent,
     URLTypes,
 )
 from .utils import ElapsedTimer, get_environment_proxies, get_logger, get_netrc
@@ -54,7 +47,57 @@ from .utils import ElapsedTimer, get_environment_proxies, get_logger, get_netrc
 logger = get_logger(__name__)
 
 
-class BaseClient:
+class Client:
+    """
+    An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.
+
+    Usage:
+
+    ```
+    >>> client = httpx.Client()
+    >>> response = client.get('https://example.org')
+    ```
+
+    **Parameters:**
+
+    * **auth** - *(optional)* An authentication class to use when sending
+    requests.
+    * **params** - *(optional)* Query parameters to include in request URLs, as
+    a string, dictionary, or list of two-tuples.
+    * **headers** - *(optional)* Dictionary of HTTP headers to include when
+    sending requests.
+    * **cookies** - *(optional)* Dictionary of Cookie items to include when
+    sending requests.
+    * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
+    verify the identity of requested hosts. Either `True` (default CA bundle),
+    a path to an SSL certificate file, or `False` (disable verification).
+    * **cert** - *(optional)* An SSL certificate used by the requested host
+    to authenticate the client. Either a path to an SSL certificate file, or
+    two-tuple of (certificate file, key file), or a three-tuple of (certificate
+    file, key file, password).
+    * **http_versions** - *(optional)* A list of strings of HTTP protocol
+    versions to use when sending requests. eg. `http_versions=["HTTP/1.1"]`
+    * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
+    URLs.
+    * **timeout** - *(optional)* The timeout configuration to use when sending
+    requests.
+    * **pool_limits** - *(optional)* The connection pool configuration to use
+    when determining the maximum number of concurrently open HTTP connections.
+    * **max_redirects** - *(optional)* The maximum number of redirect responses
+    that should be followed.
+    * **base_url** - *(optional)* A URL to use as the base when building
+    request URLs.
+    * **dispatch** - *(optional)* A dispatch class to use for sending requests
+    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.
+    * **trust_env** - *(optional)* Enables or disables usage of environment
+    variables for configuration.
+    * **uds** - *(optional)* A path to a Unix domain socket to connect through.
+    """
+
     def __init__(
         self,
         *,
@@ -70,7 +113,7 @@ class BaseClient:
         pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
         max_redirects: int = DEFAULT_MAX_REDIRECTS,
         base_url: URLTypes = None,
-        dispatch: typing.Union[AsyncDispatcher, Dispatcher] = None,
+        dispatch: Dispatcher = None,
         app: typing.Callable = None,
         backend: ConcurrencyBackend = None,
         trust_env: bool = True,
@@ -79,20 +122,13 @@ class BaseClient:
         if backend is None:
             backend = AsyncioBackend()
 
-        self.check_concurrency_backend(backend)
-
         if app is not None:
-            param_count = len(inspect.signature(app).parameters)
-            assert param_count in (2, 3)
-            if param_count == 2:
-                dispatch = WSGIDispatch(app=app)
-            else:
-                dispatch = ASGIDispatch(app=app, backend=backend)
+            dispatch = ASGIDispatch(app=app, backend=backend)
 
         self.trust_env = True if trust_env is None else trust_env
 
         if dispatch is None:
-            async_dispatch: AsyncDispatcher = ConnectionPool(
+            dispatch = ConnectionPool(
                 verify=verify,
                 cert=cert,
                 timeout=timeout,
@@ -102,10 +138,6 @@ class BaseClient:
                 trust_env=self.trust_env,
                 uds=uds,
             )
-        elif isinstance(dispatch, Dispatcher):
-            async_dispatch = ThreadedDispatcher(dispatch, backend)
-        else:
-            async_dispatch = dispatch
 
         if base_url is None:
             self.base_url = URL("", allow_relative=True)
@@ -120,13 +152,13 @@ class BaseClient:
         self._headers = Headers(headers)
         self._cookies = Cookies(cookies)
         self.max_redirects = max_redirects
-        self.dispatch = async_dispatch
+        self.dispatch = dispatch
         self.concurrency_backend = backend
 
         if proxies is None and trust_env:
             proxies = typing.cast(ProxiesTypes, get_environment_proxies())
 
-        self.proxies: typing.Dict[str, AsyncDispatcher] = _proxies_to_dispatchers(
+        self.proxies: typing.Dict[str, Dispatcher] = _proxies_to_dispatchers(
             proxies,
             verify=verify,
             cert=cert,
@@ -170,9 +202,6 @@ class BaseClient:
     def params(self, params: QueryParamTypes) -> None:
         self._params = QueryParams(params)
 
-    def check_concurrency_backend(self, backend: ConcurrencyBackend) -> None:
-        pass  # pragma: no cover
-
     def merge_url(self, url: URLTypes) -> URL:
         url = self.base_url.join(relative_url=url)
         if url.scheme == "http" and hstspreload.in_hsts_preload(url.host):
@@ -208,7 +237,7 @@ class BaseClient:
 
     async def _get_response(
         self,
-        request: AsyncRequest,
+        request: Request,
         *,
         stream: bool = False,
         auth: AuthTypes = None,
@@ -217,19 +246,20 @@ class BaseClient:
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.scheme not in ("http", "https"):
             raise InvalidURL('URL scheme must be "http" or "https".')
 
         dispatch = self._dispatcher_for_request(request, self.proxies)
 
-        async def get_response(request: AsyncRequest) -> AsyncResponse:
+        async def get_response(request: Request) -> Response:
             try:
                 with ElapsedTimer() as timer:
                     response = await dispatch.send(
                         request, verify=verify, cert=cert, timeout=timeout
                     )
                 response.elapsed = timer.elapsed
+                response.request = request
             except HTTPError as exc:
                 # Add the original request to any HTTPError unless
                 # there'a already a request attached in the case of
@@ -275,7 +305,7 @@ class BaseClient:
         return await get_response(request)
 
     def _get_auth_middleware(
-        self, request: AsyncRequest, trust_env: bool, auth: AuthTypes = None
+        self, request: Request, trust_env: bool, auth: AuthTypes = None
     ) -> typing.Optional[BaseMiddleware]:
         if isinstance(auth, tuple):
             return BasicAuthMiddleware(username=auth[0], password=auth[1])
@@ -287,7 +317,7 @@ class BaseClient:
         if auth is not None:
             raise TypeError(
                 'When specified, "auth" must be a (username, password) tuple or '
-                "a callable with signature (AsyncRequest) -> AsyncRequest "
+                "a callable with signature (Request) -> Request "
                 f"(got {auth!r})"
             )
 
@@ -312,9 +342,9 @@ class BaseClient:
         return get_netrc()
 
     def _dispatcher_for_request(
-        self, request: AsyncRequest, proxies: typing.Dict[str, AsyncDispatcher]
-    ) -> AsyncDispatcher:
-        """Gets the AsyncDispatcher instance that should be used for a given Request"""
+        self, request: Request, proxies: typing.Dict[str, Dispatcher]
+    ) -> Dispatcher:
+        """Gets the Dispatcher instance that should be used for a given Request"""
         if proxies:
             url = request.url
             is_default_port = (url.scheme == "http" and url.port == 80) or (
@@ -341,13 +371,13 @@ class BaseClient:
         method: str,
         url: URLTypes,
         *,
-        data: AsyncRequestData = None,
+        data: RequestData = None,
         files: RequestFiles = None,
         json: typing.Any = None,
         params: QueryParamTypes = None,
         headers: HeaderTypes = None,
         cookies: CookieTypes = None,
-    ) -> AsyncRequest:
+    ) -> Request:
         """
         Build and return a request instance.
         """
@@ -355,7 +385,7 @@ class BaseClient:
         headers = self.merge_headers(headers)
         cookies = self.merge_cookies(cookies)
         params = self.merge_queryparams(params)
-        return AsyncRequest(
+        return Request(
             method,
             url,
             data=data,
@@ -366,8 +396,6 @@ class BaseClient:
             cookies=cookies,
         )
 
-
-class AsyncClient(BaseClient):
     async def get(
         self,
         url: URLTypes,
@@ -382,7 +410,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "GET",
             url,
@@ -412,7 +440,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "OPTIONS",
             url,
@@ -442,7 +470,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "HEAD",
             url,
@@ -462,7 +490,7 @@ class AsyncClient(BaseClient):
         self,
         url: URLTypes,
         *,
-        data: AsyncRequestData = None,
+        data: RequestData = None,
         files: RequestFiles = None,
         json: typing.Any = None,
         params: QueryParamTypes = None,
@@ -475,7 +503,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "POST",
             url,
@@ -498,7 +526,7 @@ class AsyncClient(BaseClient):
         self,
         url: URLTypes,
         *,
-        data: AsyncRequestData = None,
+        data: RequestData = None,
         files: RequestFiles = None,
         json: typing.Any = None,
         params: QueryParamTypes = None,
@@ -511,7 +539,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "PUT",
             url,
@@ -534,7 +562,7 @@ class AsyncClient(BaseClient):
         self,
         url: URLTypes,
         *,
-        data: AsyncRequestData = None,
+        data: RequestData = None,
         files: RequestFiles = None,
         json: typing.Any = None,
         params: QueryParamTypes = None,
@@ -547,7 +575,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "PATCH",
             url,
@@ -580,7 +608,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self.request(
             "DELETE",
             url,
@@ -601,7 +629,7 @@ class AsyncClient(BaseClient):
         method: str,
         url: URLTypes,
         *,
-        data: AsyncRequestData = None,
+        data: RequestData = None,
         files: RequestFiles = None,
         json: typing.Any = None,
         params: QueryParamTypes = None,
@@ -614,7 +642,7 @@ class AsyncClient(BaseClient):
         verify: VerifyTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         request = self.build_request(
             method=method,
             url=url,
@@ -639,7 +667,7 @@ class AsyncClient(BaseClient):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         *,
         stream: bool = False,
         auth: AuthTypes = None,
@@ -648,7 +676,7 @@ class AsyncClient(BaseClient):
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
         trust_env: bool = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         return await self._get_response(
             request=request,
             stream=stream,
@@ -663,7 +691,7 @@ class AsyncClient(BaseClient):
     async def close(self) -> None:
         await self.dispatch.close()
 
-    async def __aenter__(self) -> "AsyncClient":
+    async def __aenter__(self) -> "Client":
         return self
 
     async def __aexit__(
@@ -675,537 +703,6 @@ class AsyncClient(BaseClient):
         await self.close()
 
 
-class Client(BaseClient):
-    """
-    An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.
-
-    Usage:
-
-    ```
-    >>> client = httpx.Client()
-    >>> response = client.get('https://example.org')
-    ```
-
-    **Parameters:**
-
-    * **auth** - *(optional)* An authentication class to use when sending
-    requests.
-    * **params** - *(optional)* Query parameters to include in request URLs, as
-    a string, dictionary, or list of two-tuples.
-    * **headers** - *(optional)* Dictionary of HTTP headers to include when
-    sending requests.
-    * **cookies** - *(optional)* Dictionary of Cookie items to include when
-    sending requests.
-    * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
-    verify the identity of requested hosts. Either `True` (default CA bundle),
-    a path to an SSL certificate file, or `False` (disable verification).
-    * **cert** - *(optional)* An SSL certificate used by the requested host
-    to authenticate the client. Either a path to an SSL certificate file, or
-    two-tuple of (certificate file, key file), or a three-tuple of (certificate
-    file, key file, password).
-    * **http_versions** - *(optional)* A list of strings of HTTP protocol
-    versions to use when sending requests. eg. `http_versions=["HTTP/1.1"]`
-    * **proxies** - *(optional)* A dictionary mapping HTTP protocols to proxy
-    URLs.
-    * **timeout** - *(optional)* The timeout configuration to use when sending
-    requests.
-    * **pool_limits** - *(optional)* The connection pool configuration to use
-    when determining the maximum number of concurrently open HTTP connections.
-    * **max_redirects** - *(optional)* The maximum number of redirect responses
-    that should be followed.
-    * **base_url** - *(optional)* A URL to use as the base when building
-    request URLs.
-    * **dispatch** - *(optional)* A dispatch class to use for sending requests
-    over the network.
-    * **app** - *(optional)* A WSGI or ASGI application to send requests to,
-    rather than sending actual network requests.
-    * **backend** - *(optional)* A concurrency backend to use when issuing
-    async requests.
-    * **trust_env** - *(optional)* Enables or disables usage of environment
-    variables for configuration.
-    * **uds** - *(optional)* A path to a Unix domain socket to connect through.
-    """
-
-    def check_concurrency_backend(self, backend: ConcurrencyBackend) -> None:
-        # Iterating over response content allocates an async environment on each step.
-        # This is relatively cheap on asyncio, but cannot be guaranteed for all
-        # concurrency backends.
-        # The sync client performs I/O on its own, so it doesn't need to support
-        # arbitrary concurrency backends.
-        # Therefore, we keep the `backend` parameter (for testing/mocking), but require
-        # that the concurrency backend relies on asyncio.
-
-        if isinstance(backend, AsyncioBackend):
-            return
-
-        if hasattr(backend, "loop"):
-            # Most likely a proxy class.
-            return
-
-        raise ValueError("'Client' only supports asyncio-based concurrency backends")
-
-    def _async_request_data(
-        self, data: RequestData = None
-    ) -> typing.Optional[AsyncRequestData]:
-        """
-        If the request data is an bytes iterator then return an async bytes
-        iterator onto the request data.
-        """
-        if data is None or isinstance(data, (str, bytes, dict)):
-            return data
-
-        # Coerce an iterator into an async iterator, with each item in the
-        # iteration running as a thread-pooled operation.
-        assert hasattr(data, "__iter__")
-        return self.concurrency_backend.iterate_in_threadpool(data)
-
-    def _sync_data(self, data: AsyncResponseContent) -> ResponseContent:
-        if isinstance(data, bytes):
-            return data
-
-        # Coerce an async iterator into an iterator, with each item in the
-        # iteration run within the event loop.
-        assert hasattr(data, "__aiter__")
-        return self.concurrency_backend.iterate(data)
-
-    def request(
-        self,
-        method: str,
-        url: URLTypes,
-        *,
-        data: RequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends an HTTP request.
-
-        **Parameters:**
-
-        * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`,
-        `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`.
-        * **url** - URL for the new `Request` object.
-        * **data** - *(optional)* Data to include in the body of the request, as a
-        dictionary
-        * **files** - *(optional)* A dictionary of upload files to include in the
-        body of the request.
-        * **json** - *(optional)* A JSON serializable object to include in the body
-        of the request.
-        * **params** - *(optional)* Query parameters to include in the URL, as a
-        string, dictionary, or list of two-tuples.
-        * **headers** - *(optional)* Dictionary of HTTP headers to include on the
-        request.
-        * **cookies** - *(optional)* Dictionary of Cookie items to include in the
-        request.
-        * **stream** - *(optional)* Enable/disable streaming responses.
-        * **auth** - *(optional)* An authentication class to use when sending the
-        request.
-        * **allow_redirects** - *(optional)* Enables or disables HTTP redirects.
-        * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
-        verify the identity of requested hosts. Either `True` (default CA bundle),
-        a path to an SSL certificate file, or `False` (disable verification).
-        * **cert** - *(optional)* An SSL certificate used by the requested host
-        to authenticate the client. Either a path to an SSL certificate file, or
-        two-tuple of (certificate file, key file), or a three-tuple of (certificate
-        file, key file, password).
-        * **timeout** - *(optional)* The timeout configuration to use when sending
-        the request.
-        * **trust_env** - *(optional)* Enables or disables usage of environment
-        variables for configuration.
-
-        **Returns:** `Response`
-
-        Usage:
-
-        ```
-        >>> import httpx
-        >>> client = httpx.Client()
-        >>> response = client.request('GET', 'https://httpbin.org/get')
-        >>> response
-        <Response [200 OK]>
-        ```
-        """
-        request = self.build_request(
-            method=method,
-            url=url,
-            data=self._async_request_data(data),
-            files=files,
-            json=json,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-        )
-        return self.send(
-            request,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def send(
-        self,
-        request: AsyncRequest,
-        *,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a request over the network, returning a response.
-        """
-        concurrency_backend = self.concurrency_backend
-
-        coroutine = self._get_response
-        args = [request]
-        kwargs = {
-            "stream": True,
-            "auth": auth,
-            "allow_redirects": allow_redirects,
-            "verify": verify,
-            "cert": cert,
-            "timeout": timeout,
-            "trust_env": trust_env,
-        }
-        async_response = concurrency_backend.run(coroutine, *args, **kwargs)
-
-        content = getattr(
-            async_response, "_raw_content", getattr(async_response, "_raw_stream", None)
-        )
-
-        sync_content = self._sync_data(content)
-
-        def sync_on_close() -> None:
-            nonlocal concurrency_backend, async_response
-            concurrency_backend.run(async_response.on_close)
-
-        response = Response(
-            status_code=async_response.status_code,
-            http_version=async_response.http_version,
-            headers=async_response.headers,
-            content=sync_content,
-            on_close=sync_on_close,
-            request=async_response.request,
-            history=async_response.history,
-            elapsed=async_response.elapsed,
-        )
-        if not stream:
-            try:
-                response.read()
-            finally:
-                response.close()
-        return response
-
-    def get(
-        self,
-        url: URLTypes,
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `GET` request.
-
-        **Parameters**: See `Client.request`.
-
-        Note that the `data`, `files`, and `json` parameters are not available on
-        this function, as `GET` requests should not include a request body.
-        """
-        return self.request(
-            "GET",
-            url,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def options(
-        self,
-        url: URLTypes,
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends an `OPTIONS` request.
-
-        **Parameters**: See `Client.request`.
-
-        Note that the `data`, `files`, and `json` parameters are not available on
-        this function, as `OPTIONS` requests should not include a request body.
-        """
-        return self.request(
-            "OPTIONS",
-            url,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def head(
-        self,
-        url: URLTypes,
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = False,  # NOTE: Differs to usual default.
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `HEAD` request.
-
-        **Parameters**: See `Client.request`.
-
-        Note that the `data`, `files`, and `json` parameters are not available on
-        this function, as `HEAD` requests should not include a request body. The
-        `HEAD` method also differs from the other cases in that `allow_redirects`
-        defaults to `False`.
-        """
-        return self.request(
-            "HEAD",
-            url,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def post(
-        self,
-        url: URLTypes,
-        *,
-        data: RequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `POST` request.
-
-        **Parameters**: See `Client.request`.
-        """
-        return self.request(
-            "POST",
-            url,
-            data=data,
-            files=files,
-            json=json,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def put(
-        self,
-        url: URLTypes,
-        *,
-        data: RequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `PUT` request.
-
-        **Parameters**: See `Client.request`.
-        """
-        return self.request(
-            "PUT",
-            url,
-            data=data,
-            files=files,
-            json=json,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def patch(
-        self,
-        url: URLTypes,
-        *,
-        data: RequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `PATCH` request.
-
-        **Parameters**: See `Client.request`.
-        """
-        return self.request(
-            "PATCH",
-            url,
-            data=data,
-            files=files,
-            json=json,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def delete(
-        self,
-        url: URLTypes,
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        stream: bool = False,
-        auth: AuthTypes = None,
-        allow_redirects: bool = True,
-        cert: CertTypes = None,
-        verify: VerifyTypes = None,
-        timeout: TimeoutTypes = None,
-        trust_env: bool = None,
-    ) -> Response:
-        """
-        Sends a `DELETE` request.
-
-        **Parameters**: See `Client.request`.
-        """
-        return self.request(
-            "DELETE",
-            url,
-            params=params,
-            headers=headers,
-            cookies=cookies,
-            stream=stream,
-            auth=auth,
-            allow_redirects=allow_redirects,
-            verify=verify,
-            cert=cert,
-            timeout=timeout,
-            trust_env=trust_env,
-        )
-
-    def close(self) -> None:
-        """
-        Close any open connections in the connection pool.
-        """
-        coroutine = self.dispatch.close
-        self.concurrency_backend.run(coroutine)
-
-    def __enter__(self) -> "Client":
-        return self
-
-    def __exit__(
-        self,
-        exc_type: typing.Type[BaseException] = None,
-        exc_value: BaseException = None,
-        traceback: TracebackType = None,
-    ) -> None:
-        self.close()
-
-
 def _proxies_to_dispatchers(
     proxies: typing.Optional[ProxiesTypes],
     verify: VerifyTypes,
@@ -1215,8 +712,8 @@ def _proxies_to_dispatchers(
     pool_limits: PoolLimits,
     backend: ConcurrencyBackend,
     trust_env: bool,
-) -> typing.Dict[str, AsyncDispatcher]:
-    def _proxy_from_url(url: URLTypes) -> AsyncDispatcher:
+) -> typing.Dict[str, Dispatcher]:
+    def _proxy_from_url(url: URLTypes) -> Dispatcher:
         nonlocal verify, cert, timeout, http_versions, pool_limits, backend, trust_env
         url = URL(url)
         if url.scheme in ("http", "https"):
@@ -1236,7 +733,7 @@ def _proxies_to_dispatchers(
         return {}
     elif isinstance(proxies, (str, URL)):
         return {"all": _proxy_from_url(proxies)}
-    elif isinstance(proxies, AsyncDispatcher):
+    elif isinstance(proxies, Dispatcher):
         return {"all": proxies}
     else:
         new_proxies = {}
index 3089371fd3ae1018c8ef43984df0a21198ec7c65..2be8dd6d62563c362c8334518f419a4621be1577 100644 (file)
@@ -3,6 +3,5 @@ Dispatch classes handle the raw network connections and the implementation
 details of making the HTTP request and receiving the response.
 """
 from .asgi import ASGIDispatch
-from .wsgi import WSGIDispatch
 
-__all__ = ["ASGIDispatch", "WSGIDispatch"]
+__all__ = ["ASGIDispatch"]
index 72fef53faddba0dd46aed46be370f2674b52ece9..ab640b86e037e16617cbb6761298a4c8aa7c5258 100644 (file)
@@ -3,14 +3,14 @@ import typing
 from ..concurrency.asyncio import AsyncioBackend
 from ..concurrency.base import ConcurrencyBackend
 from ..config import CertTypes, TimeoutTypes, VerifyTypes
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 from ..utils import MessageLoggerASGIMiddleware, get_logger
-from .base import AsyncDispatcher
+from .base import Dispatcher
 
 logger = get_logger(__name__)
 
 
-class ASGIDispatch(AsyncDispatcher):
+class ASGIDispatch(Dispatcher):
     """
     A custom dispatcher that handles sending requests directly to an ASGI app.
 
@@ -62,11 +62,11 @@ class ASGIDispatch(AsyncDispatcher):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
 
         scope = {
             "type": "http",
@@ -146,7 +146,7 @@ class ASGIDispatch(AsyncDispatcher):
             if app_exc is not None and self.raise_app_exceptions:
                 raise app_exc
 
-        return AsyncResponse(
+        return Response(
             status_code=status_code,
             http_version="HTTP/1.1",
             headers=headers,
index d0baa51416866c1e9745154110f0913fdaa0aa39..6e0af89861915a540d07e2cf652f6a9c50652337 100644 (file)
@@ -3,9 +3,6 @@ from types import TracebackType
 
 from ..config import CertTypes, TimeoutTypes, VerifyTypes
 from ..models import (
-    AsyncRequest,
-    AsyncRequestData,
-    AsyncResponse,
     HeaderTypes,
     QueryParamTypes,
     Request,
@@ -15,64 +12,16 @@ from ..models import (
 )
 
 
-class AsyncDispatcher:
-    """
-    Base class for async dispatcher classes, that handle sending the request.
-
-    Stubs out the interface, as well as providing a `.request()` convenience
-    implementation, to make it easy to use or test stand-alone dispatchers,
-    without requiring a complete `Client` instance.
-    """
-
-    async def request(
-        self,
-        method: str,
-        url: URLTypes,
-        *,
-        data: AsyncRequestData = b"",
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
-        request = AsyncRequest(method, url, data=data, params=params, headers=headers)
-        return await self.send(request, verify=verify, cert=cert, timeout=timeout)
-
-    async def send(
-        self,
-        request: AsyncRequest,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
-        raise NotImplementedError()  # pragma: nocover
-
-    async def close(self) -> None:
-        pass  # pragma: nocover
-
-    async def __aenter__(self) -> "AsyncDispatcher":
-        return self
-
-    async def __aexit__(
-        self,
-        exc_type: typing.Type[BaseException] = None,
-        exc_value: BaseException = None,
-        traceback: TracebackType = None,
-    ) -> None:
-        await self.close()
-
-
 class Dispatcher:
     """
-    Base class for synchronous dispatcher classes, that handle sending the request.
+    Base class for dispatcher classes, that handle sending the request.
 
     Stubs out the interface, as well as providing a `.request()` convenience
     implementation, to make it easy to use or test stand-alone dispatchers,
     without requiring a complete `Client` instance.
     """
 
-    def request(
+    async def request(
         self,
         method: str,
         url: URLTypes,
@@ -85,9 +34,9 @@ class Dispatcher:
         timeout: TimeoutTypes = None,
     ) -> Response:
         request = Request(method, url, data=data, params=params, headers=headers)
-        return self.send(request, verify=verify, cert=cert, timeout=timeout)
+        return await self.send(request, verify=verify, cert=cert, timeout=timeout)
 
-    def send(
+    async def send(
         self,
         request: Request,
         verify: VerifyTypes = None,
@@ -96,16 +45,16 @@ class Dispatcher:
     ) -> Response:
         raise NotImplementedError()  # pragma: nocover
 
-    def close(self) -> None:
+    async def close(self) -> None:
         pass  # pragma: nocover
 
-    def __enter__(self) -> "Dispatcher":
+    async def __aenter__(self) -> "Dispatcher":
         return self
 
-    def __exit__(
+    async def __aexit__(
         self,
         exc_type: typing.Type[BaseException] = None,
         exc_value: BaseException = None,
         traceback: TracebackType = None,
     ) -> None:
-        self.close()
+        await self.close()
index 0612bccb86828f77aee783ad2be47deb73ed3c28..6694caa2deb06312bab84cdfd01f2f1b3824b140 100644 (file)
@@ -14,9 +14,9 @@ from ..config import (
     TimeoutTypes,
     VerifyTypes,
 )
-from ..models import AsyncRequest, AsyncResponse, Origin
+from ..models import Origin, Request, Response
 from ..utils import get_logger
-from .base import AsyncDispatcher
+from .base import Dispatcher
 from .http2 import HTTP2Connection
 from .http11 import HTTP11Connection
 
@@ -27,7 +27,7 @@ ReleaseCallback = typing.Callable[["HTTPConnection"], typing.Awaitable[None]]
 logger = get_logger(__name__)
 
 
-class HTTPConnection(AsyncDispatcher):
+class HTTPConnection(Dispatcher):
     def __init__(
         self,
         origin: typing.Union[str, Origin],
@@ -52,11 +52,11 @@ class HTTPConnection(AsyncDispatcher):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if self.h11_connection is None and self.h2_connection is None:
             await self.connect(verify=verify, cert=cert, timeout=timeout)
 
index 5d7d886dec4ceab518c006a40c1d4a2d07ba7539..d64a1930a8a5fb0f39515e0950d5cf87cefd68dd 100644 (file)
@@ -11,9 +11,9 @@ from ..config import (
     TimeoutTypes,
     VerifyTypes,
 )
-from ..models import AsyncRequest, AsyncResponse, Origin
+from ..models import Origin, Request, Response
 from ..utils import get_logger
-from .base import AsyncDispatcher
+from .base import Dispatcher
 from .connection import HTTPConnection
 
 CONNECTIONS_DICT = typing.Dict[Origin, typing.List[HTTPConnection]]
@@ -78,7 +78,7 @@ class ConnectionStore:
         return len(self.all)
 
 
-class ConnectionPool(AsyncDispatcher):
+class ConnectionPool(Dispatcher):
     def __init__(
         self,
         *,
@@ -112,11 +112,11 @@ class ConnectionPool(AsyncDispatcher):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         connection = await self.acquire_connection(origin=request.url.origin)
         try:
             response = await connection.send(
index 188d56dc99c242c6f5d73491a33c6b10420330f6..058691a30e8674bc7f4295473ea0d417c447873f 100644 (file)
@@ -5,7 +5,7 @@ import h11
 from ..concurrency.base import BaseSocketStream, ConcurrencyBackend, TimeoutFlag
 from ..config import TimeoutConfig, TimeoutTypes
 from ..exceptions import ConnectionClosed, ProtocolError
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 from ..utils import get_logger
 
 H11Event = typing.Union[
@@ -42,9 +42,7 @@ class HTTP11Connection:
         self.h11_state = h11.Connection(our_role=h11.CLIENT)
         self.timeout_flag = TimeoutFlag()
 
-    async def send(
-        self, request: AsyncRequest, timeout: TimeoutTypes = None
-    ) -> AsyncResponse:
+    async def send(self, request: Request, timeout: TimeoutTypes = None) -> Response:
         timeout = None if timeout is None else TimeoutConfig(timeout)
 
         await self._send_request(request, timeout)
@@ -54,7 +52,7 @@ class HTTP11Connection:
             http_version, status_code, headers = await self._receive_response(timeout)
         content = self._receive_response_data(timeout)
 
-        return AsyncResponse(
+        return Response(
             status_code=status_code,
             http_version=http_version,
             headers=headers,
@@ -74,7 +72,7 @@ class HTTP11Connection:
         await self.stream.close()
 
     async def _send_request(
-        self, request: AsyncRequest, timeout: TimeoutConfig = None
+        self, request: Request, timeout: TimeoutConfig = None
     ) -> None:
         """
         Send the request method, URL, and headers to the network.
index 5c6643103d5f3cb9867f7c62db805935de395642..9947155a0c4bc9bcf6c7b34b8b8f8e13f12924e6 100644 (file)
@@ -13,7 +13,7 @@ from ..concurrency.base import (
 )
 from ..config import TimeoutConfig, TimeoutTypes
 from ..exceptions import ProtocolError
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 from ..utils import get_logger
 
 logger = get_logger(__name__)
@@ -37,9 +37,7 @@ class HTTP2Connection:
         self.initialized = False
         self.window_update_received = {}  # type: typing.Dict[int, BaseEvent]
 
-    async def send(
-        self, request: AsyncRequest, timeout: TimeoutTypes = None
-    ) -> AsyncResponse:
+    async def send(self, request: Request, timeout: TimeoutTypes = None) -> Response:
         timeout = None if timeout is None else TimeoutConfig(timeout)
 
         # Start sending the request.
@@ -58,7 +56,7 @@ class HTTP2Connection:
         content = self.body_iter(stream_id, timeout)
         on_close = functools.partial(self.response_closed, stream_id=stream_id)
 
-        return AsyncResponse(
+        return Response(
             status_code=status_code,
             http_version="HTTP/2",
             headers=headers,
@@ -99,7 +97,7 @@ class HTTP2Connection:
         self.initialized = True
 
     async def send_headers(
-        self, request: AsyncRequest, timeout: TimeoutConfig = None
+        self, request: Request, timeout: TimeoutConfig = None
     ) -> int:
         stream_id = self.h2_state.get_next_available_stream_id()
         headers = [
index 8ad0ca85972bf6e1cbdec060e40462c1c63f0d96..aa7dbc5f004539cb1509d320e880aac04210a737 100644 (file)
@@ -15,15 +15,7 @@ from ..config import (
 )
 from ..exceptions import ProxyError
 from ..middleware.basic_auth import build_basic_auth_header
-from ..models import (
-    URL,
-    AsyncRequest,
-    AsyncResponse,
-    Headers,
-    HeaderTypes,
-    Origin,
-    URLTypes,
-)
+from ..models import URL, Headers, HeaderTypes, Origin, Request, Response, URLTypes
 from ..utils import get_logger
 from .connection import HTTPConnection
 from .connection_pool import ConnectionPool
@@ -122,7 +114,7 @@ class HTTPProxy(ConnectionPool):
         """Creates an HTTPConnection by setting up a TCP tunnel"""
         proxy_headers = self.proxy_headers.copy()
         proxy_headers.setdefault("Accept", "*/*")
-        proxy_request = AsyncRequest(
+        proxy_request = Request(
             method="CONNECT", url=self.proxy_url.copy_with(), headers=proxy_headers
         )
         proxy_request.url.full_path = f"{origin.host}:{origin.port}"
@@ -225,11 +217,11 @@ class HTTPProxy(ConnectionPool):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
 
         if self.should_forward_origin(request.url.origin):
             # Change the request to have the target URL
diff --git a/httpx/dispatch/threaded.py b/httpx/dispatch/threaded.py
deleted file mode 100644 (file)
index 7454a9e..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-from ..concurrency.base import ConcurrencyBackend
-from ..config import CertTypes, TimeoutTypes, VerifyTypes
-from ..models import (
-    AsyncRequest,
-    AsyncRequestData,
-    AsyncResponse,
-    AsyncResponseContent,
-    Request,
-    RequestData,
-    Response,
-    ResponseContent,
-)
-from .base import AsyncDispatcher, Dispatcher
-
-
-class ThreadedDispatcher(AsyncDispatcher):
-    """
-    The ThreadedDispatcher class is used to mediate between the Client
-    (which always uses async under the hood), and a synchronous `Dispatch`
-    class.
-    """
-
-    def __init__(self, dispatch: Dispatcher, backend: ConcurrencyBackend) -> None:
-        self.sync_dispatcher = dispatch
-        self.backend = backend
-
-    async def send(
-        self,
-        request: AsyncRequest,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
-        concurrency_backend = self.backend
-
-        data = getattr(request, "content", getattr(request, "content_aiter", None))
-        sync_data = self._sync_request_data(data)
-
-        sync_request = Request(
-            method=request.method,
-            url=request.url,
-            headers=request.headers,
-            data=sync_data,
-        )
-
-        func = self.sync_dispatcher.send
-        kwargs = {
-            "request": sync_request,
-            "verify": verify,
-            "cert": cert,
-            "timeout": timeout,
-        }
-        sync_response = await self.backend.run_in_threadpool(func, **kwargs)
-        assert isinstance(sync_response, Response)
-
-        content = getattr(
-            sync_response, "_raw_content", getattr(sync_response, "_raw_stream", None)
-        )
-
-        async_content = self._async_response_content(content)
-
-        async def async_on_close() -> None:
-            nonlocal concurrency_backend, sync_response
-            await concurrency_backend.run_in_threadpool(sync_response.close)
-
-        return AsyncResponse(
-            status_code=sync_response.status_code,
-            http_version=sync_response.http_version,
-            headers=sync_response.headers,
-            content=async_content,
-            on_close=async_on_close,
-            request=request,
-            history=sync_response.history,
-        )
-
-    async def close(self) -> None:
-        """
-        The `.close()` method runs the `Dispatcher.close()` within a threadpool,
-        so as not to block the async event loop.
-        """
-        func = self.sync_dispatcher.close
-        await self.backend.run_in_threadpool(func)
-
-    def _async_response_content(self, content: ResponseContent) -> AsyncResponseContent:
-        if isinstance(content, bytes):
-            return content
-
-        # Coerce an async iterator into an iterator, with each item in the
-        # iteration run within the event loop.
-        assert hasattr(content, "__iter__")
-        return self.backend.iterate_in_threadpool(content)
-
-    def _sync_request_data(self, data: AsyncRequestData) -> RequestData:
-        if isinstance(data, bytes):
-            return data
-
-        return self.backend.iterate(data)
diff --git a/httpx/dispatch/wsgi.py b/httpx/dispatch/wsgi.py
deleted file mode 100644 (file)
index 73a6fc1..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-import io
-import typing
-
-from ..config import CertTypes, TimeoutTypes, VerifyTypes
-from ..models import Request, Response
-from .base import Dispatcher
-
-
-class WSGIDispatch(Dispatcher):
-    """
-    A custom dispatcher that handles sending requests directly to an ASGI app.
-
-    The simplest way to use this functionality is to use the `app`argument.
-    This will automatically infer if 'app' is a WSGI or an ASGI application,
-    and will setup an appropriate dispatch class:
-
-    ```
-    client = httpx.Client(app=app)
-    ```
-
-    Alternatively, you can setup the dispatch instance explicitly.
-    This allows you to include any additional configuration arguments specific
-    to the WSGIDispatch class:
-
-    ```
-    dispatch = httpx.WSGIDispatch(
-        app=app,
-        script_name="/submount",
-        remote_addr="1.2.3.4"
-    )
-    client = httpx.Client(dispatch=dispatch)
-
-
-    Arguments:
-
-    * `app` - The ASGI application.
-    * `raise_app_exceptions` - Boolean indicating if exceptions in the application
-       should be raised. Default to `True`. Can be set to `False` for use cases
-       such as testing the content of a client 500 response.
-    * `script_name` - The root path on which the ASGI application should be mounted.
-    * `remote_addr` - A string indicating the client IP of incoming requests.
-    ```
-    """
-
-    def __init__(
-        self,
-        app: typing.Callable,
-        raise_app_exceptions: bool = True,
-        script_name: str = "",
-        remote_addr: str = "127.0.0.1",
-    ) -> None:
-        self.app = app
-        self.raise_app_exceptions = raise_app_exceptions
-        self.script_name = script_name
-        self.remote_addr = remote_addr
-
-    def send(
-        self,
-        request: Request,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-    ) -> Response:
-        environ = {
-            "wsgi.version": (1, 0),
-            "wsgi.url_scheme": request.url.scheme,
-            "wsgi.input": BodyStream(request.stream()),
-            "wsgi.errors": io.BytesIO(),
-            "wsgi.multithread": True,
-            "wsgi.multiprocess": False,
-            "wsgi.run_once": False,
-            "REQUEST_METHOD": request.method,
-            "SCRIPT_NAME": self.script_name,
-            "PATH_INFO": request.url.path,
-            "QUERY_STRING": request.url.query,
-            "SERVER_NAME": request.url.host,
-            "SERVER_PORT": str(request.url.port),
-            "REMOTE_ADDR": self.remote_addr,
-        }
-        for key, value in request.headers.items():
-            key = key.upper().replace("-", "_")
-            if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
-                key = "HTTP_" + key
-            environ[key] = value
-
-        seen_status = None
-        seen_response_headers = None
-        seen_exc_info = None
-
-        def start_response(
-            status: str, response_headers: list, exc_info: typing.Any = None
-        ) -> None:
-            nonlocal seen_status, seen_response_headers, seen_exc_info
-            seen_status = status
-            seen_response_headers = response_headers
-            seen_exc_info = exc_info
-
-        result = self.app(environ, start_response)
-
-        assert seen_status is not None
-        assert seen_response_headers is not None
-        if seen_exc_info and self.raise_app_exceptions:
-            raise seen_exc_info[1]
-
-        return Response(
-            status_code=int(seen_status.split()[0]),
-            http_version="HTTP/1.1",
-            headers=seen_response_headers,
-            content=(chunk for chunk in result),
-            on_close=getattr(result, "close", None),
-        )
-
-
-class BodyStream(io.RawIOBase):
-    def __init__(self, iterator: typing.Iterator[bytes]) -> None:
-        self._iterator = iterator
-        self._buffer = b""
-        self._closed = False
-
-    def read(self, size: int = -1) -> bytes:
-        if self._closed:
-            return b""
-
-        if size == -1:
-            return self.readall()
-
-        try:
-            while len(self._buffer) < size:
-                self._buffer += next(self._iterator)
-        except StopIteration:
-            self._closed = True
-            return self._buffer
-
-        output = self._buffer[:size]
-        self._buffer = self._buffer[size:]
-        return output
-
-    def readall(self) -> bytes:
-        if self._closed:
-            raise OSError("Stream closed")  # pragma: nocover
-
-        for chunk in self._iterator:
-            self._buffer += chunk
-
-        self._closed = True
-        return self._buffer
-
-    def readinto(self, b: bytearray) -> typing.Optional[int]:  # pragma: nocover
-        output = self.read(len(b))
-        count = len(output)
-        b[:count] = output
-        return count
-
-    def write(self, b: bytes) -> int:
-        raise OSError("Operation not supported")  # pragma: nocover
-
-    def fileno(self) -> int:
-        raise OSError("Operation not supported")  # pragma: nocover
-
-    def seek(self, offset: int, whence: int = 0) -> int:
-        raise OSError("Operation not supported")  # pragma: nocover
-
-    def truncate(self, size: int = None) -> int:
-        raise OSError("Operation not supported")  # pragma: nocover
index 419a21a1a5d2a59eb61ed2055dd9efdd7bfd6095..ab6d2bfdab4e00f1857000dfce742ef077b3197a 100644 (file)
@@ -1,7 +1,7 @@
 import typing
 
 if typing.TYPE_CHECKING:
-    from .models import BaseRequest, BaseResponse  # pragma: nocover
+    from .models import Request, Response  # pragma: nocover
 
 
 class HTTPError(Exception):
@@ -10,10 +10,7 @@ class HTTPError(Exception):
     """
 
     def __init__(
-        self,
-        *args: typing.Any,
-        request: "BaseRequest" = None,
-        response: "BaseResponse" = None,
+        self, *args: typing.Any, request: "Request" = None, response: "Response" = None
     ) -> None:
         self.response = response
         self.request = request or getattr(self.response, "request", None)
index 4ed76ea45f1e0293f727e0332bc6dc01e5886f2a..e87176ce70f85679e02aa897f54e18e74fbc7b5c 100644 (file)
@@ -1,10 +1,10 @@
 import typing
 
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 
 
 class BaseMiddleware:
     async def __call__(
-        self, request: AsyncRequest, get_response: typing.Callable
-    ) -> AsyncResponse:
+        self, request: Request, get_response: typing.Callable
+    ) -> Response:
         raise NotImplementedError  # pragma: no cover
index faffba2fd17cb5e2052f4211277f60f34dcb62fa..cb945f145f466a87d38c0ab7d8b6ef6879197bb5 100644 (file)
@@ -1,7 +1,7 @@
 import typing
 from base64 import b64encode
 
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 from ..utils import to_bytes
 from .base import BaseMiddleware
 
@@ -13,8 +13,8 @@ class BasicAuthMiddleware(BaseMiddleware):
         self.authorization_header = build_basic_auth_header(username, password)
 
     async def __call__(
-        self, request: AsyncRequest, get_response: typing.Callable
-    ) -> AsyncResponse:
+        self, request: Request, get_response: typing.Callable
+    ) -> Response:
         request.headers["Authorization"] = self.authorization_header
         return await get_response(request)
 
index 86548ddc353deefa752fa819ce0830ca65b5971a..2fd5e22674e67e4bf7e67cb7f219552d205e23db 100644 (file)
@@ -1,15 +1,15 @@
 import typing
 
-from ..models import AsyncRequest, AsyncResponse
+from ..models import Request, Response
 from .base import BaseMiddleware
 
 
 class CustomAuthMiddleware(BaseMiddleware):
-    def __init__(self, auth: typing.Callable[[AsyncRequest], AsyncRequest]):
+    def __init__(self, auth: typing.Callable[[Request], Request]):
         self.auth = auth
 
     async def __call__(
-        self, request: AsyncRequest, get_response: typing.Callable
-    ) -> AsyncResponse:
+        self, request: Request, get_response: typing.Callable
+    ) -> Response:
         request = self.auth(request)
         return await get_response(request)
index fb139736ea0e75568715677b7f38825e8979e263..ab1a1d775aa8e29dafecf347a5dab89f0661753a 100644 (file)
@@ -6,7 +6,7 @@ import typing
 from urllib.request import parse_http_list
 
 from ..exceptions import ProtocolError
-from ..models import AsyncRequest, AsyncResponse, StatusCode
+from ..models import Request, Response, StatusCode
 from ..utils import to_bytes, to_str, unquote
 from .base import BaseMiddleware
 
@@ -30,8 +30,8 @@ class DigestAuth(BaseMiddleware):
         self.password = to_bytes(password)
 
     async def __call__(
-        self, request: AsyncRequest, get_response: typing.Callable
-    ) -> AsyncResponse:
+        self, request: Request, get_response: typing.Callable
+    ) -> Response:
         response = await get_response(request)
         if not (
             StatusCode.is_client_error(response.status_code)
@@ -49,7 +49,7 @@ class DigestAuth(BaseMiddleware):
         return await get_response(request)
 
     def _build_auth_header(
-        self, request: AsyncRequest, challenge: "DigestAuthChallenge"
+        self, request: Request, challenge: "DigestAuthChallenge"
     ) -> str:
         hash_func = self.ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm]
 
index e093bf409f6b53b41eeaf8fcc11c68794364967c..2e0ca3344c1b99ef65f98f819a650ad62279f876 100644 (file)
@@ -3,7 +3,7 @@ import typing
 
 from ..config import DEFAULT_MAX_REDIRECTS
 from ..exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects
-from ..models import URL, AsyncRequest, AsyncResponse, Cookies, Headers
+from ..models import URL, Cookies, Headers, Request, Response
 from ..status_codes import codes
 from .base import BaseMiddleware
 
@@ -18,11 +18,11 @@ class RedirectMiddleware(BaseMiddleware):
         self.allow_redirects = allow_redirects
         self.max_redirects = max_redirects
         self.cookies = cookies
-        self.history: typing.List[AsyncResponse] = []
+        self.history: typing.List[Response] = []
 
     async def __call__(
-        self, request: AsyncRequest, get_response: typing.Callable
-    ) -> AsyncResponse:
+        self, request: Request, get_response: typing.Callable
+    ) -> Response:
         if len(self.history) > self.max_redirects:
             raise TooManyRedirects()
         if request.url in (response.url for response in self.history):
@@ -43,19 +43,17 @@ class RedirectMiddleware(BaseMiddleware):
         response.call_next = functools.partial(self, next_request, get_response)
         return response
 
-    def build_redirect_request(
-        self, request: AsyncRequest, response: AsyncResponse
-    ) -> AsyncRequest:
+    def build_redirect_request(self, request: Request, response: Response) -> Request:
         method = self.redirect_method(request, response)
         url = self.redirect_url(request, response)
         headers = self.redirect_headers(request, url, method)  # TODO: merge headers?
         content = self.redirect_content(request, method)
         cookies = Cookies(self.cookies)
-        return AsyncRequest(
+        return Request(
             method=method, url=url, headers=headers, data=content, cookies=cookies
         )
 
-    def redirect_method(self, request: AsyncRequest, response: AsyncResponse) -> str:
+    def redirect_method(self, request: Request, response: Response) -> str:
         """
         When being redirected we may want to change the method of the request
         based on certain specs or browser behavior.
@@ -78,7 +76,7 @@ class RedirectMiddleware(BaseMiddleware):
 
         return method
 
-    def redirect_url(self, request: AsyncRequest, response: AsyncResponse) -> URL:
+    def redirect_url(self, request: Request, response: Response) -> URL:
         """
         Return the URL for the redirect to follow.
         """
@@ -97,7 +95,7 @@ class RedirectMiddleware(BaseMiddleware):
 
         return url
 
-    def redirect_headers(self, request: AsyncRequest, url: URL, method: str) -> Headers:
+    def redirect_headers(self, request: Request, url: URL, method: str) -> Headers:
         """
         Return the headers that should be used for the redirect request.
         """
@@ -121,7 +119,7 @@ class RedirectMiddleware(BaseMiddleware):
 
         return headers
 
-    def redirect_content(self, request: AsyncRequest, method: str) -> bytes:
+    def redirect_content(self, request: Request, method: str) -> bytes:
         """
         Return the body that should be used for the redirect request.
         """
index 136aa41c26338511eb0895146e3fa8259f4415b3..0b14734d2868ee86cb4a42f11a1be2616ecc0155 100644 (file)
@@ -44,7 +44,7 @@ from .utils import (
 
 if typing.TYPE_CHECKING:  # pragma: no cover
     from .middleware.base import BaseMiddleware  # noqa: F401
-    from .dispatch.base import AsyncDispatcher  # noqa: F401
+    from .dispatch.base import Dispatcher  # noqa: F401
 
 PrimitiveData = typing.Optional[typing.Union[str, int, float, bool]]
 
@@ -67,19 +67,15 @@ CookieTypes = typing.Union["Cookies", CookieJar, typing.Dict[str, str]]
 
 AuthTypes = typing.Union[
     typing.Tuple[typing.Union[str, bytes], typing.Union[str, bytes]],
-    typing.Callable[["AsyncRequest"], "AsyncRequest"],
+    typing.Callable[["Request"], "Request"],
     "BaseMiddleware",
 ]
 
 ProxiesTypes = typing.Union[
-    URLTypes,
-    "AsyncDispatcher",
-    typing.Dict[URLTypes, typing.Union[URLTypes, "AsyncDispatcher"]],
+    URLTypes, "Dispatcher", typing.Dict[URLTypes, typing.Union[URLTypes, "Dispatcher"]]
 ]
 
-AsyncRequestData = typing.Union[dict, str, bytes, typing.AsyncIterator[bytes]]
-
-RequestData = typing.Union[dict, str, bytes, typing.Iterator[bytes]]
+RequestData = typing.Union[dict, str, bytes, typing.AsyncIterator[bytes]]
 
 RequestFiles = typing.Dict[
     str,
@@ -92,9 +88,7 @@ RequestFiles = typing.Dict[
     ],
 ]
 
-AsyncResponseContent = typing.Union[bytes, typing.AsyncIterator[bytes]]
-
-ResponseContent = typing.Union[bytes, typing.Iterator[bytes]]
+ResponseContent = typing.Union[bytes, typing.AsyncIterator[bytes]]
 
 
 class URL:
@@ -592,7 +586,7 @@ class Headers(typing.MutableMapping[str, str]):
         return f"{class_name}({as_list!r}{encoding_str})"
 
 
-class BaseRequest:
+class Request:
     def __init__(
         self,
         method: str,
@@ -601,6 +595,9 @@ class BaseRequest:
         params: QueryParamTypes = None,
         headers: HeaderTypes = None,
         cookies: CookieTypes = None,
+        data: RequestData = None,
+        files: RequestFiles = None,
+        json: typing.Any = None,
     ):
         self.method = method.upper()
         self.url = URL(url, params=params)
@@ -608,6 +605,21 @@ class BaseRequest:
         if cookies:
             self._cookies = Cookies(cookies)
             self._cookies.set_cookie_header(self)
+        if data is None or isinstance(data, dict):
+            content, content_type = self.encode_data(data, files, json)
+            self.is_streaming = False
+            self.content = content
+            if content_type:
+                self.headers["Content-Type"] = content_type
+        elif isinstance(data, (str, bytes)):
+            data = data.encode("utf-8") if isinstance(data, str) else data
+            self.is_streaming = False
+            self.content = data
+        else:
+            assert hasattr(data, "__aiter__")
+            self.is_streaming = True
+            self.content_aiter = data
+        self.prepare()
 
     def encode_data(
         self, data: dict = None, files: RequestFiles = None, json: typing.Any = None
@@ -674,40 +686,6 @@ class BaseRequest:
         url = str(self.url)
         return f"<{class_name}({self.method!r}, {url!r})>"
 
-
-class AsyncRequest(BaseRequest):
-    def __init__(
-        self,
-        method: str,
-        url: typing.Union[str, URL],
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        data: AsyncRequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-    ):
-        super().__init__(
-            method=method, url=url, params=params, headers=headers, cookies=cookies
-        )
-
-        if data is None or isinstance(data, dict):
-            content, content_type = self.encode_data(data, files, json)
-            self.is_streaming = False
-            self.content = content
-            if content_type:
-                self.headers["Content-Type"] = content_type
-        elif isinstance(data, (str, bytes)):
-            data = data.encode("utf-8") if isinstance(data, str) else data
-            self.is_streaming = False
-            self.content = data
-        else:
-            assert hasattr(data, "__aiter__")
-            self.is_streaming = True
-            self.content_aiter = data
-        self.prepare()
-
     async def read(self) -> bytes:
         """
         Read and return the response content.
@@ -724,62 +702,17 @@ class AsyncRequest(BaseRequest):
             yield self.content
 
 
-class Request(BaseRequest):
-    def __init__(
-        self,
-        method: str,
-        url: typing.Union[str, URL],
-        *,
-        params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        cookies: CookieTypes = None,
-        data: RequestData = None,
-        files: RequestFiles = None,
-        json: typing.Any = None,
-    ):
-        super().__init__(
-            method=method, url=url, params=params, headers=headers, cookies=cookies
-        )
-
-        if data is None or isinstance(data, dict):
-            content, content_type = self.encode_data(data, files, json)
-            self.is_streaming = False
-            self.content = content
-            if content_type:
-                self.headers["Content-Type"] = content_type
-        elif isinstance(data, (str, bytes)):
-            data = data.encode("utf-8") if isinstance(data, str) else data
-            self.is_streaming = False
-            self.content = data
-        else:
-            assert hasattr(data, "__iter__")
-            self.is_streaming = True
-            self.content_iter = data
-
-        self.prepare()
-
-    def read(self) -> bytes:
-        if not hasattr(self, "content"):
-            self.content = b"".join([part for part in self.stream()])
-        return self.content
-
-    def stream(self) -> typing.Iterator[bytes]:
-        if self.is_streaming:
-            for part in self.content_iter:
-                yield part
-        elif self.content:
-            yield self.content
-
-
-class BaseResponse:
+class Response:
     def __init__(
         self,
         status_code: int,
         *,
         http_version: str = None,
         headers: HeaderTypes = None,
-        request: BaseRequest = None,
+        content: ResponseContent = None,
         on_close: typing.Callable = None,
+        request: Request = None,
+        history: typing.List["Response"] = None,
         elapsed: datetime.timedelta = None,
     ):
         self.status_code = status_code
@@ -791,6 +724,17 @@ class BaseResponse:
         self.elapsed = datetime.timedelta(0) if elapsed is None else elapsed
         self.call_next: typing.Optional[typing.Callable] = None
 
+        self.history = [] if history is None else list(history)
+
+        if content is None or isinstance(content, bytes):
+            self.is_closed = True
+            self.is_stream_consumed = True
+            self._raw_content = content or b""
+        else:
+            self.is_closed = False
+            self.is_stream_consumed = False
+            self._raw_stream = content
+
     @property
     def reason_phrase(self) -> str:
         return StatusCode.get_reason_phrase(self.status_code)
@@ -954,40 +898,6 @@ class BaseResponse:
     def __repr__(self) -> str:
         return f"<Response [{self.status_code} {self.reason_phrase}]>"
 
-
-class AsyncResponse(BaseResponse):
-    def __init__(
-        self,
-        status_code: int,
-        *,
-        http_version: str = None,
-        headers: HeaderTypes = None,
-        content: AsyncResponseContent = None,
-        on_close: typing.Callable = None,
-        request: AsyncRequest = None,
-        history: typing.List["BaseResponse"] = None,
-        elapsed: datetime.timedelta = None,
-    ):
-        super().__init__(
-            status_code=status_code,
-            http_version=http_version,
-            headers=headers,
-            request=request,
-            on_close=on_close,
-            elapsed=elapsed,
-        )
-
-        self.history = [] if history is None else list(history)
-
-        if content is None or isinstance(content, bytes):
-            self.is_closed = True
-            self.is_stream_consumed = True
-            self._raw_content = content or b""
-        else:
-            self.is_closed = False
-            self.is_stream_consumed = False
-            self._raw_stream = content
-
     async def read(self) -> bytes:
         """
         Read and return the response content.
@@ -1036,7 +946,7 @@ class AsyncResponse(BaseResponse):
                 yield part
             await self.close()
 
-    async def next(self) -> "AsyncResponse":
+    async def next(self) -> "Response":
         """
         Get the next response from a redirect response.
         """
@@ -1056,98 +966,6 @@ class AsyncResponse(BaseResponse):
                 await self.on_close()
 
 
-class Response(BaseResponse):
-    def __init__(
-        self,
-        status_code: int,
-        *,
-        http_version: str = None,
-        headers: HeaderTypes = None,
-        content: ResponseContent = None,
-        on_close: typing.Callable = None,
-        request: Request = None,
-        history: typing.List["BaseResponse"] = None,
-        elapsed: datetime.timedelta = None,
-    ):
-        super().__init__(
-            status_code=status_code,
-            http_version=http_version,
-            headers=headers,
-            request=request,
-            on_close=on_close,
-            elapsed=elapsed,
-        )
-
-        self.history = [] if history is None else list(history)
-
-        if content is None or isinstance(content, bytes):
-            self.is_closed = True
-            self.is_stream_consumed = True
-            self._raw_content = content or b""
-        else:
-            self.is_closed = False
-            self.is_stream_consumed = False
-            self._raw_stream = content
-
-    def read(self) -> bytes:
-        """
-        Read and return the response content.
-        """
-        if not hasattr(self, "_content"):
-            self._content = b"".join([part for part in self.stream()])
-        return self._content
-
-    def stream(self) -> typing.Iterator[bytes]:
-        """
-        A byte-iterator over the decoded response content.
-        This allows us to handle gzip, deflate, and brotli encoded responses.
-        """
-        if hasattr(self, "_content"):
-            yield self._content
-        else:
-            for chunk in self.raw():
-                yield self.decoder.decode(chunk)
-            yield self.decoder.flush()
-
-    def stream_text(self) -> typing.Iterator[str]:
-        """
-        A str-iterator over the decoded response content
-        that handles both gzip, deflate, etc but also detects the content's
-        string encoding.
-        """
-        decoder = TextDecoder(encoding=self.charset_encoding)
-        for chunk in self.stream():
-            yield decoder.decode(chunk)
-        yield decoder.flush()
-
-    def raw(self) -> typing.Iterator[bytes]:
-        """
-        A byte-iterator over the raw response content.
-        """
-        if hasattr(self, "_raw_content"):
-            yield self._raw_content
-        else:
-            if self.is_stream_consumed:
-                raise StreamConsumed()
-            if self.is_closed:
-                raise ResponseClosed()
-
-            self.is_stream_consumed = True
-            for part in self._raw_stream:
-                yield part
-            self.close()
-
-    def close(self) -> None:
-        """
-        Close the response and release the connection.
-        Automatically called if the response body is read to completion.
-        """
-        if not self.is_closed:
-            self.is_closed = True
-            if self.on_close is not None:
-                self.on_close()
-
-
 class Cookies(MutableMapping):
     """
     HTTP Cookies, as a mutable mapping.
@@ -1166,7 +984,7 @@ class Cookies(MutableMapping):
         else:
             self.jar = cookies
 
-    def extract_cookies(self, response: BaseResponse) -> None:
+    def extract_cookies(self, response: Response) -> None:
         """
         Loads any cookies based on the response `Set-Cookie` headers.
         """
@@ -1176,7 +994,7 @@ class Cookies(MutableMapping):
 
         self.jar.extract_cookies(urlib_response, urllib_request)  # type: ignore
 
-    def set_cookie_header(self, request: BaseRequest) -> None:
+    def set_cookie_header(self, request: Request) -> None:
         """
         Sets an appropriate 'Cookie:' HTTP header on the `Request`.
         """
@@ -1295,7 +1113,7 @@ class Cookies(MutableMapping):
         for use with `CookieJar` operations.
         """
 
-        def __init__(self, request: BaseRequest) -> None:
+        def __init__(self, request: Request) -> None:
             super().__init__(
                 url=str(request.url),
                 headers=dict(request.headers),
@@ -1313,7 +1131,7 @@ class Cookies(MutableMapping):
         for use with `CookieJar` operations.
         """
 
-        def __init__(self, response: BaseResponse):
+        def __init__(self, response: Response):
             self.response = response
 
         def info(self) -> email.message.Message:
index 1e16651a93dae1296d49e41c370d165dd202bcd3..0ddd8c85b6c6c2e544b929a3e09813a1001f689e 100644 (file)
@@ -13,8 +13,6 @@ nav:
     - QuickStart: 'quickstart.md'
     - Advanced Usage: 'advanced.md'
     - Environment Variables: 'environment_variables.md'
-    - Parallel Requests: 'parallel.md'
-    - Async Client: 'async.md'
     - Requests Compatibility: 'compatibility.md'
     - Developer Interface: 'api.md'
     - Contributing: 'contributing.md'
index 42202aa180d4738c6f91b054f9e5aee00fa0620f..69563cbf161f2cedb67348587ed3badf5f7041fd 100644 (file)
@@ -7,7 +7,7 @@ import httpx
 
 async def test_get(server, backend):
     url = server.url
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.get(url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
@@ -20,7 +20,7 @@ async def test_get(server, backend):
 async def test_build_request(server, backend):
     url = server.url.copy_with(path="/echo_headers")
     headers = {"Custom-header": "value"}
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         request = client.build_request("GET", url)
         request.headers.update(headers)
         response = await client.send(request)
@@ -37,7 +37,7 @@ async def test_get_no_backend(server):
     Verify that the client is capable of making a simple request if not given a backend.
     """
     url = server.url
-    async with httpx.AsyncClient() as client:
+    async with httpx.Client() as client:
         response = await client.get(url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
@@ -48,20 +48,20 @@ async def test_get_no_backend(server):
 
 async def test_post(server, backend):
     url = server.url
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.post(url, data=b"Hello, world!")
     assert response.status_code == 200
 
 
 async def test_post_json(server, backend):
     url = server.url
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.post(url, json={"text": "Hello, world!"})
     assert response.status_code == 200
 
 
 async def test_stream_response(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.request("GET", server.url, stream=True)
     assert response.status_code == 200
     body = await response.read()
@@ -70,7 +70,7 @@ async def test_stream_response(server, backend):
 
 
 async def test_access_content_stream_response(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.request("GET", server.url, stream=True)
     assert response.status_code == 200
     with pytest.raises(httpx.ResponseNotRead):
@@ -82,13 +82,13 @@ async def test_stream_request(server, backend):
         yield b"Hello, "
         yield b"world!"
 
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.request("POST", server.url, data=hello_world())
     assert response.status_code == 200
 
 
 async def test_raise_for_status(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         for status_code in (200, 400, 404, 500, 505):
             response = await client.request(
                 "GET", server.url.copy_with(path=f"/status/{status_code}")
@@ -103,33 +103,33 @@ async def test_raise_for_status(server, backend):
 
 
 async def test_options(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.options(server.url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
 
 
 async def test_head(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.head(server.url)
     assert response.status_code == 200
     assert response.text == ""
 
 
 async def test_put(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.put(server.url, data=b"Hello, world!")
     assert response.status_code == 200
 
 
 async def test_patch(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.patch(server.url, data=b"Hello, world!")
     assert response.status_code == 200
 
 
 async def test_delete(server, backend):
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.delete(server.url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
@@ -139,7 +139,7 @@ async def test_100_continue(server, backend):
     headers = {"Expect": "100-continue"}
     data = b"Echo request body"
 
-    async with httpx.AsyncClient(backend=backend) as client:
+    async with httpx.Client(backend=backend) as client:
         response = await client.post(
             server.url.copy_with(path="/echo_body"), headers=headers, data=data
         )
@@ -152,7 +152,7 @@ async def test_uds(uds_server, backend):
     url = uds_server.url
     uds = uds_server.config.uds
     assert uds is not None
-    async with httpx.AsyncClient(backend=backend, uds=uds) as client:
+    async with httpx.Client(backend=backend, uds=uds) as client:
         response = await client.get(url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
index 1d5b9d5c1ffe34e2ac305a47aeddcb59d2eebd99..58999cff7252f60b50ba9f3c28da550cc907a12e 100644 (file)
@@ -6,38 +6,38 @@ import pytest
 
 from httpx import (
     URL,
-    AsyncDispatcher,
-    AsyncRequest,
-    AsyncResponse,
     CertTypes,
     Client,
     DigestAuth,
+    Dispatcher,
     ProtocolError,
+    Request,
+    Response,
     TimeoutTypes,
     VerifyTypes,
 )
 
 
-class MockDispatch(AsyncDispatcher):
+class MockDispatch(Dispatcher):
     def __init__(self, auth_header: str = "", status_code: int = 200) -> None:
         self.auth_header = auth_header
         self.status_code = status_code
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         headers = [("www-authenticate", self.auth_header)] if self.auth_header else []
         body = json.dumps({"auth": request.headers.get("Authorization")}).encode()
-        return AsyncResponse(
+        return Response(
             self.status_code, headers=headers, content=body, request=request
         )
 
 
-class MockDigestAuthDispatch(AsyncDispatcher):
+class MockDigestAuthDispatch(Dispatcher):
     def __init__(
         self,
         algorithm: str = "SHA-256",
@@ -53,18 +53,18 @@ class MockDigestAuthDispatch(AsyncDispatcher):
 
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if self._response_count < self.send_response_after_attempt:
             return self.challenge_send(request)
 
         body = json.dumps({"auth": request.headers.get("Authorization")}).encode()
-        return AsyncResponse(200, content=body, request=request)
+        return Response(200, content=body, request=request)
 
-    def challenge_send(self, request: AsyncRequest) -> AsyncResponse:
+    def challenge_send(self, request: Request) -> Response:
         self._response_count += 1
         nonce = (
             hashlib.sha256(os.urandom(8)).hexdigest()
@@ -89,61 +89,66 @@ class MockDigestAuthDispatch(AsyncDispatcher):
         headers = [
             ("www-authenticate", 'Digest realm="httpx@example.org", ' + challenge_str)
         ]
-        return AsyncResponse(401, headers=headers, content=b"", request=request)
+        return Response(401, headers=headers, content=b"", request=request)
 
 
-def test_basic_auth():
+@pytest.mark.asyncio
+async def test_basic_auth():
     url = "https://example.org/"
     auth = ("tomchristie", "password123")
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
 
 
-def test_basic_auth_in_url():
+@pytest.mark.asyncio
+async def test_basic_auth_in_url():
     url = "https://tomchristie:password123@example.org/"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
 
 
-def test_basic_auth_on_session():
+@pytest.mark.asyncio
+async def test_basic_auth_on_session():
     url = "https://example.org/"
     auth = ("tomchristie", "password123")
 
-    with Client(dispatch=MockDispatch(), auth=auth) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch(), auth=auth)
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
 
 
-def test_custom_auth():
+@pytest.mark.asyncio
+async def test_custom_auth():
     url = "https://example.org/"
 
     def auth(request):
         request.headers["Authorization"] = "Token 123"
         return request
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     assert response.json() == {"auth": "Token 123"}
 
 
-def test_netrc_auth():
+@pytest.mark.asyncio
+async def test_netrc_auth():
     os.environ["NETRC"] = "tests/.netrc"
     url = "http://netrcexample.org"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {
@@ -151,18 +156,19 @@ def test_netrc_auth():
     }
 
 
-def test_trust_env_auth():
+@pytest.mark.asyncio
+async def test_trust_env_auth():
     os.environ["NETRC"] = "tests/.netrc"
     url = "http://netrcexample.org"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, trust_env=False)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, trust_env=False)
 
     assert response.status_code == 200
     assert response.json() == {"auth": None}
 
-    with Client(dispatch=MockDispatch(), trust_env=False) as client:
-        response = client.get(url, trust_env=True)
+    client = Client(dispatch=MockDispatch(), trust_env=False)
+    response = await client.get(url, trust_env=True)
 
     assert response.status_code == 200
     assert response.json() == {
@@ -177,54 +183,57 @@ def test_auth_hidden_url():
     assert expected == repr(URL(url))
 
 
-def test_auth_hidden_header():
+@pytest.mark.asyncio
+async def test_auth_hidden_header():
     url = "https://example.org/"
     auth = ("example-username", "example-password")
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, auth=auth)
 
     assert "'authorization': '[secure]'" in str(response.request.headers)
 
 
-def test_auth_invalid_type():
+@pytest.mark.asyncio
+async def test_auth_invalid_type():
     url = "https://example.org/"
-    with Client(dispatch=MockDispatch(), auth="not a tuple, not a callable") as client:
-        with pytest.raises(TypeError):
-            client.get(url)
+    client = Client(dispatch=MockDispatch(), auth="not a tuple, not a callable")
+    with pytest.raises(TypeError):
+        await client.get(url)
 
 
-def test_digest_auth_returns_no_auth_if_no_digest_header_in_response():
+@pytest.mark.asyncio
+async def test_digest_auth_returns_no_auth_if_no_digest_header_in_response():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     assert response.json() == {"auth": None}
 
 
-def test_digest_auth_200_response_including_digest_auth_header_is_returned_as_is():
+@pytest.mark.asyncio
+async def test_digest_auth_200_response_including_digest_auth_header():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
     auth_header = 'Digest realm="realm@host.com",qop="auth",nonce="abc",opaque="xyz"'
 
-    with Client(
-        dispatch=MockDispatch(auth_header=auth_header, status_code=200)
-    ) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch(auth_header=auth_header, status_code=200))
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     assert response.json() == {"auth": None}
 
 
-def test_digest_auth_401_response_without_digest_auth_header_is_returned_as_is():
+@pytest.mark.asyncio
+async def test_digest_auth_401_response_without_digest_auth_header():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(dispatch=MockDispatch(auth_header="", status_code=401)) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDispatch(auth_header="", status_code=401))
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 401
     assert response.json() == {"auth": None}
@@ -243,12 +252,13 @@ def test_digest_auth_401_response_without_digest_auth_header_is_returned_as_is()
         ("SHA-512-SESS", 64, 128),
     ],
 )
-def test_digest_auth(algorithm, expected_hash_length, expected_response_length):
+@pytest.mark.asyncio
+async def test_digest_auth(algorithm, expected_hash_length, expected_response_length):
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(dispatch=MockDigestAuthDispatch(algorithm=algorithm)) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDigestAuthDispatch(algorithm=algorithm))
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     auth = response.json()["auth"]
@@ -269,12 +279,13 @@ def test_digest_auth(algorithm, expected_hash_length, expected_response_length):
     assert len(digest_data["cnonce"]) == 16 + 2
 
 
-def test_digest_auth_no_specified_qop():
+@pytest.mark.asyncio
+async def test_digest_auth_no_specified_qop():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(dispatch=MockDigestAuthDispatch(qop=None)) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDigestAuthDispatch(qop=None))
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 200
     auth = response.json()["auth"]
@@ -296,40 +307,42 @@ def test_digest_auth_no_specified_qop():
 
 
 @pytest.mark.parametrize("qop", ("auth, auth-int", "auth,auth-int", "unknown,auth"))
-def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str):
+@pytest.mark.asyncio
+async def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str):
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(dispatch=MockDigestAuthDispatch(qop=qop)) as client:
-        client.get(url, auth=auth)
+    client = Client(dispatch=MockDigestAuthDispatch(qop=qop))
+    await client.get(url, auth=auth)
 
 
-def test_digest_auth_qop_auth_int_not_implemented():
+@pytest.mark.asyncio
+async def test_digest_auth_qop_auth_int_not_implemented():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
+    client = Client(dispatch=MockDigestAuthDispatch(qop="auth-int"))
 
     with pytest.raises(NotImplementedError):
-        with Client(dispatch=MockDigestAuthDispatch(qop="auth-int")) as client:
-            client.get(url, auth=auth)
+        await client.get(url, auth=auth)
 
 
-def test_digest_auth_qop_must_be_auth_or_auth_int():
+@pytest.mark.asyncio
+async def test_digest_auth_qop_must_be_auth_or_auth_int():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
+    client = Client(dispatch=MockDigestAuthDispatch(qop="not-auth"))
 
     with pytest.raises(ProtocolError):
-        with Client(dispatch=MockDigestAuthDispatch(qop="not-auth")) as client:
-            client.get(url, auth=auth)
+        await client.get(url, auth=auth)
 
 
-def test_digest_auth_incorrect_credentials():
+@pytest.mark.asyncio
+async def test_digest_auth_incorrect_credentials():
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
 
-    with Client(
-        dispatch=MockDigestAuthDispatch(send_response_after_attempt=2)
-    ) as client:
-        response = client.get(url, auth=auth)
+    client = Client(dispatch=MockDigestAuthDispatch(send_response_after_attempt=2))
+    response = await client.get(url, auth=auth)
 
     assert response.status_code == 401
 
@@ -344,12 +357,11 @@ def test_digest_auth_incorrect_credentials():
         'Digest realm="httpx@example.org", qop="auth,au',  # malformed fields list
     ],
 )
-def test_digest_auth_raises_protocol_error_on_malformed_header(auth_header: str):
+@pytest.mark.asyncio
+async def test_digest_auth_raises_protocol_error_on_malformed_header(auth_header: str):
     url = "https://example.org/"
     auth = DigestAuth(username="tomchristie", password="password123")
+    client = Client(dispatch=MockDispatch(auth_header=auth_header, status_code=401))
 
     with pytest.raises(ProtocolError):
-        with Client(
-            dispatch=MockDispatch(auth_header=auth_header, status_code=401)
-        ) as client:
-            client.get(url, auth=auth)
+        await client.get(url, auth=auth)
index 5dc196d933cc67f45005cb6d33f31122a0b652e1..5514f6d67a1c713ef1c3a4d25c01bf08d6e32be4 100644 (file)
@@ -6,10 +6,11 @@ import pytest
 import httpx
 
 
-def test_get(server):
+@pytest.mark.asyncio
+async def test_get(server):
     url = server.url
-    with httpx.Client() as http:
-        response = http.get(url)
+    async with httpx.Client() as http:
+        response = await http.get(url)
     assert response.status_code == 200
     assert response.url == url
     assert response.content == b"Hello, world!"
@@ -23,14 +24,15 @@ def test_get(server):
     assert response.elapsed > timedelta(0)
 
 
-def test_build_request(server):
+@pytest.mark.asyncio
+async def test_build_request(server):
     url = server.url.copy_with(path="/echo_headers")
     headers = {"Custom-header": "value"}
 
-    with httpx.Client() as http:
-        request = http.build_request("GET", url)
+    async with httpx.Client() as client:
+        request = client.build_request("GET", url)
         request.headers.update(headers)
-        response = http.send(request)
+        response = await client.send(request)
 
     assert response.status_code == 200
     assert response.url == url
@@ -38,53 +40,59 @@ def test_build_request(server):
     assert response.json()["Custom-header"] == "value"
 
 
-def test_post(server):
-    with httpx.Client() as http:
-        response = http.post(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_post(server):
+    async with httpx.Client() as client:
+        response = await client.post(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_post_json(server):
-    with httpx.Client() as http:
-        response = http.post(server.url, json={"text": "Hello, world!"})
+@pytest.mark.asyncio
+async def test_post_json(server):
+    async with httpx.Client() as client:
+        response = await client.post(server.url, json={"text": "Hello, world!"})
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_stream_response(server):
-    with httpx.Client() as http:
-        response = http.get(server.url, stream=True)
+@pytest.mark.asyncio
+async def test_stream_response(server):
+    async with httpx.Client() as client:
+        response = await client.get(server.url, stream=True)
     assert response.status_code == 200
-    content = response.read()
+    content = await response.read()
     assert content == b"Hello, world!"
 
 
-def test_stream_iterator(server):
-    with httpx.Client() as http:
-        response = http.get(server.url, stream=True)
+@pytest.mark.asyncio
+async def test_stream_iterator(server):
+    async with httpx.Client() as client:
+        response = await client.get(server.url, stream=True)
     assert response.status_code == 200
     body = b""
-    for chunk in response.stream():
+    async for chunk in response.stream():
         body += chunk
     assert body == b"Hello, world!"
 
 
-def test_raw_iterator(server):
-    with httpx.Client() as http:
-        response = http.get(server.url, stream=True)
+@pytest.mark.asyncio
+async def test_raw_iterator(server):
+    async with httpx.Client() as client:
+        response = await client.get(server.url, stream=True)
     assert response.status_code == 200
     body = b""
-    for chunk in response.raw():
+    async for chunk in response.raw():
         body += chunk
     assert body == b"Hello, world!"
-    response.close()  # TODO: should Response be available as context managers?
+    await response.close()
 
 
-def test_raise_for_status(server):
-    with httpx.Client() as client:
+@pytest.mark.asyncio
+async def test_raise_for_status(server):
+    async with httpx.Client() as client:
         for status_code in (200, 400, 404, 500, 505):
-            response = client.request(
+            response = await client.request(
                 "GET", server.url.copy_with(path="/status/{}".format(status_code))
             )
             if 400 <= status_code < 600:
@@ -95,55 +103,62 @@ def test_raise_for_status(server):
                 assert response.raise_for_status() is None
 
 
-def test_options(server):
-    with httpx.Client() as http:
-        response = http.options(server.url)
+@pytest.mark.asyncio
+async def test_options(server):
+    async with httpx.Client() as client:
+        response = await client.options(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_head(server):
-    with httpx.Client() as http:
-        response = http.head(server.url)
+@pytest.mark.asyncio
+async def test_head(server):
+    async with httpx.Client() as client:
+        response = await client.head(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_put(server):
-    with httpx.Client() as http:
-        response = http.put(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_put(server):
+    async with httpx.Client() as client:
+        response = await client.put(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_patch(server):
-    with httpx.Client() as http:
-        response = http.patch(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_patch(server):
+    async with httpx.Client() as client:
+        response = await client.patch(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_delete(server):
-    with httpx.Client() as http:
-        response = http.delete(server.url)
+@pytest.mark.asyncio
+async def test_delete(server):
+    async with httpx.Client() as client:
+        response = await client.delete(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_base_url(server):
+@pytest.mark.asyncio
+async def test_base_url(server):
     base_url = server.url
-    with httpx.Client(base_url=base_url) as http:
-        response = http.get("/")
+    async with httpx.Client(base_url=base_url) as client:
+        response = await client.get("/")
     assert response.status_code == 200
     assert response.url == base_url
 
 
-def test_uds(uds_server):
+@pytest.mark.asyncio
+async def test_uds(uds_server):
     url = uds_server.url
     uds = uds_server.config.uds
     assert uds is not None
-    with httpx.Client(uds=uds) as http:
-        response = http.get(url)
+    async with httpx.Client(uds=uds) as client:
+        response = await client.get(url)
     assert response.status_code == 200
     assert response.text == "Hello, world!"
     assert response.encoding == "iso-8859-1"
@@ -157,33 +172,19 @@ def test_merge_url():
     assert url.is_ssl
 
 
-class DerivedFromAsyncioBackend(httpx.AsyncioBackend):
-    pass
-
-
-class AnyBackend:
-    pass
-
-
-def test_client_backend_must_be_asyncio_based():
-    httpx.Client(backend=httpx.AsyncioBackend())
-    httpx.Client(backend=DerivedFromAsyncioBackend())
-
-    with pytest.raises(ValueError):
-        httpx.Client(backend=AnyBackend())
-
-
-def test_elapsed_delay(server):
-    with httpx.Client() as http:
-        response = http.get(server.url.copy_with(path="/slow_response/100"))
+@pytest.mark.asyncio
+async def test_elapsed_delay(server):
+    async with httpx.Client() as client:
+        response = await client.get(server.url.copy_with(path="/slow_response/100"))
     assert response.elapsed.total_seconds() == pytest.approx(0.1, rel=0.2)
 
 
-def test_elapsed_delay_ignores_read_time(server):
-    with httpx.Client() as http:
-        response = http.get(
+@pytest.mark.asyncio
+async def test_elapsed_delay_ignores_read_time(server):
+    async with httpx.Client() as client:
+        response = await client.get(
             server.url.copy_with(path="/slow_response/100"), stream=True
         )
     sleep(0.2)
-    response.read()
+    await response.read()
     assert response.elapsed.total_seconds() == pytest.approx(0.1, rel=0.2)
index f6a7e20789fb299ff03df9853a975c67762aeea7..b20631dfbb0d788a173331fb9f1e52844ce7994f 100644 (file)
@@ -1,49 +1,53 @@
 import json
 from http.cookiejar import Cookie, CookieJar
 
+import pytest
+
 from httpx import (
-    AsyncDispatcher,
-    AsyncRequest,
-    AsyncResponse,
     CertTypes,
     Client,
     Cookies,
+    Dispatcher,
+    Request,
+    Response,
     TimeoutTypes,
     VerifyTypes,
 )
 
 
-class MockDispatch(AsyncDispatcher):
+class MockDispatch(Dispatcher):
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.path.startswith("/echo_cookies"):
             body = json.dumps({"cookies": request.headers.get("Cookie")}).encode()
-            return AsyncResponse(200, content=body, request=request)
+            return Response(200, content=body, request=request)
         elif request.url.path.startswith("/set_cookie"):
             headers = {"set-cookie": "example-name=example-value"}
-            return AsyncResponse(200, headers=headers, request=request)
+            return Response(200, headers=headers, request=request)
 
 
-def test_set_cookie():
+@pytest.mark.asyncio
+async def test_set_cookie():
     """
     Send a request including a cookie.
     """
     url = "http://example.org/echo_cookies"
     cookies = {"example-name": "example-value"}
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, cookies=cookies)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, cookies=cookies)
 
     assert response.status_code == 200
     assert response.json() == {"cookies": "example-name=example-value"}
 
 
-def test_set_cookie_with_cookiejar():
+@pytest.mark.asyncio
+async def test_set_cookie_with_cookiejar():
     """
     Send a request including a cookie, using a `CookieJar` instance.
     """
@@ -71,14 +75,15 @@ def test_set_cookie_with_cookiejar():
     )
     cookies.set_cookie(cookie)
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, cookies=cookies)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, cookies=cookies)
 
     assert response.status_code == 200
     assert response.json() == {"cookies": "example-name=example-value"}
 
 
-def test_setting_client_cookies_to_cookiejar():
+@pytest.mark.asyncio
+async def test_setting_client_cookies_to_cookiejar():
     """
     Send a request including a cookie, using a `CookieJar` instance.
     """
@@ -106,15 +111,16 @@ def test_setting_client_cookies_to_cookiejar():
     )
     cookies.set_cookie(cookie)
 
-    with Client(dispatch=MockDispatch()) as client:
-        client.cookies = cookies
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    client.cookies = cookies
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {"cookies": "example-name=example-value"}
 
 
-def test_set_cookie_with_cookies_model():
+@pytest.mark.asyncio
+async def test_set_cookie_with_cookies_model():
     """
     Send a request including a cookie, using a `Cookies` instance.
     """
@@ -123,38 +129,41 @@ def test_set_cookie_with_cookies_model():
     cookies = Cookies()
     cookies["example-name"] = "example-value"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url, cookies=cookies)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url, cookies=cookies)
 
     assert response.status_code == 200
     assert response.json() == {"cookies": "example-name=example-value"}
 
 
-def test_get_cookie():
+@pytest.mark.asyncio
+async def test_get_cookie():
     url = "http://example.org/set_cookie"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.cookies["example-name"] == "example-value"
     assert client.cookies["example-name"] == "example-value"
 
 
-def test_cookie_persistence():
+@pytest.mark.asyncio
+async def test_cookie_persistence():
     """
     Ensure that Client instances persist cookies between requests.
     """
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get("http://example.org/echo_cookies")
-        assert response.status_code == 200
-        assert response.json() == {"cookies": None}
-
-        response = client.get("http://example.org/set_cookie")
-        assert response.status_code == 200
-        assert response.cookies["example-name"] == "example-value"
-        assert client.cookies["example-name"] == "example-value"
-
-        response = client.get("http://example.org/echo_cookies")
-        assert response.status_code == 200
-        assert response.json() == {"cookies": "example-name=example-value"}
+    client = Client(dispatch=MockDispatch())
+
+    response = await client.get("http://example.org/echo_cookies")
+    assert response.status_code == 200
+    assert response.json() == {"cookies": None}
+
+    response = await client.get("http://example.org/set_cookie")
+    assert response.status_code == 200
+    assert response.cookies["example-name"] == "example-value"
+    assert client.cookies["example-name"] == "example-value"
+
+    response = await client.get("http://example.org/echo_cookies")
+    assert response.status_code == 200
+    assert response.json() == {"cookies": "example-name=example-value"}
index a8c5445bcde9a3d2c24b0a28dcc78aaba32dd899..a7921c0579d1c1194f8e5487745d123da980ec1c 100755 (executable)
@@ -5,11 +5,11 @@ import json
 import pytest
 
 from httpx import (
-    AsyncDispatcher,
-    AsyncRequest,
-    AsyncResponse,
     CertTypes,
     Client,
+    Dispatcher,
+    Request,
+    Response,
     TimeoutTypes,
     VerifyTypes,
     __version__,
@@ -17,29 +17,30 @@ from httpx import (
 )
 
 
-class MockDispatch(AsyncDispatcher):
+class MockDispatch(Dispatcher):
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.path.startswith("/echo_headers"):
             request_headers = dict(request.headers.items())
             body = json.dumps({"headers": request_headers}).encode()
-            return AsyncResponse(200, content=body, request=request)
+            return Response(200, content=body, request=request)
 
 
-def test_client_header():
+@pytest.mark.asyncio
+async def test_client_header():
     """
     Set a header in the Client.
     """
     url = "http://example.org/echo_headers"
     headers = {"Example-Header": "example-value"}
 
-    with Client(dispatch=MockDispatch(), headers=headers) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch(), headers=headers)
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {
@@ -54,12 +55,13 @@ def test_client_header():
     }
 
 
-def test_header_merge():
+@pytest.mark.asyncio
+async def test_header_merge():
     url = "http://example.org/echo_headers"
     client_headers = {"User-Agent": "python-myclient/0.2.1"}
     request_headers = {"X-Auth-Token": "FooBarBazToken"}
-    with Client(dispatch=MockDispatch(), headers=client_headers) as client:
-        response = client.get(url, headers=request_headers)
+    client = Client(dispatch=MockDispatch(), headers=client_headers)
+    response = await client.get(url, headers=request_headers)
 
     assert response.status_code == 200
     assert response.json() == {
@@ -74,12 +76,13 @@ def test_header_merge():
     }
 
 
-def test_header_merge_conflicting_headers():
+@pytest.mark.asyncio
+async def test_header_merge_conflicting_headers():
     url = "http://example.org/echo_headers"
     client_headers = {"X-Auth-Token": "FooBar"}
     request_headers = {"X-Auth-Token": "BazToken"}
-    with Client(dispatch=MockDispatch(), headers=client_headers) as client:
-        response = client.get(url, headers=request_headers)
+    client = Client(dispatch=MockDispatch(), headers=client_headers)
+    response = await client.get(url, headers=request_headers)
 
     assert response.status_code == 200
     assert response.json() == {
@@ -94,14 +97,15 @@ def test_header_merge_conflicting_headers():
     }
 
 
-def test_header_update():
+@pytest.mark.asyncio
+async def test_header_update():
     url = "http://example.org/echo_headers"
-    with Client(dispatch=MockDispatch()) as client:
-        first_response = client.get(url)
-        client.headers.update(
-            {"User-Agent": "python-myclient/0.2.1", "Another-Header": "AThing"}
-        )
-        second_response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    first_response = await client.get(url)
+    client.headers.update(
+        {"User-Agent": "python-myclient/0.2.1", "Another-Header": "AThing"}
+    )
+    second_response = await client.get(url)
 
     assert first_response.status_code == 200
     assert first_response.json() == {
@@ -133,11 +137,12 @@ def test_header_does_not_exist():
         del headers["baz"]
 
 
-def test_host_without_auth_in_header():
+@pytest.mark.asyncio
+async def test_host_without_auth_in_header():
     url = "http://username:password@example.org:80/echo_headers"
 
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
+    client = Client(dispatch=MockDispatch())
+    response = await client.get(url)
 
     assert response.status_code == 200
     assert response.json() == {
index 32025666acabe24d40479e9196e78d232e410efc..1c02ce8c4b4d4ed5577eaa72e080a145caa16de2 100644 (file)
@@ -30,7 +30,7 @@ def test_proxies_parameter(proxies, expected_proxies):
 
 
 def test_proxies_has_same_properties_as_dispatch():
-    client = httpx.AsyncClient(
+    client = httpx.Client(
         proxies="http://127.0.0.1",
         verify="/path/to/verify",
         cert="/path/to/cert",
@@ -101,8 +101,8 @@ PROXY_URL = "http://[::1]"
     ],
 )
 def test_dispatcher_for_request(url, proxies, expected):
-    client = httpx.AsyncClient(proxies=proxies)
-    request = httpx.AsyncRequest("GET", url)
+    client = httpx.Client(proxies=proxies)
+    request = httpx.Request("GET", url)
     dispatcher = client._dispatcher_for_request(request, client.proxies)
 
     if expected is None:
@@ -115,4 +115,4 @@ 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")
+        httpx.Client(proxies="ftp://127.0.0.1")
index 852593c0d14611cea3d5e10eb8df0f54325ae9a4..8fcba512a5d5c7703de9e3579dd97744273c8a02 100644 (file)
@@ -1,29 +1,31 @@
 import json
 
+import pytest
+
 from httpx import (
-    AsyncDispatcher,
-    AsyncRequest,
-    AsyncResponse,
     CertTypes,
     Client,
+    Dispatcher,
     QueryParams,
+    Request,
+    Response,
     TimeoutTypes,
     VerifyTypes,
 )
 from httpx.models import URL
 
 
-class MockDispatch(AsyncDispatcher):
+class MockDispatch(Dispatcher):
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.path.startswith("/echo_queryparams"):
             body = json.dumps({"ok": "ok"}).encode()
-            return AsyncResponse(200, content=body, request=request)
+            return Response(200, content=body, request=request)
 
 
 def test_client_queryparams():
@@ -43,12 +45,13 @@ def test_client_queryparams_string():
     assert client.params["a"] == "b"
 
 
-def test_client_queryparams_echo():
+@pytest.mark.asyncio
+async def test_client_queryparams_echo():
     url = "http://example.org/echo_queryparams"
     client_queryparams = "first=str"
     request_queryparams = {"second": "dict"}
-    with Client(dispatch=MockDispatch(), params=client_queryparams) as client:
-        response = client.get(url, params=request_queryparams)
+    client = Client(dispatch=MockDispatch(), params=client_queryparams)
+    response = await client.get(url, params=request_queryparams)
 
     assert response.status_code == 200
     assert response.url == URL(
index b014b0f1613df0622ac483282e2f0bcaf4d1cd90..4dbf2b85aa7a974bb955d025191e02a8c890ad05 100644 (file)
@@ -5,14 +5,14 @@ import pytest
 
 from httpx import (
     URL,
-    AsyncClient,
-    AsyncDispatcher,
-    AsyncRequest,
-    AsyncResponse,
     CertTypes,
+    Client,
+    Dispatcher,
     NotRedirectResponse,
     RedirectBodyUnavailable,
     RedirectLoop,
+    Request,
+    Response,
     TimeoutTypes,
     TooManyRedirects,
     VerifyTypes,
@@ -20,39 +20,39 @@ from httpx import (
 )
 
 
-class MockDispatch(AsyncDispatcher):
+class MockDispatch(Dispatcher):
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.path == "/no_redirect":
-            return AsyncResponse(codes.OK, request=request)
+            return Response(codes.OK, request=request)
 
         elif request.url.path == "/redirect_301":
             status_code = codes.MOVED_PERMANENTLY
             headers = {"location": "https://example.org/"}
-            return AsyncResponse(status_code, headers=headers, request=request)
+            return Response(status_code, headers=headers, request=request)
 
         elif request.url.path == "/redirect_302":
             status_code = codes.FOUND
             headers = {"location": "https://example.org/"}
-            return AsyncResponse(status_code, headers=headers, request=request)
+            return Response(status_code, headers=headers, request=request)
 
         elif request.url.path == "/redirect_303":
             status_code = codes.SEE_OTHER
             headers = {"location": "https://example.org/"}
-            return AsyncResponse(status_code, headers=headers, request=request)
+            return Response(status_code, headers=headers, request=request)
 
         elif request.url.path == "/relative_redirect":
             headers = {"location": "/"}
-            return AsyncResponse(codes.SEE_OTHER, headers=headers, request=request)
+            return Response(codes.SEE_OTHER, headers=headers, request=request)
 
         elif request.url.path == "/no_scheme_redirect":
             headers = {"location": "//example.org/"}
-            return AsyncResponse(codes.SEE_OTHER, headers=headers, request=request)
+            return Response(codes.SEE_OTHER, headers=headers, request=request)
 
         elif request.url.path == "/multiple_redirects":
             params = parse_qs(request.url.query)
@@ -63,55 +63,51 @@ class MockDispatch(AsyncDispatcher):
             if redirect_count:
                 location += "?count=" + str(redirect_count)
             headers = {"location": location} if count else {}
-            return AsyncResponse(code, headers=headers, request=request)
+            return Response(code, headers=headers, request=request)
 
         if request.url.path == "/redirect_loop":
             headers = {"location": "/redirect_loop"}
-            return AsyncResponse(codes.SEE_OTHER, headers=headers, request=request)
+            return Response(codes.SEE_OTHER, headers=headers, request=request)
 
         elif request.url.path == "/cross_domain":
             headers = {"location": "https://example.org/cross_domain_target"}
-            return AsyncResponse(codes.SEE_OTHER, headers=headers, request=request)
+            return Response(codes.SEE_OTHER, headers=headers, request=request)
 
         elif request.url.path == "/cross_domain_target":
             headers = dict(request.headers.items())
             content = json.dumps({"headers": headers}).encode()
-            return AsyncResponse(codes.OK, content=content, request=request)
+            return Response(codes.OK, content=content, request=request)
 
         elif request.url.path == "/redirect_body":
             await request.read()
             headers = {"location": "/redirect_body_target"}
-            return AsyncResponse(
-                codes.PERMANENT_REDIRECT, headers=headers, request=request
-            )
+            return Response(codes.PERMANENT_REDIRECT, headers=headers, request=request)
 
         elif request.url.path == "/redirect_no_body":
             await request.read()
             headers = {"location": "/redirect_body_target"}
-            return AsyncResponse(codes.SEE_OTHER, headers=headers, request=request)
+            return Response(codes.SEE_OTHER, headers=headers, request=request)
 
         elif request.url.path == "/redirect_body_target":
             content = await request.read()
             headers = dict(request.headers.items())
             body = json.dumps({"body": content.decode(), "headers": headers}).encode()
-            return AsyncResponse(codes.OK, content=body, request=request)
+            return Response(codes.OK, content=body, request=request)
 
         elif request.url.path == "/cross_subdomain":
             if request.headers["host"] != "www.example.org":
                 headers = {"location": "https://www.example.org/cross_subdomain"}
-                return AsyncResponse(
+                return Response(
                     codes.PERMANENT_REDIRECT, headers=headers, request=request
                 )
             else:
-                return AsyncResponse(
-                    codes.OK, content=b"Hello, world!", request=request
-                )
+                return Response(codes.OK, content=b"Hello, world!", request=request)
 
-        return AsyncResponse(codes.OK, content=b"Hello, world!", request=request)
+        return Response(codes.OK, content=b"Hello, world!", request=request)
 
 
 async def test_no_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.com/no_redirect"
     response = await client.get(url)
     assert response.status_code == 200
@@ -120,7 +116,7 @@ async def test_no_redirect(backend):
 
 
 async def test_redirect_301(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.post("https://example.org/redirect_301")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/")
@@ -128,7 +124,7 @@ async def test_redirect_301(backend):
 
 
 async def test_redirect_302(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.post("https://example.org/redirect_302")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/")
@@ -136,7 +132,7 @@ async def test_redirect_302(backend):
 
 
 async def test_redirect_303(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.get("https://example.org/redirect_303")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/")
@@ -144,7 +140,7 @@ async def test_redirect_303(backend):
 
 
 async def test_disallow_redirects(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.post(
         "https://example.org/redirect_303", allow_redirects=False
     )
@@ -161,7 +157,7 @@ async def test_disallow_redirects(backend):
 
 
 async def test_relative_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.get("https://example.org/relative_redirect")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/")
@@ -169,7 +165,7 @@ async def test_relative_redirect(backend):
 
 
 async def test_no_scheme_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.get("https://example.org/no_scheme_redirect")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/")
@@ -177,7 +173,7 @@ async def test_no_scheme_redirect(backend):
 
 
 async def test_fragment_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.get("https://example.org/relative_redirect#fragment")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/#fragment")
@@ -185,7 +181,7 @@ async def test_fragment_redirect(backend):
 
 
 async def test_multiple_redirects(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     response = await client.get("https://example.org/multiple_redirects?count=20")
     assert response.status_code == codes.OK
     assert response.url == URL("https://example.org/multiple_redirects")
@@ -201,13 +197,13 @@ async def test_multiple_redirects(backend):
 
 
 async def test_too_many_redirects(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     with pytest.raises(TooManyRedirects):
         await client.get("https://example.org/multiple_redirects?count=21")
 
 
 async def test_too_many_redirects_calling_next(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/multiple_redirects?count=21"
     response = await client.get(url, allow_redirects=False)
     with pytest.raises(TooManyRedirects):
@@ -216,13 +212,13 @@ async def test_too_many_redirects_calling_next(backend):
 
 
 async def test_redirect_loop(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     with pytest.raises(RedirectLoop):
         await client.get("https://example.org/redirect_loop")
 
 
 async def test_redirect_loop_calling_next(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/redirect_loop"
     response = await client.get(url, allow_redirects=False)
     with pytest.raises(RedirectLoop):
@@ -231,7 +227,7 @@ async def test_redirect_loop_calling_next(backend):
 
 
 async def test_cross_domain_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.com/cross_domain"
     headers = {"Authorization": "abc"}
     response = await client.get(url, headers=headers)
@@ -240,7 +236,7 @@ async def test_cross_domain_redirect(backend):
 
 
 async def test_same_domain_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/cross_domain"
     headers = {"Authorization": "abc"}
     response = await client.get(url, headers=headers)
@@ -252,7 +248,7 @@ async def test_body_redirect(backend):
     """
     A 308 redirect should preserve the request body.
     """
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/redirect_body"
     data = b"Example request body"
     response = await client.post(url, data=data)
@@ -265,7 +261,7 @@ async def test_no_body_redirect(backend):
     """
     A 303 redirect should remove the request body.
     """
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/redirect_no_body"
     data = b"Example request body"
     response = await client.post(url, data=data)
@@ -275,7 +271,7 @@ async def test_no_body_redirect(backend):
 
 
 async def test_cannot_redirect_streaming_body(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.org/redirect_body"
 
     async def streaming_body():
@@ -286,26 +282,26 @@ async def test_cannot_redirect_streaming_body(backend):
 
 
 async def test_cross_subdomain_redirect(backend):
-    client = AsyncClient(dispatch=MockDispatch(), backend=backend)
+    client = Client(dispatch=MockDispatch(), backend=backend)
     url = "https://example.com/cross_subdomain"
     response = await client.get(url)
     assert response.url == URL("https://www.example.org/cross_subdomain")
 
 
-class MockCookieDispatch(AsyncDispatcher):
+class MockCookieDispatch(Dispatcher):
     async def send(
         self,
-        request: AsyncRequest,
+        request: Request,
         verify: VerifyTypes = None,
         cert: CertTypes = None,
         timeout: TimeoutTypes = None,
-    ) -> AsyncResponse:
+    ) -> Response:
         if request.url.path == "/":
             if "cookie" in request.headers:
                 content = b"Logged in"
             else:
                 content = b"Not logged in"
-            return AsyncResponse(codes.OK, content=content, request=request)
+            return Response(codes.OK, content=content, request=request)
 
         elif request.url.path == "/login":
             status_code = codes.SEE_OTHER
@@ -317,7 +313,7 @@ class MockCookieDispatch(AsyncDispatcher):
                 ),
             }
 
-            return AsyncResponse(status_code, headers=headers, request=request)
+            return Response(status_code, headers=headers, request=request)
 
         elif request.url.path == "/logout":
             status_code = codes.SEE_OTHER
@@ -328,11 +324,11 @@ class MockCookieDispatch(AsyncDispatcher):
                     "httponly; samesite=lax"
                 ),
             }
-            return AsyncResponse(status_code, headers=headers, request=request)
+            return Response(status_code, headers=headers, request=request)
 
 
 async def test_redirect_cookie_behavior(backend):
-    client = AsyncClient(dispatch=MockCookieDispatch(), backend=backend)
+    client = Client(dispatch=MockCookieDispatch(), backend=backend)
 
     # The client is not logged in.
     response = await client.get("https://example.com/")
index 540b013fefc2c36df28582dc96c5ffe6efe83e9f..b803bd1d504ad6641486afbe631b11aecc4c87c2 100644 (file)
@@ -163,11 +163,7 @@ async def echo_headers(scope, receive, send):
 
 async def redirect_301(scope, receive, send):
     await send(
-        {
-            "type": "http.response.start",
-            "status": 301,
-            "headers": [[b"location", b"/"]],
-        }
+        {"type": "http.response.start", "status": 301, "headers": [[b"location", b"/"]]}
     )
     await send({"type": "http.response.body"})
 
index b6262c6142d7472656328c7fda63f84e826a0ba5..0c8c9177be65f96e3183ded1a619b4a7cdb5a228 100644 (file)
@@ -6,7 +6,7 @@ import h2.events
 import pytest
 from h2.settings import SettingCodes
 
-from httpx import AsyncClient, Client, Response, Timeout
+from httpx import Client, Response, Timeout
 
 from .utils import MockHTTP2Backend
 
@@ -23,44 +23,22 @@ def app(request):
     return Response(200, headers=headers, content=content)
 
 
-def test_http2_get_request():
+@pytest.mark.asyncio
+async def test_http2_get_request():
     backend = MockHTTP2Backend(app=app)
 
-    with Client(backend=backend) as client:
-        response = client.get("http://example.org")
-
-    assert response.status_code == 200
-    assert json.loads(response.content) == {"method": "GET", "path": "/", "body": ""}
-
-
-async def test_async_http2_get_request(backend):
-    backend = MockHTTP2Backend(app=app, backend=backend)
-
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) as client:
         response = await client.get("http://example.org")
 
     assert response.status_code == 200
     assert json.loads(response.content) == {"method": "GET", "path": "/", "body": ""}
 
 
-def test_http2_post_request():
+@pytest.mark.asyncio
+async def test_http2_post_request():
     backend = MockHTTP2Backend(app=app)
 
-    with Client(backend=backend) as client:
-        response = client.post("http://example.org", data=b"<data>")
-
-    assert response.status_code == 200
-    assert json.loads(response.content) == {
-        "method": "POST",
-        "path": "/",
-        "body": "<data>",
-    }
-
-
-async def test_async_http2_post_request(backend):
-    backend = MockHTTP2Backend(app=app, backend=backend)
-
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) as client:
         response = await client.post("http://example.org", data=b"<data>")
 
     assert response.status_code == 200
@@ -71,25 +49,12 @@ async def test_async_http2_post_request(backend):
     }
 
 
-def test_http2_large_post_request():
+@pytest.mark.asyncio
+async def test_http2_large_post_request():
     backend = MockHTTP2Backend(app=app)
 
     data = b"a" * 100000
-    with Client(backend=backend) as client:
-        response = client.post("http://example.org", data=data)
-    assert response.status_code == 200
-    assert json.loads(response.content) == {
-        "method": "POST",
-        "path": "/",
-        "body": data.decode(),
-    }
-
-
-async def test_async_http2_large_post_request(backend):
-    backend = MockHTTP2Backend(app=app, backend=backend)
-
-    data = b"a" * 100000
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) as client:
         response = await client.post("http://example.org", data=data)
     assert response.status_code == 200
     assert json.loads(response.content) == {
@@ -99,28 +64,11 @@ async def test_async_http2_large_post_request(backend):
     }
 
 
-def test_http2_multiple_requests():
+@pytest.mark.asyncio
+async def test_http2_multiple_requests():
     backend = MockHTTP2Backend(app=app)
 
-    with Client(backend=backend) as client:
-        response_1 = client.get("http://example.org/1")
-        response_2 = client.get("http://example.org/2")
-        response_3 = client.get("http://example.org/3")
-
-    assert response_1.status_code == 200
-    assert json.loads(response_1.content) == {"method": "GET", "path": "/1", "body": ""}
-
-    assert response_2.status_code == 200
-    assert json.loads(response_2.content) == {"method": "GET", "path": "/2", "body": ""}
-
-    assert response_3.status_code == 200
-    assert json.loads(response_3.content) == {"method": "GET", "path": "/3", "body": ""}
-
-
-async def test_async_http2_multiple_requests(backend):
-    backend = MockHTTP2Backend(app=app, backend=backend)
-
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) 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")
@@ -135,33 +83,15 @@ async def test_async_http2_multiple_requests(backend):
     assert json.loads(response_3.content) == {"method": "GET", "path": "/3", "body": ""}
 
 
-def test_http2_reconnect():
+@pytest.mark.asyncio
+async def test_http2_reconnect():
     """
     If a connection has been dropped between requests, then we should
     be seemlessly reconnected.
     """
     backend = MockHTTP2Backend(app=app)
 
-    with Client(backend=backend) as client:
-        response_1 = client.get("http://example.org/1")
-        backend.server.close_connection = True
-        response_2 = client.get("http://example.org/2")
-
-    assert response_1.status_code == 200
-    assert json.loads(response_1.content) == {"method": "GET", "path": "/1", "body": ""}
-
-    assert response_2.status_code == 200
-    assert json.loads(response_2.content) == {"method": "GET", "path": "/2", "body": ""}
-
-
-async def test_async_http2_reconnect(backend):
-    """
-    If a connection has been dropped between requests, then we should
-    be seemlessly reconnected.
-    """
-    backend = MockHTTP2Backend(app=app, backend=backend)
-
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) 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")
@@ -176,7 +106,7 @@ async def test_async_http2_reconnect(backend):
 async def test_http2_settings_in_handshake(backend):
     backend = MockHTTP2Backend(app=app, backend=backend)
 
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) as client:
         await client.get("http://example.org")
 
     h2_conn = backend.server.conn
@@ -209,7 +139,7 @@ async def test_http2_settings_in_handshake(backend):
 
 
 async def test_http2_live_request(backend):
-    async with AsyncClient(backend=backend) as client:
+    async with Client(backend=backend) as client:
         try:
             resp = await client.get("https://nghttp2.org/httpbin/anything")
         except Timeout:
diff --git a/tests/dispatch/test_threaded.py b/tests/dispatch/test_threaded.py
deleted file mode 100644 (file)
index 8698d63..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-import json
-
-from httpx import (
-    AsyncClient,
-    CertTypes,
-    Client,
-    Dispatcher,
-    Request,
-    Response,
-    TimeoutTypes,
-    VerifyTypes,
-)
-
-
-def streaming_body():
-    for part in [b"Hello", b", ", b"world!"]:
-        yield part
-
-
-class MockDispatch(Dispatcher):
-    def send(
-        self,
-        request: Request,
-        verify: VerifyTypes = None,
-        cert: CertTypes = None,
-        timeout: TimeoutTypes = None,
-    ) -> Response:
-        if request.url.path == "/streaming_response":
-            return Response(200, content=streaming_body(), request=request)
-        elif request.url.path == "/echo_request_body":
-            content = request.read()
-            return Response(200, content=content, request=request)
-        elif request.url.path == "/echo_request_body_streaming":
-            content = b"".join([part for part in request.stream()])
-            return Response(200, content=content, request=request)
-        else:
-            body = json.dumps({"hello": "world"}).encode()
-            return Response(200, content=body, request=request)
-
-
-def test_threaded_dispatch():
-    """
-    Use a synchronous 'Dispatcher' class with the client.
-    Calls to the dispatcher will end up running within a thread pool.
-    """
-    url = "https://example.org/"
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
-
-    assert response.status_code == 200
-    assert response.json() == {"hello": "world"}
-
-
-async def test_async_threaded_dispatch(backend):
-    """
-    Use a synchronous 'Dispatcher' class with the async client.
-    Calls to the dispatcher will end up running within a thread pool.
-    """
-    url = "https://example.org/"
-    async with AsyncClient(dispatch=MockDispatch(), backend=backend) as client:
-        response = await client.get(url)
-
-    assert response.status_code == 200
-    assert response.json() == {"hello": "world"}
-
-
-def test_threaded_streaming_response():
-    url = "https://example.org/streaming_response"
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.get(url)
-
-    assert response.status_code == 200
-    assert response.text == "Hello, world!"
-
-
-def test_threaded_streaming_request():
-    url = "https://example.org/echo_request_body"
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.post(url, data=streaming_body())
-
-    assert response.status_code == 200
-    assert response.text == "Hello, world!"
-
-
-def test_threaded_request_body():
-    url = "https://example.org/echo_request_body"
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.post(url, data=b"Hello, world!")
-
-    assert response.status_code == 200
-    assert response.text == "Hello, world!"
-
-
-def test_threaded_request_body_streaming():
-    url = "https://example.org/echo_request_body_streaming"
-    with Client(dispatch=MockDispatch()) as client:
-        response = client.post(url, data=b"Hello, world!")
-
-    assert response.status_code == 200
-    assert response.text == "Hello, world!"
-
-
-def test_dispatch_class():
-    """
-    Use a synchronous 'Dispatcher' class directly.
-    """
-    url = "https://example.org/"
-    with MockDispatch() as dispatcher:
-        response = dispatcher.request("GET", url)
-
-    assert response.status_code == 200
-    assert response.json() == {"hello": "world"}
index 07ff750d49846da95b6fe3b731b3f88bb92fa720..7835f98e72f8f370d8119635a7272b997ae39e10 100644 (file)
@@ -19,21 +19,19 @@ def test_content_length_header():
 
 
 def test_url_encoded_data():
-    for RequestClass in (httpx.Request, httpx.AsyncRequest):
-        request = RequestClass("POST", "http://example.org", data={"test": "123"})
-        assert request.headers["Content-Type"] == "application/x-www-form-urlencoded"
-        assert request.content == b"test=123"
+    request = httpx.Request("POST", "http://example.org", data={"test": "123"})
+    assert request.headers["Content-Type"] == "application/x-www-form-urlencoded"
+    assert request.content == b"test=123"
 
 
 def test_json_encoded_data():
-    for RequestClass in (httpx.Request, httpx.AsyncRequest):
-        request = RequestClass("POST", "http://example.org", json={"test": 123})
-        assert request.headers["Content-Type"] == "application/json"
-        assert request.content == b'{"test": 123}'
+    request = httpx.Request("POST", "http://example.org", json={"test": 123})
+    assert request.headers["Content-Type"] == "application/json"
+    assert request.content == b'{"test": 123}'
 
 
 def test_transfer_encoding_header():
-    def streaming_body(data):
+    async def streaming_body(data):
         yield data  # pragma: nocover
 
     data = streaming_body(b"test 123")
@@ -58,7 +56,7 @@ def test_override_accept_encoding_header():
 
 
 def test_override_content_length_header():
-    def streaming_body(data):
+    async def streaming_body(data):
         yield data  # pragma: nocover
 
     data = streaming_body(b"test 123")
index 6467e5ca60a0cc7cfd6dc8730f099f049544d469..012d2b747fa1e2caed2853d0c72ae29aedb47116 100644 (file)
@@ -115,7 +115,8 @@ def test_response_force_encoding():
     assert response.encoding == "iso-8859-1"
 
 
-def test_read_response():
+@pytest.mark.asyncio
+async def test_read_response():
     response = httpx.Response(200, content=b"Hello, world!")
 
     assert response.status_code == 200
@@ -123,34 +124,26 @@ def test_read_response():
     assert response.encoding == "ascii"
     assert response.is_closed
 
-    content = response.read()
+    content = await response.read()
 
     assert content == b"Hello, world!"
     assert response.content == b"Hello, world!"
     assert response.is_closed
 
 
-def test_raw_interface():
+@pytest.mark.asyncio
+async def test_raw_interface():
     response = httpx.Response(200, content=b"Hello, world!")
 
     raw = b""
-    for part in response.raw():
+    async for part in response.raw():
         raw += part
     assert raw == b"Hello, world!"
 
 
-def test_stream_interface():
-    response = httpx.Response(200, content=b"Hello, world!")
-
-    content = b""
-    for part in response.stream():
-        content += part
-    assert content == b"Hello, world!"
-
-
 @pytest.mark.asyncio
-async def test_async_stream_interface():
-    response = httpx.AsyncResponse(200, content=b"Hello, world!")
+async def test_stream_interface():
+    response = httpx.Response(200, content=b"Hello, world!")
 
     content = b""
     async for part in response.stream():
@@ -158,20 +151,21 @@ async def test_async_stream_interface():
     assert content == b"Hello, world!"
 
 
-def test_stream_interface_after_read():
+@pytest.mark.asyncio
+async def test_stream_text():
     response = httpx.Response(200, content=b"Hello, world!")
 
-    response.read()
+    await response.read()
 
-    content = b""
-    for part in response.stream():
+    content = ""
+    async for part in response.stream_text():
         content += part
-    assert content == b"Hello, world!"
+    assert content == "Hello, world!"
 
 
 @pytest.mark.asyncio
-async def test_async_stream_interface_after_read():
-    response = httpx.AsyncResponse(200, content=b"Hello, world!")
+async def test_stream_interface_after_read():
+    response = httpx.Response(200, content=b"Hello, world!")
 
     await response.read()
 
@@ -181,22 +175,9 @@ async def test_async_stream_interface_after_read():
     assert content == b"Hello, world!"
 
 
-def test_streaming_response():
-    response = httpx.Response(200, content=streaming_body())
-
-    assert response.status_code == 200
-    assert not response.is_closed
-
-    content = response.read()
-
-    assert content == b"Hello, world!"
-    assert response.content == b"Hello, world!"
-    assert response.is_closed
-
-
 @pytest.mark.asyncio
-async def test_async_streaming_response():
-    response = httpx.AsyncResponse(200, content=async_streaming_body())
+async def test_streaming_response():
+    response = httpx.Response(200, content=async_streaming_body())
 
     assert response.status_code == 200
     assert not response.is_closed
@@ -208,20 +189,9 @@ async def test_async_streaming_response():
     assert response.is_closed
 
 
-def test_cannot_read_after_stream_consumed():
-    response = httpx.Response(200, content=streaming_body())
-
-    content = b""
-    for part in response.stream():
-        content += part
-
-    with pytest.raises(httpx.StreamConsumed):
-        response.read()
-
-
 @pytest.mark.asyncio
-async def test_async_cannot_read_after_stream_consumed():
-    response = httpx.AsyncResponse(200, content=async_streaming_body())
+async def test_cannot_read_after_stream_consumed():
+    response = httpx.Response(200, content=async_streaming_body())
 
     content = b""
     async for part in response.stream():
@@ -231,18 +201,9 @@ async def test_async_cannot_read_after_stream_consumed():
         await response.read()
 
 
-def test_cannot_read_after_response_closed():
-    response = httpx.Response(200, content=streaming_body())
-
-    response.close()
-
-    with pytest.raises(httpx.ResponseClosed):
-        response.read()
-
-
 @pytest.mark.asyncio
-async def test_async_cannot_read_after_response_closed():
-    response = httpx.AsyncResponse(200, content=async_streaming_body())
+async def test_cannot_read_after_response_closed():
+    response = httpx.Response(200, content=async_streaming_body())
 
     await response.close()
 
@@ -311,18 +272,3 @@ def test_json_without_specified_encoding_decode_error():
 def test_link_headers(headers, expected):
     response = httpx.Response(200, content=None, headers=headers)
     assert response.links == expected
-
-
-@pytest.mark.asyncio
-async def test_stream_text():
-    async def iterator():
-        yield b"Hello, world!"
-
-    response = httpx.AsyncResponse(200, content=iterator().__aiter__())
-
-    await response.read()
-
-    content = ""
-    async for part in response.stream_text():
-        content += part
-    assert content == "Hello, world!"
diff --git a/tests/requests_compat/__init__.py b/tests/requests_compat/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tests/requests_compat/conftest.py b/tests/requests_compat/conftest.py
deleted file mode 100644 (file)
index 18fade7..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-import sys
-
-import pytest
-
-import httpx
-
-
-def pytest_configure(config):
-    # Allow to 'import requests' without having to write 'import httpx as requests'.
-    # This means we *could* literally copy-paste Requests test code as-is, if relevant.
-    sys.modules["requests"] = httpx
-
-
-@pytest.fixture(autouse=True)
-def patch_httpx(monkeypatch):
-    """Monkey-patch Requests APIs onto HTTPX."""
-    monkeypatch.setattr(httpx, "session", httpx.Client, raising=False)
diff --git a/tests/requests_compat/test_api.py b/tests/requests_compat/test_api.py
deleted file mode 100644 (file)
index 39ae712..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-"""Test compatibility with the Requests high-level API."""
-import pytest
-import requests
-
-
-@pytest.mark.copied_from(
-    "https://github.com/psf/requests/blob/v2.22.0/tests/test_requests.py#L61-L70"
-)
-def test_entrypoints():
-    requests.session
-    requests.session().get
-    requests.session().head
-    requests.get
-    requests.head
-    requests.put
-    requests.patch
-    requests.post
-
-
-@pytest.mark.copied_from(
-    "https://github.com/psf/requests/blob/v2.22.0/tests/test_requests.py#L72",
-    changes=["added noqa comment to silence flake8"],
-)
-@pytest.mark.xfail(
-    reason="PoolManager has no obvious equivalent in HTTPX", raises=ImportError
-)
-def test_poolmanager_entrypoint():
-    # Not really an entry point, but people rely on it.
-    from requests.packages.urllib3.poolmanager import PoolManager  # noqa: F401
index 1500869eb00945e2c63ee03a3dc54eecbfb29b9a..a3fb23d9f9e5d580910d48c7b7ae23eae24ced9f 100644 (file)
@@ -3,61 +3,70 @@ import pytest
 import httpx
 
 
-def test_get(server):
-    response = httpx.get(server.url)
+@pytest.mark.asyncio
+async def test_get(server):
+    response = await httpx.get(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.text == "Hello, world!"
     assert response.http_version == "HTTP/1.1"
 
 
-def test_post(server):
-    response = httpx.post(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_post(server):
+    response = await httpx.post(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_post_byte_iterator(server):
-    def data():
+@pytest.mark.asyncio
+async def test_post_byte_iterator(server):
+    async def data():
         yield b"Hello"
         yield b", "
         yield b"world!"
 
-    response = httpx.post(server.url, data=data())
+    response = await httpx.post(server.url, data=data())
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_options(server):
-    response = httpx.options(server.url)
+@pytest.mark.asyncio
+async def test_options(server):
+    response = await httpx.options(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_head(server):
-    response = httpx.head(server.url)
+@pytest.mark.asyncio
+async def test_head(server):
+    response = await httpx.head(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_put(server):
-    response = httpx.put(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_put(server):
+    response = await httpx.put(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_patch(server):
-    response = httpx.patch(server.url, data=b"Hello, world!")
+@pytest.mark.asyncio
+async def test_patch(server):
+    response = await httpx.patch(server.url, data=b"Hello, world!")
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_delete(server):
-    response = httpx.delete(server.url)
+@pytest.mark.asyncio
+async def test_delete(server):
+    response = await httpx.delete(server.url)
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
 
 
-def test_get_invalid_url(server):
+@pytest.mark.asyncio
+async def test_get_invalid_url(server):
     with pytest.raises(httpx.InvalidURL):
-        httpx.get("invalid://example.org")
+        await httpx.get("invalid://example.org")
index f31e871a2a2194fb656411a9af0e8d948ac98e00..3ae1968c4a111788e969ec0e132bf28bd87154bc 100644 (file)
@@ -39,53 +39,31 @@ async def raise_exc_after_response(scope, receive, send):
     raise ValueError()
 
 
-def test_asgi():
+@pytest.mark.asyncio
+async def test_asgi():
     client = httpx.Client(app=hello_world)
-    response = client.get("http://www.example.org/")
-    assert response.status_code == 200
-    assert response.text == "Hello, World!"
-
-
-async def test_asgi_async(backend):
-    client = httpx.AsyncClient(app=hello_world, backend=backend)
     response = await client.get("http://www.example.org/")
     assert response.status_code == 200
     assert response.text == "Hello, World!"
 
 
-def test_asgi_upload():
+@pytest.mark.asyncio
+async def test_asgi_upload():
     client = httpx.Client(app=echo_body)
-    response = client.post("http://www.example.org/", data=b"example")
-    assert response.status_code == 200
-    assert response.text == "example"
-
-
-async def test_asgi_upload_async(backend):
-    client = httpx.AsyncClient(app=echo_body, backend=backend)
     response = await client.post("http://www.example.org/", data=b"example")
     assert response.status_code == 200
     assert response.text == "example"
 
 
-def test_asgi_exc():
+@pytest.mark.asyncio
+async def test_asgi_exc():
     client = httpx.Client(app=raise_exc)
-    with pytest.raises(ValueError):
-        client.get("http://www.example.org/")
-
-
-async def test_asgi_exc_async(backend):
-    client = httpx.AsyncClient(app=raise_exc, backend=backend)
     with pytest.raises(ValueError):
         await client.get("http://www.example.org/")
 
 
-def test_asgi_exc_after_response():
+@pytest.mark.asyncio
+async def test_asgi_exc_after_response():
     client = httpx.Client(app=raise_exc_after_response)
-    with pytest.raises(ValueError):
-        client.get("http://www.example.org/")
-
-
-async def test_asgi_exc_after_response_async(backend):
-    client = httpx.AsyncClient(app=raise_exc_after_response, backend=backend)
     with pytest.raises(ValueError):
         await client.get("http://www.example.org/")
index 1b5d6e1dee4971e924a15e832cd36209558b05fb..75252390b547036faca805cb2a41ea8f611dd6e2 100644 (file)
@@ -71,18 +71,19 @@ def test_multi_with_identity():
     assert response.content == body
 
 
-def test_streaming():
+@pytest.mark.asyncio
+async def test_streaming():
     body = b"test 123"
     compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)
 
-    def compress(body):
+    async def compress(body):
         yield compressor.compress(body)
         yield compressor.flush()
 
     headers = [(b"Content-Encoding", b"gzip")]
     response = httpx.Response(200, headers=headers, content=compress(body))
     assert not hasattr(response, "body")
-    assert response.read() == body
+    assert await response.read() == body
 
 
 @pytest.mark.parametrize("header_value", (b"deflate", b"gzip", b"br", b"identity"))
@@ -128,18 +129,21 @@ def test_decoding_errors(header_value):
         ),
     ],
 )
-def test_text_decoder(data, encoding):
-    def iterator():
+@pytest.mark.asyncio
+async def test_text_decoder(data, encoding):
+    async def iterator():
         nonlocal data
         for chunk in data:
             yield chunk
 
     response = httpx.Response(200, content=iterator())
-    assert "".join(response.stream_text()) == (b"".join(data)).decode(encoding)
+    await response.read()
+    assert response.text == (b"".join(data)).decode(encoding)
 
 
-def test_text_decoder_known_encoding():
-    def iterator():
+@pytest.mark.asyncio
+async def test_text_decoder_known_encoding():
+    async def iterator():
         yield b"\x83g"
         yield b"\x83"
         yield b"\x89\x83x\x83\x8b"
@@ -150,7 +154,8 @@ def test_text_decoder_known_encoding():
         content=iterator(),
     )
 
-    assert "".join(response.stream_text()) == "トラベル"
+    await response.read()
+    assert "".join(response.text) == "トラベル"
 
 
 def test_text_decoder_empty_cases():
@@ -167,5 +172,4 @@ def test_invalid_content_encoding_header():
     body = b"test 123"
 
     response = httpx.Response(200, headers=headers, content=body)
-
-    assert response.read() == body
+    assert response.content == body
index 41e69f7ab94e455f6db09f6f6b7b6fa656deb42b..87727020fa7ed468239f0fbc082d953b605ac487 100644 (file)
@@ -20,7 +20,7 @@ from httpx import (
 
 
 class MockDispatch(Dispatcher):
-    def send(
+    async def send(
         self,
         request: Request,
         verify: VerifyTypes = None,
@@ -28,17 +28,19 @@ class MockDispatch(Dispatcher):
         timeout: TimeoutTypes = None,
         http_versions: HTTPVersionTypes = None,
     ) -> Response:
-        return Response(200, content=request.read())
+        content = await request.read()
+        return Response(200, content=content)
 
 
 @pytest.mark.parametrize(("value,output"), (("abc", b"abc"), (b"abc", b"abc")))
-def test_multipart(value, output):
+@pytest.mark.asyncio
+async def test_multipart(value, output):
     client = Client(dispatch=MockDispatch())
 
     # Test with a single-value 'data' argument, and a plain file 'files' argument.
     data = {"text": value}
     files = {"file": io.BytesIO(b"<file content>")}
-    response = client.post("http://127.0.0.1:8000/", data=data, files=files)
+    response = await client.post("http://127.0.0.1:8000/", data=data, files=files)
     assert response.status_code == 200
 
     # We're using the cgi module to verify the behavior here, which is a
@@ -55,32 +57,35 @@ def test_multipart(value, output):
 
 
 @pytest.mark.parametrize(("key"), (b"abc", 1, 2.3, None))
-def test_multipart_invalid_key(key):
+@pytest.mark.asyncio
+async def test_multipart_invalid_key(key):
     client = Client(dispatch=MockDispatch())
     data = {key: "abc"}
     files = {"file": io.BytesIO(b"<file content>")}
     with pytest.raises(TypeError) as e:
-        client.post("http://127.0.0.1:8000/", data=data, files=files)
+        await client.post("http://127.0.0.1:8000/", data=data, files=files)
     assert "Invalid type for name" in str(e.value)
 
 
 @pytest.mark.parametrize(("value"), (1, 2.3, None, [None, "abc"], {None: "abc"}))
-def test_multipart_invalid_value(value):
+@pytest.mark.asyncio
+async def test_multipart_invalid_value(value):
     client = Client(dispatch=MockDispatch())
     data = {"text": value}
     files = {"file": io.BytesIO(b"<file content>")}
     with pytest.raises(TypeError) as e:
-        client.post("http://127.0.0.1:8000/", data=data, files=files)
+        await client.post("http://127.0.0.1:8000/", data=data, files=files)
     assert "Invalid type for value" in str(e.value)
 
 
-def test_multipart_file_tuple():
+@pytest.mark.asyncio
+async def test_multipart_file_tuple():
     client = Client(dispatch=MockDispatch())
 
     # Test with a list of values 'data' argument, and a tuple style 'files' argument.
     data = {"text": ["abc"]}
     files = {"file": ("name.txt", io.BytesIO(b"<file content>"))}
-    response = client.post("http://127.0.0.1:8000/", data=data, files=files)
+    response = await client.post("http://127.0.0.1:8000/", data=data, files=files)
     assert response.status_code == 200
 
     # We're using the cgi module to verify the behavior here, which is a
index ff5273b9a9cd48a367cfca5217ded3f310c63f4d..bd6bf8c4c4e4db820f83486fe5c87010f10440dd 100644 (file)
@@ -1,7 +1,6 @@
 import pytest
 
 from httpx import (
-    AsyncClient,
     Client,
     ConnectTimeout,
     PoolLimits,
@@ -15,7 +14,7 @@ from httpx import (
 async def test_read_timeout(server, backend):
     timeout = TimeoutConfig(read_timeout=1e-6)
 
-    async with AsyncClient(timeout=timeout, backend=backend) as client:
+    async with Client(timeout=timeout, backend=backend) as client:
         with pytest.raises(ReadTimeout):
             await client.get(server.url.copy_with(path="/slow_response"))
 
@@ -23,7 +22,7 @@ async def test_read_timeout(server, backend):
 async def test_write_timeout(server, backend):
     timeout = TimeoutConfig(write_timeout=1e-6)
 
-    async with AsyncClient(timeout=timeout, backend=backend) as client:
+    async with Client(timeout=timeout, backend=backend) as client:
         with pytest.raises(WriteTimeout):
             data = b"*" * 1024 * 1024 * 100
             await client.put(server.url.copy_with(path="/slow_response"), data=data)
@@ -32,7 +31,7 @@ async def test_write_timeout(server, backend):
 async def test_connect_timeout(server, backend):
     timeout = TimeoutConfig(connect_timeout=1e-6)
 
-    async with AsyncClient(timeout=timeout, backend=backend) as client:
+    async with Client(timeout=timeout, backend=backend) as client:
         with pytest.raises(ConnectTimeout):
             # See https://stackoverflow.com/questions/100841/
             await client.get("http://10.255.255.1/")
@@ -41,20 +40,10 @@ async def test_connect_timeout(server, backend):
 async def test_pool_timeout(server, backend):
     pool_limits = PoolLimits(hard_limit=1, pool_timeout=1e-4)
 
-    async with AsyncClient(pool_limits=pool_limits, backend=backend) as client:
+    async with Client(pool_limits=pool_limits, backend=backend) as client:
         response = await client.get(server.url, stream=True)
 
         with pytest.raises(PoolTimeout):
             await client.get("http://localhost:8000/")
 
         await response.read()
-
-
-def test_sync_infinite_timeout(server):
-    """Regression test for a bug that occurred under Python 3.6.
-
-    See: https://github.com/encode/httpx/issues/382
-    """
-    no_timeout = TimeoutConfig()
-    with Client(timeout=no_timeout) as client:
-        client.get(server.url.copy_with(path="/slow_response/50"))
index 1da08fae24b24cc4bda56d10130be469aa9f64f7..e1f4ff785b00f4879af644d900ea6243a8bd21b2 100644 (file)
@@ -104,7 +104,7 @@ def test_parse_header_links(value, expected):
 @pytest.mark.asyncio
 async def test_logs_debug(server, capsys):
     with override_log_level("debug"):
-        async with httpx.AsyncClient() as client:
+        async with httpx.Client() as client:
             response = await client.get(server.url)
             assert response.status_code == 200
     stderr = capsys.readouterr().err
@@ -115,7 +115,7 @@ async def test_logs_debug(server, capsys):
 @pytest.mark.asyncio
 async def test_logs_trace(server, capsys):
     with override_log_level("trace"):
-        async with httpx.AsyncClient() as client:
+        async with httpx.Client() as client:
             response = await client.get(server.url)
             assert response.status_code == 200
     stderr = capsys.readouterr().err
@@ -126,7 +126,7 @@ async def test_logs_trace(server, capsys):
 @pytest.mark.asyncio
 async def test_logs_redirect_chain(server, capsys):
     with override_log_level("debug"):
-        async with httpx.AsyncClient() as client:
+        async with httpx.Client() as client:
             response = await client.get(server.url.copy_with(path="/redirect_301"))
             assert response.status_code == 200
 
diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py
deleted file mode 100644 (file)
index 0dcf995..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-import sys
-
-import pytest
-
-import httpx
-
-
-def hello_world(environ, start_response):
-    status = "200 OK"
-    output = b"Hello, World!"
-
-    response_headers = [
-        ("Content-type", "text/plain"),
-        ("Content-Length", str(len(output))),
-    ]
-
-    start_response(status, response_headers)
-
-    return [output]
-
-
-def echo_body(environ, start_response):
-    status = "200 OK"
-    output = environ["wsgi.input"].read()
-
-    response_headers = [
-        ("Content-type", "text/plain"),
-        ("Content-Length", str(len(output))),
-    ]
-
-    start_response(status, response_headers)
-
-    return [output]
-
-
-def echo_body_with_response_stream(environ, start_response):
-    status = "200 OK"
-
-    response_headers = [("Content-Type", "text/plain")]
-
-    start_response(status, response_headers)
-
-    def output_generator(f):
-        while True:
-            output = f.read(2)
-            if not output:
-                break
-            yield output
-
-    return output_generator(f=environ["wsgi.input"])
-
-
-def raise_exc(environ, start_response):
-    status = "500 Server Error"
-    output = b"Nope!"
-
-    response_headers = [
-        ("Content-type", "text/plain"),
-        ("Content-Length", str(len(output))),
-    ]
-
-    try:
-        raise ValueError()
-    except ValueError:
-        exc_info = sys.exc_info()
-        start_response(status, response_headers, exc_info=exc_info)
-
-    return [output]
-
-
-def test_wsgi():
-    client = httpx.Client(app=hello_world)
-    response = client.get("http://www.example.org/")
-    assert response.status_code == 200
-    assert response.text == "Hello, World!"
-
-
-def test_wsgi_upload():
-    client = httpx.Client(app=echo_body)
-    response = client.post("http://www.example.org/", data=b"example")
-    assert response.status_code == 200
-    assert response.text == "example"
-
-
-def test_wsgi_upload_with_response_stream():
-    client = httpx.Client(app=echo_body_with_response_stream)
-    response = client.post("http://www.example.org/", data=b"example")
-    assert response.status_code == 200
-    assert response.text == "example"
-
-
-def test_wsgi_exc():
-    client = httpx.Client(app=raise_exc)
-    with pytest.raises(ValueError):
-        client.get("http://www.example.org/")