Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate app=... in favor of explicit WSGITransport/ASGITransport. #3050

Merged
merged 16 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions httpx/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@
)
from ._models import Cookies, Headers, Request, Response
from ._status_codes import codes
from ._transports.asgi import ASGITransport
from ._transports.base import AsyncBaseTransport, BaseTransport
from ._transports.default import AsyncHTTPTransport, HTTPTransport
from ._transports.wsgi import WSGITransport
from ._types import (
AsyncByteStream,
AuthTypes,
Expand Down Expand Up @@ -612,8 +610,7 @@ class Client(BaseClient):
request URLs.
* **transport** - *(optional)* A transport class to use for sending requests
over the network.
* **app** - *(optional)* An WSGI application to send requests to,
rather than sending actual network requests.
* **app** - DEPRECATED
* **trust_env** - *(optional)* Enables or disables usage of environment
variables for configuration.
* **default_encoding** - *(optional)* The default encoding to use for decoding
Expand Down Expand Up @@ -646,7 +643,7 @@ def __init__(
] = None,
base_url: URLTypes = "",
transport: typing.Optional[BaseTransport] = None,
app: typing.Optional[typing.Callable[..., typing.Any]] = None,
app: typing.Any = None,
trust_env: bool = True,
default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
) -> None:
Expand Down Expand Up @@ -682,7 +679,13 @@ def __init__(
if proxy:
raise RuntimeError("Use either `proxy` or 'proxies', not both.")

allow_env_proxies = trust_env and app is None and transport is None
if app:
raise RuntimeError(
tomchristie marked this conversation as resolved.
Show resolved Hide resolved
"'app=...' is deprecated. "
"Use 'transport=httpx.WSGITransport(app=...).'"
)

allow_env_proxies = trust_env and transport is None
proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)

self._transport = self._init_transport(
Expand All @@ -692,7 +695,6 @@ def __init__(
http2=http2,
limits=limits,
transport=transport,
app=app,
trust_env=trust_env,
)
self._mounts: typing.Dict[URLPattern, typing.Optional[BaseTransport]] = {
Expand Down Expand Up @@ -724,15 +726,11 @@ def _init_transport(
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
transport: typing.Optional[BaseTransport] = None,
app: typing.Optional[typing.Callable[..., typing.Any]] = None,
trust_env: bool = True,
) -> BaseTransport:
if transport is not None:
return transport

if app is not None:
return WSGITransport(app=app)

return HTTPTransport(
verify=verify,
cert=cert,
Expand Down Expand Up @@ -1354,8 +1352,7 @@ class AsyncClient(BaseClient):
request URLs.
* **transport** - *(optional)* A transport class to use for sending requests
over the network.
* **app** - *(optional)* An ASGI application to send requests to,
rather than sending actual network requests.
* **app** - DEPRECATED.
* **trust_env** - *(optional)* Enables or disables usage of environment
variables for configuration.
* **default_encoding** - *(optional)* The default encoding to use for decoding
Expand Down Expand Up @@ -1388,7 +1385,7 @@ def __init__(
] = None,
base_url: URLTypes = "",
transport: typing.Optional[AsyncBaseTransport] = None,
app: typing.Optional[typing.Callable[..., typing.Any]] = None,
app: typing.Any = None,
trust_env: bool = True,
default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
) -> None:
Expand Down Expand Up @@ -1424,7 +1421,13 @@ def __init__(
if proxy:
raise RuntimeError("Use either `proxy` or 'proxies', not both.")

allow_env_proxies = trust_env and app is None and transport is None
if app:
raise RuntimeError(
"'app=...' is deprecated. "
"Use 'transport=httpx.ASGITransport(app=...).'"
)

allow_env_proxies = trust_env and transport is None
proxy_map = self._get_proxy_map(proxies or proxy, allow_env_proxies)

self._transport = self._init_transport(
Expand All @@ -1434,7 +1437,6 @@ def __init__(
http2=http2,
limits=limits,
transport=transport,
app=app,
trust_env=trust_env,
)

Expand Down Expand Up @@ -1466,15 +1468,11 @@ def _init_transport(
http2: bool = False,
limits: Limits = DEFAULT_LIMITS,
transport: typing.Optional[AsyncBaseTransport] = None,
app: typing.Optional[typing.Callable[..., typing.Any]] = None,
trust_env: bool = True,
) -> AsyncBaseTransport:
if transport is not None:
return transport

if app is not None:
return ASGITransport(app=app)

return AsyncHTTPTransport(
verify=verify,
cert=cert,
Expand Down
32 changes: 23 additions & 9 deletions tests/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ async def test_asgi_transport_no_body():

@pytest.mark.anyio
async def test_asgi():
async with httpx.AsyncClient(app=hello_world) as client:
transport = httpx.ASGITransport(app=hello_world)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get("http://www.example.org/")

assert response.status_code == 200
Expand All @@ -101,7 +102,8 @@ async def test_asgi():

@pytest.mark.anyio
async def test_asgi_urlencoded_path():
async with httpx.AsyncClient(app=echo_path) as client:
transport = httpx.ASGITransport(app=echo_path)
async with httpx.AsyncClient(transport=transport) as client:
url = httpx.URL("http://www.example.org/").copy_with(path="/user@example.org")
response = await client.get(url)

Expand All @@ -111,7 +113,8 @@ async def test_asgi_urlencoded_path():

@pytest.mark.anyio
async def test_asgi_raw_path():
async with httpx.AsyncClient(app=echo_raw_path) as client:
transport = httpx.ASGITransport(app=echo_raw_path)
async with httpx.AsyncClient(transport=transport) as client:
url = httpx.URL("http://www.example.org/").copy_with(path="/user@example.org")
response = await client.get(url)

Expand All @@ -124,7 +127,8 @@ async def test_asgi_raw_path_should_not_include_querystring_portion():
"""
See https://github.com/encode/httpx/issues/2810
"""
async with httpx.AsyncClient(app=echo_raw_path) as client:
transport = httpx.ASGITransport(app=echo_raw_path)
async with httpx.AsyncClient(transport=transport) as client:
url = httpx.URL("http://www.example.org/path?query")
response = await client.get(url)

Expand All @@ -134,7 +138,8 @@ async def test_asgi_raw_path_should_not_include_querystring_portion():

@pytest.mark.anyio
async def test_asgi_upload():
async with httpx.AsyncClient(app=echo_body) as client:
transport = httpx.ASGITransport(app=echo_body)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.post("http://www.example.org/", content=b"example")

assert response.status_code == 200
Expand All @@ -143,7 +148,8 @@ async def test_asgi_upload():

@pytest.mark.anyio
async def test_asgi_headers():
async with httpx.AsyncClient(app=echo_headers) as client:
transport = httpx.ASGITransport(app=echo_headers)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get("http://www.example.org/")

assert response.status_code == 200
Expand All @@ -160,14 +166,16 @@ async def test_asgi_headers():

@pytest.mark.anyio
async def test_asgi_exc():
async with httpx.AsyncClient(app=raise_exc) as client:
transport = httpx.ASGITransport(app=raise_exc)
async with httpx.AsyncClient(transport=transport) as client:
with pytest.raises(RuntimeError):
await client.get("http://www.example.org/")


@pytest.mark.anyio
async def test_asgi_exc_after_response():
async with httpx.AsyncClient(app=raise_exc_after_response) as client:
transport = httpx.ASGITransport(app=raise_exc_after_response)
async with httpx.AsyncClient(transport=transport) as client:
with pytest.raises(RuntimeError):
await client.get("http://www.example.org/")

Expand Down Expand Up @@ -199,7 +207,8 @@ async def read_body(scope, receive, send):
message = await receive()
disconnect = message.get("type") == "http.disconnect"

async with httpx.AsyncClient(app=read_body) as client:
transport = httpx.ASGITransport(app=read_body)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.post("http://www.example.org/", content=b"example")

assert response.status_code == 200
Expand All @@ -213,3 +222,8 @@ async def test_asgi_exc_no_raise():
response = await client.get("http://www.example.org/")

assert response.status_code == 500


def test_app_deprecated():
with pytest.raises(RuntimeError):
httpx.AsyncClient(app=...)
32 changes: 23 additions & 9 deletions tests/test_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,49 +90,56 @@ def log_to_wsgi_log_buffer(environ, start_response):


def test_wsgi():
client = httpx.Client(app=application_factory([b"Hello, World!"]))
transport = httpx.WSGITransport(app=application_factory([b"Hello, World!"]))
client = httpx.Client(transport=transport)
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)
transport = httpx.WSGITransport(app=echo_body)
client = httpx.Client(transport=transport)
response = client.post("http://www.example.org/", content=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)
transport = httpx.WSGITransport(app=echo_body_with_response_stream)
client = httpx.Client(transport=transport)
response = client.post("http://www.example.org/", content=b"example")
assert response.status_code == 200
assert response.text == "example"


def test_wsgi_exc():
client = httpx.Client(app=raise_exc)
transport = httpx.WSGITransport(app=raise_exc)
client = httpx.Client(transport=transport)
with pytest.raises(ValueError):
client.get("http://www.example.org/")


def test_wsgi_http_error():
client = httpx.Client(app=partial(raise_exc, exc=RuntimeError))
transport = httpx.WSGITransport(app=partial(raise_exc, exc=RuntimeError))
client = httpx.Client(transport=transport)
with pytest.raises(RuntimeError):
client.get("http://www.example.org/")


def test_wsgi_generator():
output = [b"", b"", b"Some content", b" and more content"]
client = httpx.Client(app=application_factory(output))
transport = httpx.WSGITransport(app=application_factory(output))
client = httpx.Client(transport=transport)
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == "Some content and more content"


def test_wsgi_generator_empty():
output = [b"", b"", b"", b""]
client = httpx.Client(app=application_factory(output))
transport = httpx.WSGITransport(app=application_factory(output))
client = httpx.Client(transport=transport)
response = client.get("http://www.example.org/")
assert response.status_code == 200
assert response.text == ""
Expand Down Expand Up @@ -168,7 +175,8 @@ def app(environ, start_response):
server_port = environ["SERVER_PORT"]
return hello_world_app(environ, start_response)

client = httpx.Client(app=app)
transport = httpx.WSGITransport(app=app)
client = httpx.Client(transport=transport)
response = client.get(url)
assert response.status_code == 200
assert response.text == "Hello, World!"
Expand All @@ -184,9 +192,15 @@ def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"success"]

with httpx.Client(app=app, base_url="http://testserver") as client:
transport = httpx.WSGITransport(app=app)
with httpx.Client(transport=transport, base_url="http://testserver") as client:
response = client.get("/")

assert response.status_code == 200
assert response.text == "success"
assert server_protocol == "HTTP/1.1"


def test_app_deprecated():
with pytest.raises(RuntimeError):
httpx.Client(app=...)