Better test case consistency. Prefer import httpx and httpx.Client. (#1222)

* Prefer httpx.Client over httpx.AsyncClient in test cases, unless required.

* Prefer httpx.Client in test_headers

* Consistent httpx imports and httpx.Client usage

* Use 'import httpx' consistently in tests. Prefer httpx.Client.
This commit is contained in:
Tom Christie 2020-08-26 14:10:23 +01:00 committed by GitHub
parent 534400ee42
commit 28c72050e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 289 additions and 329 deletions

View File

@ -2,9 +2,8 @@ import typing
from http.cookiejar import Cookie, CookieJar
import httpcore
import pytest
from httpx import AsyncClient, Cookies
import httpx
from httpx._content_streams import ByteStream, ContentStream, JSONStream
@ -16,13 +15,13 @@ def get_header_value(headers, key, default=None):
return default
class MockTransport(httpcore.AsyncHTTPTransport):
async def request(
class MockTransport(httpcore.SyncHTTPTransport):
def request(
self,
method: bytes,
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
stream: httpcore.AsyncByteStream = None,
stream: httpcore.SyncByteStream = None,
timeout: typing.Mapping[str, typing.Optional[float]] = None,
) -> typing.Tuple[
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
@ -41,23 +40,21 @@ class MockTransport(httpcore.AsyncHTTPTransport):
raise NotImplementedError() # pragma: no cover
@pytest.mark.asyncio
async def test_set_cookie() -> None:
def test_set_cookie() -> None:
"""
Send a request including a cookie.
"""
url = "http://example.org/echo_cookies"
cookies = {"example-name": "example-value"}
client = AsyncClient(transport=MockTransport())
response = await client.get(url, cookies=cookies)
client = httpx.Client(transport=MockTransport())
response = client.get(url, cookies=cookies)
assert response.status_code == 200
assert response.json() == {"cookies": "example-name=example-value"}
@pytest.mark.asyncio
async def test_set_cookie_with_cookiejar() -> None:
def test_set_cookie_with_cookiejar() -> None:
"""
Send a request including a cookie, using a `CookieJar` instance.
"""
@ -85,15 +82,14 @@ async def test_set_cookie_with_cookiejar() -> None:
)
cookies.set_cookie(cookie)
client = AsyncClient(transport=MockTransport())
response = await client.get(url, cookies=cookies)
client = httpx.Client(transport=MockTransport())
response = client.get(url, cookies=cookies)
assert response.status_code == 200
assert response.json() == {"cookies": "example-name=example-value"}
@pytest.mark.asyncio
async def test_setting_client_cookies_to_cookiejar() -> None:
def test_setting_client_cookies_to_cookiejar() -> None:
"""
Send a request including a cookie, using a `CookieJar` instance.
"""
@ -121,59 +117,56 @@ async def test_setting_client_cookies_to_cookiejar() -> None:
)
cookies.set_cookie(cookie)
client = AsyncClient(transport=MockTransport())
client = httpx.Client(transport=MockTransport())
client.cookies = cookies # type: ignore
response = await client.get(url)
response = client.get(url)
assert response.status_code == 200
assert response.json() == {"cookies": "example-name=example-value"}
@pytest.mark.asyncio
async def test_set_cookie_with_cookies_model() -> None:
def test_set_cookie_with_cookies_model() -> None:
"""
Send a request including a cookie, using a `Cookies` instance.
"""
url = "http://example.org/echo_cookies"
cookies = Cookies()
cookies = httpx.Cookies()
cookies["example-name"] = "example-value"
client = AsyncClient(transport=MockTransport())
response = await client.get(url, cookies=cookies)
client = httpx.Client(transport=MockTransport())
response = client.get(url, cookies=cookies)
assert response.status_code == 200
assert response.json() == {"cookies": "example-name=example-value"}
@pytest.mark.asyncio
async def test_get_cookie() -> None:
def test_get_cookie() -> None:
url = "http://example.org/set_cookie"
client = AsyncClient(transport=MockTransport())
response = await client.get(url)
client = httpx.Client(transport=MockTransport())
response = client.get(url)
assert response.status_code == 200
assert response.cookies["example-name"] == "example-value"
assert client.cookies["example-name"] == "example-value"
@pytest.mark.asyncio
async def test_cookie_persistence() -> None:
def test_cookie_persistence() -> None:
"""
Ensure that Client instances persist cookies between requests.
"""
client = AsyncClient(transport=MockTransport())
client = httpx.Client(transport=MockTransport())
response = await client.get("http://example.org/echo_cookies")
response = 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")
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 = await client.get("http://example.org/echo_cookies")
response = client.get("http://example.org/echo_cookies")
assert response.status_code == 200
assert response.json() == {"cookies": "example-name=example-value"}

View File

@ -5,17 +5,17 @@ import typing
import httpcore
import pytest
from httpx import AsyncClient, Headers, Request, __version__
import httpx
from httpx._content_streams import ContentStream, JSONStream
class MockTransport(httpcore.AsyncHTTPTransport):
async def request(
class MockTransport(httpcore.SyncHTTPTransport):
def request(
self,
method: bytes,
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
stream: httpcore.AsyncByteStream = None,
stream: httpcore.SyncByteStream = None,
timeout: typing.Mapping[str, typing.Optional[float]] = None,
) -> typing.Tuple[
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
@ -28,16 +28,15 @@ class MockTransport(httpcore.AsyncHTTPTransport):
return b"HTTP/1.1", 200, b"OK", [], body
@pytest.mark.asyncio
async def test_client_header():
def test_client_header():
"""
Set a header in the Client.
"""
url = "http://example.org/echo_headers"
headers = {"Example-Header": "example-value"}
client = AsyncClient(transport=MockTransport(), headers=headers)
response = await client.get(url)
client = httpx.Client(transport=MockTransport(), headers=headers)
response = client.get(url)
assert response.status_code == 200
assert response.json() == {
@ -47,18 +46,17 @@ async def test_client_header():
"connection": "keep-alive",
"example-header": "example-value",
"host": "example.org",
"user-agent": f"python-httpx/{__version__}",
"user-agent": f"python-httpx/{httpx.__version__}",
}
}
@pytest.mark.asyncio
async def test_header_merge():
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"}
client = AsyncClient(transport=MockTransport(), headers=client_headers)
response = await client.get(url, headers=request_headers)
client = httpx.Client(transport=MockTransport(), headers=client_headers)
response = client.get(url, headers=request_headers)
assert response.status_code == 200
assert response.json() == {
@ -73,13 +71,12 @@ async def test_header_merge():
}
@pytest.mark.asyncio
async def test_header_merge_conflicting_headers():
def test_header_merge_conflicting_headers():
url = "http://example.org/echo_headers"
client_headers = {"X-Auth-Token": "FooBar"}
request_headers = {"X-Auth-Token": "BazToken"}
client = AsyncClient(transport=MockTransport(), headers=client_headers)
response = await client.get(url, headers=request_headers)
client = httpx.Client(transport=MockTransport(), headers=client_headers)
response = client.get(url, headers=request_headers)
assert response.status_code == 200
assert response.json() == {
@ -88,21 +85,20 @@ async def test_header_merge_conflicting_headers():
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"host": "example.org",
"user-agent": f"python-httpx/{__version__}",
"user-agent": f"python-httpx/{httpx.__version__}",
"x-auth-token": "BazToken",
}
}
@pytest.mark.asyncio
async def test_header_update():
def test_header_update():
url = "http://example.org/echo_headers"
client = AsyncClient(transport=MockTransport())
first_response = await client.get(url)
client = httpx.Client(transport=MockTransport())
first_response = client.get(url)
client.headers.update(
{"User-Agent": "python-myclient/0.2.1", "Another-Header": "AThing"}
)
second_response = await client.get(url)
second_response = client.get(url)
assert first_response.status_code == 200
assert first_response.json() == {
@ -111,7 +107,7 @@ async def test_header_update():
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"host": "example.org",
"user-agent": f"python-httpx/{__version__}",
"user-agent": f"python-httpx/{httpx.__version__}",
}
}
@ -129,13 +125,12 @@ async def test_header_update():
def test_header_does_not_exist():
headers = Headers({"foo": "bar"})
headers = httpx.Headers({"foo": "bar"})
with pytest.raises(KeyError):
del headers["baz"]
@pytest.mark.asyncio
async def test_host_with_auth_and_port_in_url():
def test_host_with_auth_and_port_in_url():
"""
The Host header should only include the hostname, or hostname:port
(for non-default ports only). Any userinfo or default port should not
@ -143,8 +138,8 @@ async def test_host_with_auth_and_port_in_url():
"""
url = "http://username:password@example.org:80/echo_headers"
client = AsyncClient(transport=MockTransport())
response = await client.get(url)
client = httpx.Client(transport=MockTransport())
response = client.get(url)
assert response.status_code == 200
assert response.json() == {
@ -153,22 +148,21 @@ async def test_host_with_auth_and_port_in_url():
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"host": "example.org",
"user-agent": f"python-httpx/{__version__}",
"user-agent": f"python-httpx/{httpx.__version__}",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
}
}
@pytest.mark.asyncio
async def test_host_with_non_default_port_in_url():
def test_host_with_non_default_port_in_url():
"""
If the URL includes a non-default port, then it should be included in
the Host header.
"""
url = "http://username:password@example.org:123/echo_headers"
client = AsyncClient(transport=MockTransport())
response = await client.get(url)
client = httpx.Client(transport=MockTransport())
response = client.get(url)
assert response.status_code == 200
assert response.json() == {
@ -177,12 +171,12 @@ async def test_host_with_non_default_port_in_url():
"accept-encoding": "gzip, deflate, br",
"connection": "keep-alive",
"host": "example.org:123",
"user-agent": f"python-httpx/{__version__}",
"user-agent": f"python-httpx/{httpx.__version__}",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
}
}
def test_request_auto_headers():
request = Request("GET", "https://www.example.org/")
request = httpx.Request("GET", "https://www.example.org/")
assert "host" in request.headers

