Closing AsyncClient in all tests (#871) (#1219)

All over the AsyncClient invocation is made using context manager or with try-finally block
This commit is contained in:
cdeler 2020-08-31 18:02:28 +03:00 committed by GitHub
parent aad8209928
commit fa7661b306
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 147 deletions

View File

@ -5,12 +5,11 @@ import typing
import httpcore
import pytest
import httpx
from httpx import (
URL,
AsyncClient,
Auth,
BasicAuth,
Client,
DigestAuth,
ProtocolError,
Request,
@ -189,8 +188,8 @@ async def test_basic_auth() -> None:
url = "https://example.org/"
auth = ("tomchristie", "password123")
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
@ -200,8 +199,8 @@ async def test_basic_auth() -> None:
async def test_basic_auth_in_url() -> None:
url = "https://tomchristie:password123@example.org/"
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
@ -212,8 +211,8 @@ async def test_basic_auth_on_session() -> None:
url = "https://example.org/"
auth = ("tomchristie", "password123")
client = AsyncClient(transport=AsyncMockTransport(), auth=auth)
response = await client.get(url)
async with httpx.AsyncClient(transport=AsyncMockTransport(), auth=auth) as client:
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
@ -227,8 +226,8 @@ async def test_custom_auth() -> None:
request.headers["Authorization"] = "Token 123"
return request
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": "Token 123"}
@ -239,8 +238,8 @@ async def test_netrc_auth() -> None:
os.environ["NETRC"] = str(FIXTURES_DIR / ".netrc")
url = "http://netrcexample.org"
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {
@ -253,8 +252,8 @@ async def test_auth_header_has_priority_over_netrc() -> None:
os.environ["NETRC"] = str(FIXTURES_DIR / ".netrc")
url = "http://netrcexample.org"
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url, headers={"Authorization": "Override"})
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, headers={"Authorization": "Override"})
assert response.status_code == 200
assert response.json() == {"auth": "Override"}
@ -265,14 +264,18 @@ async def test_trust_env_auth() -> None:
os.environ["NETRC"] = str(FIXTURES_DIR / ".netrc")
url = "http://netrcexample.org"
client = AsyncClient(transport=AsyncMockTransport(), trust_env=False)
response = await client.get(url)
async with httpx.AsyncClient(
transport=AsyncMockTransport(), trust_env=False
) as client:
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {"auth": None}
client = AsyncClient(transport=AsyncMockTransport(), trust_env=True)
response = await client.get(url)
async with httpx.AsyncClient(
transport=AsyncMockTransport(), trust_env=True
) as client:
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {
@ -285,8 +288,8 @@ async def test_auth_disable_per_request() -> None:
url = "https://example.org/"
auth = ("tomchristie", "password123")
client = AsyncClient(transport=AsyncMockTransport(), auth=auth)
response = await client.get(url, auth=None)
async with httpx.AsyncClient(transport=AsyncMockTransport(), auth=auth) as client:
response = await client.get(url, auth=None)
assert response.status_code == 200
assert response.json() == {"auth": None}
@ -304,41 +307,40 @@ async def test_auth_hidden_header() -> None:
url = "https://example.org/"
auth = ("example-username", "example-password")
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, auth=auth)
assert "'authorization': '[secure]'" in str(response.request.headers)
@pytest.mark.asyncio
async def test_auth_property() -> None:
client = AsyncClient(transport=AsyncMockTransport())
assert client.auth is None
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
assert client.auth is None
client.auth = ("tomchristie", "password123") # type: ignore
assert isinstance(client.auth, BasicAuth)
client.auth = ("tomchristie", "password123") # type: ignore
assert isinstance(client.auth, BasicAuth)
url = "https://example.org/"
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
url = "https://example.org/"
response = await client.get(url)
assert response.status_code == 200
assert response.json() == {"auth": "Basic dG9tY2hyaXN0aWU6cGFzc3dvcmQxMjM="}
@pytest.mark.asyncio
async def test_auth_invalid_type() -> None:
with pytest.raises(TypeError):
client = AsyncClient(
client = httpx.AsyncClient(
transport=AsyncMockTransport(),
auth="not a tuple, not a callable", # type: ignore
)
client = AsyncClient(transport=AsyncMockTransport())
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
with pytest.raises(TypeError):
await client.get(auth="not a tuple, not a callable") # type: ignore
with pytest.raises(TypeError):
await client.get(auth="not a tuple, not a callable") # type: ignore
with pytest.raises(TypeError):
client.auth = "not a tuple, not a callable" # type: ignore
with pytest.raises(TypeError):
client.auth = "not a tuple, not a callable" # type: ignore
@pytest.mark.asyncio
@ -346,8 +348,8 @@ async def test_digest_auth_returns_no_auth_if_no_digest_header_in_response() ->
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": None}
@ -360,10 +362,10 @@ async def test_digest_auth_200_response_including_digest_auth_header() -> None:
auth = DigestAuth(username="tomchristie", password="password123")
auth_header = b'Digest realm="realm@host.com",qop="auth",nonce="abc",opaque="xyz"'
client = AsyncClient(
async with httpx.AsyncClient(
transport=AsyncMockTransport(auth_header=auth_header, status_code=200)
)
response = await client.get(url, auth=auth)
) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": None}
@ -375,8 +377,10 @@ async def test_digest_auth_401_response_without_digest_auth_header() -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=AsyncMockTransport(auth_header=b"", status_code=401))
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(
transport=AsyncMockTransport(auth_header=b"", status_code=401)
) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 401
assert response.json() == {"auth": None}
@ -403,8 +407,10 @@ async def test_digest_auth(
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=MockDigestAuthTransport(algorithm=algorithm))
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(
transport=MockDigestAuthTransport(algorithm=algorithm)
) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert len(response.history) == 1
@ -433,8 +439,8 @@ async def test_digest_auth_no_specified_qop() -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=MockDigestAuthTransport(qop=""))
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=MockDigestAuthTransport(qop="")) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert len(response.history) == 1
@ -464,8 +470,8 @@ async def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str)
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=MockDigestAuthTransport(qop=qop))
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(transport=MockDigestAuthTransport(qop=qop)) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert len(response.history) == 1
@ -475,20 +481,24 @@ async def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str)
async def test_digest_auth_qop_auth_int_not_implemented() -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=MockDigestAuthTransport(qop="auth-int"))
with pytest.raises(NotImplementedError):
await client.get(url, auth=auth)
async with httpx.AsyncClient(
transport=MockDigestAuthTransport(qop="auth-int")
) as client:
with pytest.raises(NotImplementedError):
await client.get(url, auth=auth)
@pytest.mark.asyncio
async def test_digest_auth_qop_must_be_auth_or_auth_int() -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=MockDigestAuthTransport(qop="not-auth"))
with pytest.raises(ProtocolError):
await client.get(url, auth=auth)
async with httpx.AsyncClient(
transport=MockDigestAuthTransport(qop="not-auth")
) as client:
with pytest.raises(ProtocolError):
await client.get(url, auth=auth)
@pytest.mark.asyncio
@ -496,10 +506,10 @@ async def test_digest_auth_incorrect_credentials() -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(
async with httpx.AsyncClient(
transport=MockDigestAuthTransport(send_response_after_attempt=2)
)
response = await client.get(url, auth=auth)
) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 401
assert len(response.history) == 1
@ -521,12 +531,11 @@ async def test_async_digest_auth_raises_protocol_error_on_malformed_header(
) -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(
async with httpx.AsyncClient(
transport=AsyncMockTransport(auth_header=auth_header, status_code=401)
)
with pytest.raises(ProtocolError):
await client.get(url, auth=auth)
) as client:
with pytest.raises(ProtocolError):
await client.get(url, auth=auth)
@pytest.mark.parametrize(
@ -544,12 +553,12 @@ def test_sync_digest_auth_raises_protocol_error_on_malformed_header(
) -> None:
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = Client(
transport=SyncMockTransport(auth_header=auth_header, status_code=401)
)
with pytest.raises(ProtocolError):
client.get(url, auth=auth)
with httpx.Client(
transport=SyncMockTransport(auth_header=auth_header, status_code=401)
) as client:
with pytest.raises(ProtocolError):
client.get(url, auth=auth)
@pytest.mark.asyncio
@ -560,9 +569,12 @@ async def test_async_auth_history() -> None:
"""
url = "https://example.org/"
auth = RepeatAuth(repeat=2)
client = AsyncClient(transport=AsyncMockTransport(auth_header=b"abc"))
response = await client.get(url, auth=auth)
async with httpx.AsyncClient(
transport=AsyncMockTransport(auth_header=b"abc")
) as client:
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": "Repeat abc.abc"}
@ -584,9 +596,10 @@ def test_sync_auth_history() -> None:
"""
url = "https://example.org/"
auth = RepeatAuth(repeat=2)
client = Client(transport=SyncMockTransport(auth_header=b"abc"))
response = client.get(url, auth=auth)
with httpx.Client(transport=SyncMockTransport(auth_header=b"abc")) as client:
response = client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": "Repeat abc.abc"}
@ -605,13 +618,13 @@ def test_sync_auth_history() -> None:
async def test_digest_auth_unavailable_streaming_body():
url = "https://example.org/"
auth = DigestAuth(username="tomchristie", password="password123")
client = AsyncClient(transport=AsyncMockTransport())
async def streaming_body():
yield b"Example request body" # pragma: nocover
with pytest.raises(RequestBodyUnavailable):
await client.post(url, data=streaming_body(), auth=auth)
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
with pytest.raises(RequestBodyUnavailable):
await client.post(url, data=streaming_body(), auth=auth)
@pytest.mark.asyncio
@ -622,9 +635,9 @@ async def test_async_auth_reads_response_body() -> None:
"""
url = "https://example.org/"
auth = ResponseBodyAuth("xyz")
client = AsyncClient(transport=AsyncMockTransport())
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
response = await client.get(url, auth=auth)
response = await client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": '{"auth": "xyz"}'}
@ -636,8 +649,9 @@ def test_sync_auth_reads_response_body() -> None:
"""
url = "https://example.org/"
auth = ResponseBodyAuth("xyz")
client = Client(transport=SyncMockTransport())
response = client.get(url, auth=auth)
with httpx.Client(transport=SyncMockTransport()) as client:
response = client.get(url, auth=auth)
assert response.status_code == 200
assert response.json() == {"auth": '{"auth": "xyz"}'}

View File

@ -43,39 +43,41 @@ async def raise_exc_after_response(scope, receive, send):
@pytest.mark.usefixtures("async_environment")
async def test_asgi():
client = httpx.AsyncClient(app=hello_world)
response = await client.get("http://www.example.org/")
async with httpx.AsyncClient(app=hello_world) as client:
response = await client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Hello, World!"
@pytest.mark.usefixtures("async_environment")
async def test_asgi_upload():
client = httpx.AsyncClient(app=echo_body)
response = await client.post("http://www.example.org/", data=b"example")
async with httpx.AsyncClient(app=echo_body) as client:
response = await client.post("http://www.example.org/", data=b"example")
assert response.status_code == 200
assert response.text == "example"
@pytest.mark.usefixtures("async_environment")
async def test_asgi_exc():
client = httpx.AsyncClient(app=raise_exc)
with pytest.raises(ValueError):
await client.get("http://www.example.org/")
async with httpx.AsyncClient(app=raise_exc) as client:
with pytest.raises(ValueError):
await client.get("http://www.example.org/")
@pytest.mark.usefixtures("async_environment")
async def test_asgi_http_error():
client = httpx.AsyncClient(app=partial(raise_exc, exc=RuntimeError))
with pytest.raises(RuntimeError):
await client.get("http://www.example.org/")
async with httpx.AsyncClient(app=partial(raise_exc, exc=RuntimeError)) as client:
with pytest.raises(RuntimeError):
await client.get("http://www.example.org/")
@pytest.mark.usefixtures("async_environment")
async def test_asgi_exc_after_response():
client = httpx.AsyncClient(app=raise_exc_after_response)
with pytest.raises(ValueError):
await client.get("http://www.example.org/")
async with httpx.AsyncClient(app=raise_exc_after_response) as client:
with pytest.raises(ValueError):
await client.get("http://www.example.org/")
@pytest.mark.usefixtures("async_environment")
@ -105,7 +107,8 @@ async def test_asgi_disconnect_after_response_complete():
message = await receive()
disconnect = message.get("type") == "http.disconnect"
client = httpx.AsyncClient(app=read_body)
response = await client.post("http://www.example.org/", data=b"example")
async with httpx.AsyncClient(app=read_body) as client:
response = await client.post("http://www.example.org/", data=b"example")
assert response.status_code == 200
assert disconnect

View File

@ -35,76 +35,79 @@ class MockTransport(httpcore.AsyncHTTPTransport):
@pytest.mark.parametrize(("value,output"), (("abc", b"abc"), (b"abc", b"abc")))
@pytest.mark.asyncio
async def test_multipart(value, output):
client = httpx.AsyncClient(transport=MockTransport())
async with httpx.AsyncClient(transport=MockTransport()) as client:
# Test with a single-value 'data' argument, and a plain file 'files' argument.
data = {"text": value}
files = {"file": io.BytesIO(b"<file content>")}
response = await client.post("http://127.0.0.1:8000/", data=data, files=files)
assert response.status_code == 200
# Test with a single-value 'data' argument, and a plain file 'files' argument.
data = {"text": value}
files = {"file": io.BytesIO(b"<file content>")}
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
# bit grungy, but sufficient just for our testing purposes.
boundary = response.request.headers["Content-Type"].split("boundary=")[-1]
content_length = response.request.headers["Content-Length"]
pdict: dict = {
"boundary": boundary.encode("ascii"),
"CONTENT-LENGTH": content_length,
}
multipart = cgi.parse_multipart(io.BytesIO(response.content), pdict)
# We're using the cgi module to verify the behavior here, which is a
# bit grungy, but sufficient just for our testing purposes.
boundary = response.request.headers["Content-Type"].split("boundary=")[-1]
content_length = response.request.headers["Content-Length"]
pdict: dict = {
"boundary": boundary.encode("ascii"),
"CONTENT-LENGTH": content_length,
}
multipart = cgi.parse_multipart(io.BytesIO(response.content), pdict)
# Note that the expected return type for text fields
# appears to differs from 3.6 to 3.7+
assert multipart["text"] == [output.decode()] or multipart["text"] == [output]
assert multipart["file"] == [b"<file content>"]
# Note that the expected return type for text fields
# appears to differs from 3.6 to 3.7+
assert multipart["text"] == [output.decode()] or multipart["text"] == [output]
assert multipart["file"] == [b"<file content>"]
@pytest.mark.parametrize(("key"), (b"abc", 1, 2.3, None))
@pytest.mark.asyncio
async def test_multipart_invalid_key(key):
client = httpx.AsyncClient(transport=MockTransport())
data = {key: "abc"}
files = {"file": io.BytesIO(b"<file content>")}
with pytest.raises(TypeError) as e:
await client.post("http://127.0.0.1:8000/", data=data, files=files)
assert "Invalid type for name" in str(e.value)
async with httpx.AsyncClient(transport=MockTransport()) as client:
data = {key: "abc"}
files = {"file": io.BytesIO(b"<file content>")}
with pytest.raises(TypeError) as e:
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"}))
@pytest.mark.asyncio
async def test_multipart_invalid_value(value):
client = httpx.AsyncClient(transport=MockTransport())
data = {"text": value}
files = {"file": io.BytesIO(b"<file content>")}
with pytest.raises(TypeError) as e:
await client.post("http://127.0.0.1:8000/", data=data, files=files)
assert "Invalid type for value" in str(e.value)
async with httpx.AsyncClient(transport=MockTransport()) as client:
data = {"text": value}
files = {"file": io.BytesIO(b"<file content>")}
with pytest.raises(TypeError) as e:
await client.post("http://127.0.0.1:8000/", data=data, files=files)
assert "Invalid type for value" in str(e.value)
@pytest.mark.asyncio
async def test_multipart_file_tuple():
client = httpx.AsyncClient(transport=MockTransport())
async with httpx.AsyncClient(transport=MockTransport()) as client:
# 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 = await client.post("http://127.0.0.1:8000/", data=data, files=files)
assert response.status_code == 200
# 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 = 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
# bit grungy, but sufficient just for our testing purposes.
boundary = response.request.headers["Content-Type"].split("boundary=")[-1]
content_length = response.request.headers["Content-Length"]
pdict: dict = {
"boundary": boundary.encode("ascii"),
"CONTENT-LENGTH": content_length,
}
multipart = cgi.parse_multipart(io.BytesIO(response.content), pdict)
# We're using the cgi module to verify the behavior here, which is a
# bit grungy, but sufficient just for our testing purposes.
boundary = response.request.headers["Content-Type"].split("boundary=")[-1]
content_length = response.request.headers["Content-Length"]
pdict: dict = {
"boundary": boundary.encode("ascii"),
"CONTENT-LENGTH": content_length,
}
multipart = cgi.parse_multipart(io.BytesIO(response.content), pdict)
# Note that the expected return type for text fields
# appears to differs from 3.6 to 3.7+
assert multipart["text"] == ["abc"] or multipart["text"] == [b"abc"]
assert multipart["file"] == [b"<file content>"]
# Note that the expected return type for text fields
# appears to differs from 3.6 to 3.7+
assert multipart["text"] == ["abc"] or multipart["text"] == [b"abc"]
assert multipart["file"] == [b"<file content>"]
def test_multipart_encode(tmp_path: typing.Any) -> None: