Drop StreamContextManager in favour of contextlib.contextmanager/asynccontextmanager (#1577)

* Drop StreamContextManager in favour of using contextlib.contextmanager/asyncontextmanager

* Use type: ignore to avoid mypy errors on 3.6
This commit is contained in:
Tom Christie 2021-04-19 11:07:07 +01:00 committed by GitHub
parent ed19995747
commit 8fd5b71016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 129 deletions

View File

@ -122,6 +122,7 @@ The HTTPX project relies on these excellent libraries:
* `rfc3986` - URL parsing & normalization.
* `idna` - Internationalized domain name support.
* `sniffio` - Async library autodetection.
* `async_generator` - Backport support for `contextlib.asynccontextmanager`. *(Only required for Python 3.6)*
* `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)*
A huge amount of credit is due to `requests` for the API layout that

View File

@ -114,6 +114,7 @@ The HTTPX project relies on these excellent libraries:
* `rfc3986` - URL parsing & normalization.
* `idna` - Internationalized domain name support.
* `sniffio` - Async library autodetection.
* `async_generator` - Backport support for `contextlib.asynccontextmanager`. *(Only required for Python 3.6)*
* `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)*
A huge amount of credit is due to `requests` for the API layout that

View File

@ -1,8 +1,9 @@
import typing
from contextlib import contextmanager
from ._client import Client, StreamContextManager
from ._client import Client
from ._config import DEFAULT_TIMEOUT_CONFIG
from ._models import Request, Response
from ._models import Response
from ._types import (
AuthTypes,
CertTypes,
@ -106,6 +107,7 @@ def request(
)
@contextmanager
def stream(
method: str,
url: URLTypes,
@ -124,7 +126,7 @@ def stream(
verify: VerifyTypes = True,
cert: CertTypes = None,
trust_env: bool = True,
) -> StreamContextManager:
) -> typing.Iterator[Response]:
"""
Alternative to `httpx.request()` that streams the response body
instead of loading it into memory at once.
@ -135,26 +137,23 @@ def stream(
[0]: /quickstart#streaming-responses
"""
client = Client(proxies=proxies, cert=cert, verify=verify, trust_env=trust_env)
request = Request(
method=method,
url=url,
params=params,
content=content,
data=data,
files=files,
json=json,
headers=headers,
cookies=cookies,
)
return StreamContextManager(
client=client,
request=request,
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
close_client=True,
)
with Client(
proxies=proxies, cert=cert, verify=verify, trust_env=trust_env
) as client:
with client.stream(
method=method,
url=url,
content=content,
data=data,
files=files,
json=json,
params=params,
headers=headers,
cookies=cookies,
auth=auth,
allow_redirects=allow_redirects,
) as response:
yield response
def get(

View File

@ -2,10 +2,12 @@ import datetime
import enum
import typing
import warnings
from contextlib import contextmanager
from types import TracebackType
from .__version__ import __version__
from ._auth import Auth, BasicAuth, FunctionAuth
from ._compat import asynccontextmanager
from ._config import (
DEFAULT_LIMITS,
DEFAULT_MAX_REDIRECTS,
@ -289,51 +291,6 @@ class BaseClient:
def params(self, params: QueryParamTypes) -> None:
self._params = QueryParams(params)
def stream(
self,
method: str,
url: URLTypes,
*,
content: RequestContent = None,
data: RequestData = None,
files: RequestFiles = None,
json: typing.Any = None,
params: QueryParamTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UnsetType] = UNSET,
allow_redirects: bool = True,
timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
) -> "StreamContextManager":
"""
Alternative to `httpx.request()` that streams the response body
instead of loading it into memory at once.
**Parameters**: See `httpx.request`.
See also: [Streaming Responses][0]
[0]: /quickstart#streaming-responses
"""
request = self.build_request(
method=method,
url=url,
content=content,
data=data,
files=files,
json=json,
params=params,
headers=headers,
cookies=cookies,
)
return StreamContextManager(
client=self,
request=request,
auth=auth,
allow_redirects=allow_redirects,
timeout=timeout,
)
def build_request(
self,
method: str,
@ -793,6 +750,56 @@ class Client(BaseClient):
request, auth=auth, allow_redirects=allow_redirects, timeout=timeout
)
@contextmanager
def stream(
self,
method: str,
url: URLTypes,
*,
content: RequestContent = None,
data: RequestData = None,
files: RequestFiles = None,
json: typing.Any = None,
params: QueryParamTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UnsetType] = UNSET,
allow_redirects: bool = True,
timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
) -> typing.Iterator[Response]:
"""
Alternative to `httpx.request()` that streams the response body
instead of loading it into memory at once.
**Parameters**: See `httpx.request`.
See also: [Streaming Responses][0]
[0]: /quickstart#streaming-responses
"""
request = self.build_request(
method=method,
url=url,
content=content,
data=data,
files=files,
json=json,
params=params,
headers=headers,
cookies=cookies,
)
response = self.send(
request=request,
auth=auth,
allow_redirects=allow_redirects,
timeout=timeout,
stream=True,
)
try:
yield response
finally:
response.close()
def send(
self,
request: Request,
@ -1430,6 +1437,56 @@ class AsyncClient(BaseClient):
)
return response
@asynccontextmanager
async def stream(
self,
method: str,
url: URLTypes,
*,
content: RequestContent = None,
data: RequestData = None,
files: RequestFiles = None,
json: typing.Any = None,
params: QueryParamTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
auth: typing.Union[AuthTypes, UnsetType] = UNSET,
allow_redirects: bool = True,
timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
) -> typing.AsyncIterator[Response]:
"""
Alternative to `httpx.request()` that streams the response body
instead of loading it into memory at once.
**Parameters**: See `httpx.request`.
See also: [Streaming Responses][0]
[0]: /quickstart#streaming-responses
"""
request = self.build_request(
method=method,
url=url,
content=content,
data=data,
files=files,
json=json,
params=params,
headers=headers,
cookies=cookies,
)
response = await self.send(
request=request,
auth=auth,
allow_redirects=allow_redirects,
timeout=timeout,
stream=True,
)
try:
yield response
finally:
await response.aclose()
async def send(
self,
request: Request,
@ -1869,64 +1926,3 @@ class AsyncClient(BaseClient):
"See https://www.python-httpx.org/async/#opening-and-closing-clients "
"for details."
)
class StreamContextManager:
def __init__(
self,
client: BaseClient,
request: Request,
*,
auth: typing.Union[AuthTypes, UnsetType] = UNSET,
allow_redirects: bool = True,
timeout: typing.Union[TimeoutTypes, UnsetType] = UNSET,
close_client: bool = False,
) -> None:
self.client = client
self.request = request
self.auth = auth
self.allow_redirects = allow_redirects
self.timeout = timeout
self.close_client = close_client
def __enter__(self) -> "Response":
assert isinstance(self.client, Client)
self.response = self.client.send(
request=self.request,
auth=self.auth,
allow_redirects=self.allow_redirects,
timeout=self.timeout,
stream=True,
)
return self.response
def __exit__(
self,
exc_type: typing.Type[BaseException] = None,
exc_value: BaseException = None,
traceback: TracebackType = None,
) -> None:
assert isinstance(self.client, Client)
self.response.close()
if self.close_client:
self.client.close()
async def __aenter__(self) -> "Response":
assert isinstance(self.client, AsyncClient)
self.response = await self.client.send(
request=self.request,
auth=self.auth,
allow_redirects=self.allow_redirects,
timeout=self.timeout,
stream=True,
)
return self.response
async def __aexit__(
self,
exc_type: typing.Type[BaseException] = None,
exc_value: BaseException = None,
traceback: TracebackType = None,
) -> None:
assert isinstance(self.client, AsyncClient)
await self.response.aclose()

6
httpx/_compat.py Normal file
View File

@ -0,0 +1,6 @@
# `contextlib.asynccontextmanager` exists from Python 3.7 onwards.
# For 3.6 we require the `async_generator` package for a backported version.
try:
from contextlib import asynccontextmanager # type: ignore
except ImportError: # pragma: no cover
from async_generator import asynccontextmanager # type: ignore # noqa

View File

@ -60,6 +60,7 @@ setup(
"sniffio",
"rfc3986[idna2008]>=1.3,<2",
"httpcore>=0.12.1,<0.13",
"async_generator; python_version < '3.7'"
],
extras_require={
"http2": "h2==3.*",