View File

@ -1,38 +1,38 @@
from httpx import URL, AsyncClient, Cookies, Headers, Timeout
import httpx
def test_client_base_url():
client = AsyncClient()
client = httpx.Client()
client.base_url = "https://www.example.org/" # type: ignore
assert isinstance(client.base_url, URL)
assert client.base_url == URL("https://www.example.org/")
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == httpx.URL("https://www.example.org/")
def test_client_base_url_without_trailing_slash():
client = AsyncClient()
client = httpx.Client()
client.base_url = "https://www.example.org/path" # type: ignore
assert isinstance(client.base_url, URL)
assert client.base_url == URL("https://www.example.org/path/")
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == httpx.URL("https://www.example.org/path/")
def test_client_base_url_with_trailing_slash():
client = AsyncClient()
client = httpx.Client()
client.base_url = "https://www.example.org/path/" # type: ignore
assert isinstance(client.base_url, URL)
assert client.base_url == URL("https://www.example.org/path/")
assert isinstance(client.base_url, httpx.URL)
assert client.base_url == httpx.URL("https://www.example.org/path/")
def test_client_headers():
client = AsyncClient()
client = httpx.Client()
client.headers = {"a": "b"} # type: ignore
assert isinstance(client.headers, Headers)
assert isinstance(client.headers, httpx.Headers)
assert client.headers["A"] == "b"
def test_client_cookies():
client = AsyncClient()
client = httpx.Client()
client.cookies = {"a": "b"} # type: ignore
assert isinstance(client.cookies, Cookies)
assert isinstance(client.cookies, httpx.Cookies)
mycookies = list(client.cookies.jar)
assert len(mycookies) == 1
assert mycookies[0].name == "a" and mycookies[0].value == "b"
@ -40,11 +40,11 @@ def test_client_cookies():
def test_client_timeout():
expected_timeout = 12.0
client = AsyncClient()
client = httpx.Client()
client.timeout = expected_timeout # type: ignore
assert isinstance(client.timeout, Timeout)
assert isinstance(client.timeout, httpx.Timeout)
assert client.timeout.connect == expected_timeout
assert client.timeout.read == expected_timeout
assert client.timeout.write == expected_timeout

View File

@ -37,13 +37,13 @@ def url_to_origin(url: str):
],
)
def test_proxies_parameter(proxies, expected_proxies):
client = httpx.AsyncClient(proxies=proxies)
client = httpx.Client(proxies=proxies)
for proxy_key, url in expected_proxies:
pattern = URLPattern(proxy_key)
assert pattern in client._proxies
proxy = client._proxies[pattern]
assert isinstance(proxy, httpcore.AsyncHTTPProxy)
assert isinstance(proxy, httpcore.SyncHTTPProxy)
assert proxy.proxy_origin == url_to_origin(url)
assert len(expected_proxies) == len(client._proxies)
@ -110,30 +110,34 @@ PROXY_URL = "http://[::1]"
],
)
def test_transport_for_request(url, proxies, expected):
client = httpx.AsyncClient(proxies=proxies)
client = httpx.Client(proxies=proxies)
transport = client._transport_for_url(httpx.URL(url))
if expected is None:
assert transport is client._transport
else:
assert isinstance(transport, httpcore.AsyncHTTPProxy)
assert isinstance(transport, httpcore.SyncHTTPProxy)
assert transport.proxy_origin == url_to_origin(expected)
@pytest.mark.asyncio
async def test_async_proxy_close():
client = httpx.AsyncClient(proxies={"all://": PROXY_URL})
await client.aclose()
try:
client = httpx.AsyncClient(proxies={"all://": PROXY_URL})
finally:
await client.aclose()
def test_sync_proxy_close():
client = httpx.Client(proxies={"all://": PROXY_URL})
client.close()
try:
client = httpx.Client(proxies={"all://": PROXY_URL})
finally:
client.close()
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")
@pytest.mark.parametrize(
@ -257,7 +261,7 @@ def test_proxies_environ(monkeypatch, client_class, url, env, expected):
)
def test_for_deprecated_proxy_params(proxies, expected_scheme):
with pytest.deprecated_call() as block:
httpx.AsyncClient(proxies=proxies)
httpx.Client(proxies=proxies)
warning_message = str(block.pop(DeprecationWarning))

View File

@ -1,19 +1,18 @@
import typing
import httpcore
import pytest
from httpx import URL, AsyncClient, QueryParams
import httpx
from httpx._content_streams import ContentStream, JSONStream
class MockTransport(httpcore.AsyncHTTPTransport):
async def request(
class MockTransport(httpcore.SyncHTTPTransport):
def request(
self,
method: bytes,
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
stream: httpcore.AsyncByteStream = None,
stream: httpcore.SyncByteStream = None,
timeout: typing.Mapping[str, typing.Optional[float]] = None,
) -> typing.Tuple[
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
@ -23,31 +22,30 @@ class MockTransport(httpcore.AsyncHTTPTransport):
def test_client_queryparams():
client = AsyncClient(params={"a": "b"})
assert isinstance(client.params, QueryParams)
client = httpx.Client(params={"a": "b"})
assert isinstance(client.params, httpx.QueryParams)
assert client.params["a"] == "b"
def test_client_queryparams_string():
client = AsyncClient(params="a=b")
assert isinstance(client.params, QueryParams)
client = httpx.Client(params="a=b")
assert isinstance(client.params, httpx.QueryParams)
assert client.params["a"] == "b"
client = AsyncClient()
client = httpx.Client()
client.params = "a=b" # type: ignore
assert isinstance(client.params, QueryParams)
assert isinstance(client.params, httpx.QueryParams)
assert client.params["a"] == "b"
@pytest.mark.asyncio
async def test_client_queryparams_echo():
def test_client_queryparams_echo():
url = "http://example.org/echo_queryparams"
client_queryparams = "first=str"
request_queryparams = {"second": "dict"}
client = AsyncClient(transport=MockTransport(), params=client_queryparams)
response = await client.get(url, params=request_queryparams)
client = httpx.Client(transport=MockTransport(), params=client_queryparams)
response = client.get(url, params=request_queryparams)
assert response.status_code == 200
assert response.url == URL(
assert response.url == httpx.URL(
"http://example.org/echo_queryparams?first=str&second=dict"
)

View File

@ -5,18 +5,8 @@ from urllib.parse import parse_qs
import httpcore
import pytest
from httpx import (
URL,
AsyncClient,
Client,
NotRedirectResponse,
RemoteProtocolError,
RequestBodyUnavailable,
TooManyRedirects,
UnsupportedProtocol,
codes,
)
from httpx._content_streams import AsyncIteratorStream, ByteStream, ContentStream
import httpx
from httpx._content_streams import ByteStream, ContentStream, IteratorStream
def get_header_value(headers, key, default=None):
@ -44,45 +34,45 @@ class MockTransport:
path, _, query = path.partition(b"?")
if path == b"/no_redirect":
return b"HTTP/1.1", codes.OK, b"OK", [], ByteStream(b"")
return b"HTTP/1.1", httpx.codes.OK, b"OK", [], ByteStream(b"")
elif path == b"/redirect_301":
async def body():
def body():
yield b"<a href='https://example.org/'>here</a>"
status_code = codes.MOVED_PERMANENTLY
status_code = httpx.codes.MOVED_PERMANENTLY
headers = [(b"location", b"https://example.org/")]
stream = AsyncIteratorStream(aiterator=body())
stream = IteratorStream(iterator=body())
return b"HTTP/1.1", status_code, b"Moved Permanently", headers, stream
elif path == b"/redirect_302":
status_code = codes.FOUND
status_code = httpx.codes.FOUND
headers = [(b"location", b"https://example.org/")]
return b"HTTP/1.1", status_code, b"Found", headers, ByteStream(b"")
elif path == b"/redirect_303":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [(b"location", b"https://example.org/")]
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
elif path == b"/relative_redirect":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [(b"location", b"/")]
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
elif path == b"/malformed_redirect":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [(b"location", b"https://:443/")]
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
elif path == b"/invalid_redirect":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [(b"location", "https://😇/".encode("utf-8"))]
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
elif path == b"/no_scheme_redirect":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [(b"location", b"//example.org/")]
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
@ -90,7 +80,7 @@ class MockTransport:
params = parse_qs(query.decode("ascii"))
count = int(params.get("count", "0")[0])
redirect_count = count - 1
code = codes.SEE_OTHER if count else codes.OK
code = httpx.codes.SEE_OTHER if count else httpx.codes.OK
phrase = b"See Other" if count else b"OK"
location = b"/multiple_redirects"
if redirect_count:
@ -99,12 +89,12 @@ class MockTransport:
return b"HTTP/1.1", code, phrase, headers, ByteStream(b"")
if path == b"/redirect_loop":
code = codes.SEE_OTHER
code = httpx.codes.SEE_OTHER
headers = [(b"location", b"/redirect_loop")]
return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"")
elif path == b"/cross_domain":
code = codes.SEE_OTHER
code = httpx.codes.SEE_OTHER
headers = [(b"location", b"https://example.org/cross_domain_target")]
return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"")
@ -116,12 +106,12 @@ class MockTransport:
return b"HTTP/1.1", 200, b"OK", [], stream
elif path == b"/redirect_body":
code = codes.PERMANENT_REDIRECT
code = httpx.codes.PERMANENT_REDIRECT
headers = [(b"location", b"/redirect_body_target")]
return b"HTTP/1.1", code, b"Permanent Redirect", headers, ByteStream(b"")
elif path == b"/redirect_no_body":
code = codes.SEE_OTHER
code = httpx.codes.SEE_OTHER
headers = [(b"location", b"/redirect_body_target")]
return b"HTTP/1.1", code, b"See Other", headers, ByteStream(b"")
@ -141,7 +131,7 @@ class MockTransport:
headers = [(b"location", b"https://www.example.org/cross_subdomain")]
return (
b"HTTP/1.1",
codes.PERMANENT_REDIRECT,
httpx.codes.PERMANENT_REDIRECT,
b"Permanent Redirect",
headers,
ByteStream(b""),
@ -150,7 +140,7 @@ class MockTransport:
return b"HTTP/1.1", 200, b"OK", [], ByteStream(b"Hello, world!")
elif path == b"/redirect_custom_scheme":
status_code = codes.MOVED_PERMANENTLY
status_code = httpx.codes.MOVED_PERMANENTLY
headers = [(b"location", b"market://details?id=42")]
return (
b"HTTP/1.1",
@ -183,130 +173,116 @@ class SyncMockTransport(MockTransport, httpcore.SyncHTTPTransport):
return self._request(*args, **kwargs)
@pytest.mark.usefixtures("async_environment")
async def test_no_redirect():
client = AsyncClient(transport=AsyncMockTransport())
def test_no_redirect():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.com/no_redirect"
response = await client.get(url)
response = client.get(url)
assert response.status_code == 200
with pytest.raises(NotRedirectResponse):
await response.anext()
with pytest.raises(httpx.NotRedirectResponse):
response.next()
@pytest.mark.usefixtures("async_environment")
async def test_redirect_301():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.post("https://example.org/redirect_301")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
def test_redirect_301():
client = httpx.Client(transport=SyncMockTransport())
response = client.post("https://example.org/redirect_301")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_redirect_302():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.post("https://example.org/redirect_302")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
def test_redirect_302():
client = httpx.Client(transport=SyncMockTransport())
response = client.post("https://example.org/redirect_302")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_redirect_303():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get("https://example.org/redirect_303")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
def test_redirect_303():
client = httpx.Client(transport=SyncMockTransport())
response = client.get("https://example.org/redirect_303")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_disallow_redirects():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.post(
"https://example.org/redirect_303", allow_redirects=False
)
assert response.status_code == codes.SEE_OTHER
assert response.url == URL("https://example.org/redirect_303")
def test_disallow_redirects():
client = httpx.Client(transport=SyncMockTransport())
response = client.post("https://example.org/redirect_303", allow_redirects=False)
assert response.status_code == httpx.codes.SEE_OTHER
assert response.url == httpx.URL("https://example.org/redirect_303")
assert response.is_redirect is True
assert len(response.history) == 0
response = await response.anext()
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
response = response.next()
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert response.is_redirect is False
assert len(response.history) == 1
@pytest.mark.asyncio
async def test_head_redirect():
def test_head_redirect():
"""
Contrary to Requests, redirects remain enabled by default for HEAD requests.
"""
client = AsyncClient(transport=AsyncMockTransport())
response = await client.head("https://example.org/redirect_302")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
client = httpx.Client(transport=SyncMockTransport())
response = client.head("https://example.org/redirect_302")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert response.request.method == "HEAD"
assert len(response.history) == 1
assert response.text == ""
@pytest.mark.usefixtures("async_environment")
async def test_relative_redirect():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get("https://example.org/relative_redirect")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
def test_relative_redirect():
client = httpx.Client(transport=SyncMockTransport())
response = client.get("https://example.org/relative_redirect")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_malformed_redirect():
def test_malformed_redirect():
# https://github.com/encode/httpx/issues/771
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get("http://example.org/malformed_redirect")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org:443/")
client = httpx.Client(transport=SyncMockTransport())
response = client.get("http://example.org/malformed_redirect")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org:443/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_invalid_redirect():
client = AsyncClient(transport=AsyncMockTransport())
with pytest.raises(RemoteProtocolError):
await client.get("http://example.org/invalid_redirect")
def test_invalid_redirect():
client = httpx.Client(transport=SyncMockTransport())
with pytest.raises(httpx.RemoteProtocolError):
client.get("http://example.org/invalid_redirect")
@pytest.mark.usefixtures("async_environment")
async def test_no_scheme_redirect():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get("https://example.org/no_scheme_redirect")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/")
def test_no_scheme_redirect():
client = httpx.Client(transport=SyncMockTransport())
response = client.get("https://example.org/no_scheme_redirect")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_fragment_redirect():
client = AsyncClient(transport=AsyncMockTransport())
response = await client.get("https://example.org/relative_redirect#fragment")
assert response.status_code == codes.OK
assert response.url == URL("https://example.org/#fragment")
def test_fragment_redirect():
client = httpx.Client(transport=SyncMockTransport())
response = client.get("https://example.org/relative_redirect#fragment")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/#fragment")
assert len(response.history) == 1
@pytest.mark.usefixtures("async_environment")
async def test_multiple_redirects():
client = AsyncClient(transport=AsyncMockTransport())
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")
def test_multiple_redirects():
client = httpx.Client(transport=SyncMockTransport())
response = client.get("https://example.org/multiple_redirects?count=20")
assert response.status_code == httpx.codes.OK
assert response.url == httpx.URL("https://example.org/multiple_redirects")
assert len(response.history) == 20
assert response.history[0].url == URL(
assert response.history[0].url == httpx.URL(
"https://example.org/multiple_redirects?count=20"
)
assert response.history[1].url == URL(
assert response.history[1].url == httpx.URL(
"https://example.org/multiple_redirects?count=19"
)
assert len(response.history[0].history) == 0
@ -315,128 +291,120 @@ async def test_multiple_redirects():
@pytest.mark.usefixtures("async_environment")
async def test_async_too_many_redirects():
client = AsyncClient(transport=AsyncMockTransport())
with pytest.raises(TooManyRedirects):
await client.get("https://example.org/multiple_redirects?count=21")
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
with pytest.raises(httpx.TooManyRedirects):
await client.get("https://example.org/multiple_redirects?count=21")
@pytest.mark.usefixtures("async_environment")
async def test_async_too_many_redirects_calling_next():
client = AsyncClient(transport=AsyncMockTransport())
url = "https://example.org/multiple_redirects?count=21"
response = await client.get(url, allow_redirects=False)
with pytest.raises(TooManyRedirects):
while response.is_redirect:
response = await response.anext()
async with httpx.AsyncClient(transport=AsyncMockTransport()) as client:
url = "https://example.org/multiple_redirects?count=21"
response = await client.get(url, allow_redirects=False)
with pytest.raises(httpx.TooManyRedirects):
while response.is_redirect:
response = await response.anext()
def test_sync_too_many_redirects():
client = Client(transport=SyncMockTransport())
with pytest.raises(TooManyRedirects):
client = httpx.Client(transport=SyncMockTransport())
with pytest.raises(httpx.TooManyRedirects):
client.get("https://example.org/multiple_redirects?count=21")
def test_sync_too_many_redirects_calling_next():
client = Client(transport=SyncMockTransport())
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/multiple_redirects?count=21"
response = client.get(url, allow_redirects=False)
with pytest.raises(TooManyRedirects):
with pytest.raises(httpx.TooManyRedirects):
while response.is_redirect:
response = response.next()
@pytest.mark.usefixtures("async_environment")
async def test_redirect_loop():
client = AsyncClient(transport=AsyncMockTransport())
with pytest.raises(TooManyRedirects):
await client.get("https://example.org/redirect_loop")
def test_redirect_loop():
client = httpx.Client(transport=SyncMockTransport())
with pytest.raises(httpx.TooManyRedirects):
client.get("https://example.org/redirect_loop")
@pytest.mark.usefixtures("async_environment")
async def test_cross_domain_redirect():
client = AsyncClient(transport=AsyncMockTransport())
def test_cross_domain_redirect():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.com/cross_domain"
headers = {"Authorization": "abc"}
response = await client.get(url, headers=headers)
assert response.url == URL("https://example.org/cross_domain_target")
response = client.get(url, headers=headers)
assert response.url == httpx.URL("https://example.org/cross_domain_target")
assert "authorization" not in response.json()["headers"]
@pytest.mark.usefixtures("async_environment")
async def test_same_domain_redirect():
client = AsyncClient(transport=AsyncMockTransport())
def test_same_domain_redirect():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/cross_domain"
headers = {"Authorization": "abc"}
response = await client.get(url, headers=headers)
assert response.url == URL("https://example.org/cross_domain_target")
response = client.get(url, headers=headers)
assert response.url == httpx.URL("https://example.org/cross_domain_target")
assert response.json()["headers"]["authorization"] == "abc"
@pytest.mark.usefixtures("async_environment")
async def test_body_redirect():
def test_body_redirect():
"""
A 308 redirect should preserve the request body.
"""
client = AsyncClient(transport=AsyncMockTransport())
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/redirect_body"
data = b"Example request body"
response = await client.post(url, data=data)
assert response.url == URL("https://example.org/redirect_body_target")
response = client.post(url, data=data)
assert response.url == httpx.URL("https://example.org/redirect_body_target")
assert response.json()["body"] == "Example request body"
assert "content-length" in response.json()["headers"]
@pytest.mark.usefixtures("async_environment")
async def test_no_body_redirect():
def test_no_body_redirect():
"""
A 303 redirect should remove the request body.
"""
client = AsyncClient(transport=AsyncMockTransport())
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/redirect_no_body"
data = b"Example request body"
response = await client.post(url, data=data)
assert response.url == URL("https://example.org/redirect_body_target")
response = client.post(url, data=data)
assert response.url == httpx.URL("https://example.org/redirect_body_target")
assert response.json()["body"] == ""
assert "content-length" not in response.json()["headers"]
@pytest.mark.usefixtures("async_environment")
async def test_can_stream_if_no_redirect():
client = AsyncClient(transport=AsyncMockTransport())
def test_can_stream_if_no_redirect():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/redirect_301"
async with client.stream("GET", url, allow_redirects=False) as response:
with client.stream("GET", url, allow_redirects=False) as response:
assert not response.is_closed
assert response.status_code == codes.MOVED_PERMANENTLY
assert response.status_code == httpx.codes.MOVED_PERMANENTLY
assert response.headers["location"] == "https://example.org/"
@pytest.mark.usefixtures("async_environment")
async def test_cannot_redirect_streaming_body():
client = AsyncClient(transport=AsyncMockTransport())
def test_cannot_redirect_streaming_body():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.org/redirect_body"
async def streaming_body():
def streaming_body():
yield b"Example request body" # pragma: nocover
with pytest.raises(RequestBodyUnavailable):
await client.post(url, data=streaming_body())
with pytest.raises(httpx.RequestBodyUnavailable):
client.post(url, data=streaming_body())
@pytest.mark.usefixtures("async_environment")
async def test_cross_subdomain_redirect():
client = AsyncClient(transport=AsyncMockTransport())
def test_cross_subdomain_redirect():
client = httpx.Client(transport=SyncMockTransport())
url = "https://example.com/cross_subdomain"
response = await client.get(url)
assert response.url == URL("https://www.example.org/cross_subdomain")
response = client.get(url)
assert response.url == httpx.URL("https://www.example.org/cross_subdomain")
class MockCookieTransport(httpcore.AsyncHTTPTransport):
async def request(
class MockCookieTransport(httpcore.SyncHTTPTransport):
def request(
self,
method: bytes,
url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
headers: typing.List[typing.Tuple[bytes, bytes]] = None,
stream: httpcore.AsyncByteStream = None,
stream: httpcore.SyncByteStream = None,
timeout: typing.Mapping[str, typing.Optional[float]] = None,
) -> typing.Tuple[
bytes, int, bytes, typing.List[typing.Tuple[bytes, bytes]], ContentStream
@ -451,7 +419,7 @@ class MockCookieTransport(httpcore.AsyncHTTPTransport):
return b"HTTP/1.1", 200, b"OK", [], ByteStream(content)
elif path == b"/login":
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [
(b"location", b"/"),
(
@ -466,7 +434,7 @@ class MockCookieTransport(httpcore.AsyncHTTPTransport):
else:
assert path == b"/logout"
status_code = codes.SEE_OTHER
status_code = httpx.codes.SEE_OTHER
headers = [
(b"location", b"/"),
(
@ -480,39 +448,37 @@ class MockCookieTransport(httpcore.AsyncHTTPTransport):
return b"HTTP/1.1", status_code, b"See Other", headers, ByteStream(b"")
@pytest.mark.usefixtures("async_environment")
async def test_redirect_cookie_behavior():
client = AsyncClient(transport=MockCookieTransport())
def test_redirect_cookie_behavior():
client = httpx.Client(transport=MockCookieTransport())
# The client is not logged in.
response = await client.get("https://example.com/")
response = client.get("https://example.com/")
assert response.url == "https://example.com/"
assert response.text == "Not logged in"
# Login redirects to the homepage, setting a session cookie.
response = await client.post("https://example.com/login")
response = client.post("https://example.com/login")
assert response.url == "https://example.com/"
assert response.text == "Logged in"
# The client is logged in.
response = await client.get("https://example.com/")
response = client.get("https://example.com/")
assert response.url == "https://example.com/"
assert response.text == "Logged in"
# Logout redirects to the homepage, expiring the session cookie.
response = await client.post("https://example.com/logout")
response = client.post("https://example.com/logout")
assert response.url == "https://example.com/"
assert response.text == "Not logged in"
# The client is not logged in.
response = await client.get("https://example.com/")
response = client.get("https://example.com/")
assert response.url == "https://example.com/"
assert response.text == "Not logged in"
@pytest.mark.usefixtures("async_environment")
async def test_redirect_custom_scheme():
client = AsyncClient(transport=AsyncMockTransport())
with pytest.raises(UnsupportedProtocol) as e:
await client.post("https://example.org/redirect_custom_scheme")
def test_redirect_custom_scheme():
client = httpx.Client(transport=SyncMockTransport())
with pytest.raises(httpx.UnsupportedProtocol) as e:
client.post("https://example.org/redirect_custom_scheme")
assert str(e.value) == "Scheme b'market' not supported."

View File

@ -1,6 +1,6 @@
import pytest
from httpx import QueryParams
import httpx
@pytest.mark.parametrize(
@ -12,7 +12,7 @@ from httpx import QueryParams
],
)
def test_queryparams(source):
q = QueryParams(source)
q = httpx.QueryParams(source)
assert "a" in q
assert "A" not in q
assert "c" not in q
@ -32,59 +32,63 @@ def test_queryparams(source):
assert dict(q) == {"a": "456", "b": "789"}
assert str(q) == "a=123&a=456&b=789"
assert repr(q) == "QueryParams('a=123&a=456&b=789')"
assert QueryParams({"a": "123", "b": "456"}) == QueryParams(
assert httpx.QueryParams({"a": "123", "b": "456"}) == httpx.QueryParams(
[("a", "123"), ("b", "456")]
)
assert QueryParams({"a": "123", "b": "456"}) == QueryParams("a=123&b=456")
assert QueryParams({"a": "123", "b": "456"}) == QueryParams(
assert httpx.QueryParams({"a": "123", "b": "456"}) == httpx.QueryParams(
"a=123&b=456"
)
assert httpx.QueryParams({"a": "123", "b": "456"}) == httpx.QueryParams(
{"b": "456", "a": "123"}
)
assert QueryParams() == QueryParams({})
assert QueryParams([("a", "123"), ("a", "456")]) == QueryParams("a=123&a=456")
assert QueryParams({"a": "123", "b": "456"}) != "invalid"
assert httpx.QueryParams() == httpx.QueryParams({})
assert httpx.QueryParams([("a", "123"), ("a", "456")]) == httpx.QueryParams(
"a=123&a=456"
)
assert httpx.QueryParams({"a": "123", "b": "456"}) != "invalid"
q = QueryParams([("a", "123"), ("a", "456")])
assert QueryParams(q) == q
q = httpx.QueryParams([("a", "123"), ("a", "456")])
assert httpx.QueryParams(q) == q
def test_queryparam_types():
q = QueryParams(None)
q = httpx.QueryParams(None)
assert str(q) == ""
q = QueryParams({"a": True})
q = httpx.QueryParams({"a": True})
assert str(q) == "a=true"
q = QueryParams({"a": False})
q = httpx.QueryParams({"a": False})
assert str(q) == "a=false"
q = QueryParams({"a": ""})
q = httpx.QueryParams({"a": ""})
assert str(q) == "a="
q = QueryParams({"a": None})
q = httpx.QueryParams({"a": None})
assert str(q) == "a="
q = QueryParams({"a": 1.23})
q = httpx.QueryParams({"a": 1.23})
assert str(q) == "a=1.23"
q = QueryParams({"a": 123})
q = httpx.QueryParams({"a": 123})
assert str(q) == "a=123"
q = QueryParams({"a": [1, 2]})
q = httpx.QueryParams({"a": [1, 2]})
assert str(q) == "a=1&a=2"
def test_queryparam_setters():
q = QueryParams({"a": 1})
q = httpx.QueryParams({"a": 1})
q.update([])
assert str(q) == "a=1"
q = QueryParams([("a", 1), ("a", 2)])
q = httpx.QueryParams([("a", 1), ("a", 2)])
q["a"] = "3"
assert str(q) == "a=3"
q = QueryParams([("a", 1), ("b", 1)])
u = QueryParams([("b", 2), ("b", 3)])
q = httpx.QueryParams([("a", 1), ("b", 1)])
u = httpx.QueryParams([("b", 2), ("b", 3)])
q.update(u)
assert str(q) == "a=1&b=2&b=3"

View File

@ -20,8 +20,7 @@ async def async_streaming_body():
yield b"world!"
@pytest.mark.asyncio
async def test_response():
def test_response():
response = httpx.Response(200, content=b"Hello, world!", request=REQUEST)
assert response.status_code == 200

View File

@ -1,6 +1,6 @@
import pytest
from httpx import URL, InvalidURL
import httpx
@pytest.mark.parametrize(
@ -53,15 +53,15 @@ from httpx import URL, InvalidURL
],
)
def test_idna_url(given, idna, host, scheme, port):
url = URL(given)
assert url == URL(idna)
url = httpx.URL(given)
assert url == httpx.URL(idna)
assert url.host == host
assert url.scheme == scheme
assert url.port == port
def test_url():
url = URL("https://example.org:123/path/to/somewhere?abc=123#anchor")
url = httpx.URL("https://example.org:123/path/to/somewhere?abc=123#anchor")
assert url.scheme == "https"
assert url.host == "example.org"
assert url.port == 123
@ -74,21 +74,23 @@ def test_url():
)
new = url.copy_with(scheme="http", port=None)
assert new == URL("http://example.org/path/to/somewhere?abc=123#anchor")
assert new == httpx.URL("http://example.org/path/to/somewhere?abc=123#anchor")
assert new.scheme == "http"
def test_url_eq_str():
url = URL("https://example.org:123/path/to/somewhere?abc=123#anchor")
url = httpx.URL("https://example.org:123/path/to/somewhere?abc=123#anchor")
assert url == "https://example.org:123/path/to/somewhere?abc=123#anchor"
assert str(url) == url
def test_url_params():
url = URL("https://example.org:123/path/to/somewhere", params={"a": "123"})
url = httpx.URL("https://example.org:123/path/to/somewhere", params={"a": "123"})
assert str(url) == "https://example.org:123/path/to/somewhere?a=123"
url = URL("https://example.org:123/path/to/somewhere?b=456", params={"a": "123"})
url = httpx.URL(
"https://example.org:123/path/to/somewhere?b=456", params={"a": "123"}
)
assert str(url) == "https://example.org:123/path/to/somewhere?b=456&a=123"
@ -96,7 +98,7 @@ def test_url_join():
"""
Some basic URL joining tests.
"""
url = URL("https://example.org:123/path/to/somewhere")
url = httpx.URL("https://example.org:123/path/to/somewhere")
assert url.join("/somewhere-else") == "https://example.org:123/somewhere-else"
assert (
url.join("somewhere-else") == "https://example.org:123/path/to/somewhere-else"
@ -114,7 +116,7 @@ def test_url_join_rfc3986():
https://tools.ietf.org/html/rfc3986#section-5.4
"""
url = URL("http://example.com/b/c/d;p?q")
url = httpx.URL("http://example.com/b/c/d;p?q")
assert url.join("g") == "http://example.com/b/c/g"
assert url.join("./g") == "http://example.com/b/c/g"
@ -164,8 +166,8 @@ def test_url_join_rfc3986():
def test_url_set():
urls = (
URL("http://example.org:123/path/to/somewhere"),
URL("http://example.org:123/path/to/somewhere/else"),
httpx.URL("http://example.org:123/path/to/somewhere"),
httpx.URL("http://example.org:123/path/to/somewhere/else"),
)
url_set = set(urls)
@ -180,7 +182,7 @@ def test_url_copywith_for_authority():
"port": 444,
"host": "example.net",
}
url = URL("https://example.org")
url = httpx.URL("https://example.org")
new = url.copy_with(**copy_with_kwargs)
for k, v in copy_with_kwargs.items():
assert getattr(new, k) == v
@ -192,7 +194,7 @@ def test_url_copywith_for_userinfo():
"username": "tom@example.org",
"password": "abc123@ %",
}
url = URL("https://example.org")
url = httpx.URL("https://example.org")
new = url.copy_with(**copy_with_kwargs)
assert str(new) == "https://tom%40example.org:abc123%40%20%25@example.org"
assert new.username == "tom@example.org"
@ -200,5 +202,5 @@ def test_url_copywith_for_userinfo():
def test_url_invalid():
with pytest.raises(InvalidURL):
URL("https://😇/")
with pytest.raises(httpx.InvalidURL):
httpx.URL("https://😇/")