import inspect
-import typing
from json import dumps as json_dumps
+from typing import (
+ Any,
+ AsyncIterable,
+ AsyncIterator,
+ Dict,
+ Iterable,
+ Iterator,
+ Tuple,
+ Union,
+)
from urllib.parse import urlencode
from ._exceptions import StreamConsumed
def __init__(self, body: bytes) -> None:
self._body = body
- def __iter__(self) -> typing.Iterator[bytes]:
+ def __iter__(self) -> Iterator[bytes]:
yield self._body
- async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ async def __aiter__(self) -> AsyncIterator[bytes]:
yield self._body
Request content encoded as plain bytes, using an byte generator.
"""
- def __init__(self, generator: typing.Iterable[bytes]) -> None:
+ def __init__(self, generator: Iterable[bytes]) -> None:
self._generator = generator
self._is_stream_consumed = False
- def __iter__(self) -> typing.Iterator[bytes]:
+ def __iter__(self) -> Iterator[bytes]:
if self._is_stream_consumed:
raise StreamConsumed()
Request content encoded as plain bytes, using an async byte iterator.
"""
- def __init__(self, agenerator: typing.AsyncIterable[bytes]) -> None:
+ def __init__(self, agenerator: AsyncIterable[bytes]) -> None:
self._agenerator = agenerator
self._is_stream_consumed = False
- async def __aiter__(self) -> typing.AsyncIterator[bytes]:
+ async def __aiter__(self) -> AsyncIterator[bytes]:
if self._is_stream_consumed:
raise StreamConsumed()
def encode_content(
- content: typing.Union[str, bytes, ByteStream]
-) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+ content: Union[str, bytes, ByteStream]
+) -> Tuple[Dict[str, str], ByteStream]:
if isinstance(content, (str, bytes)):
body = content.encode("utf-8") if isinstance(content, str) else content
content_length = str(len(body))
stream = PlainByteStream(body)
return headers, stream
- elif isinstance(content, (typing.Iterable, typing.AsyncIterable)):
+ elif isinstance(content, (Iterable, AsyncIterable)):
headers = {"Transfer-Encoding": "chunked"}
# Generators should be wrapped in GeneratorStream/AsyncGeneratorStream
def encode_urlencoded_data(
data: dict,
-) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+) -> Tuple[Dict[str, str], ByteStream]:
body = urlencode(data, doseq=True).encode("utf-8")
content_length = str(len(body))
content_type = "application/x-www-form-urlencoded"
def encode_multipart_data(
data: dict, files: RequestFiles, boundary: bytes = None
-) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+) -> Tuple[Dict[str, str], ByteStream]:
stream = MultipartStream(data=data, files=files, boundary=boundary)
headers = stream.get_headers()
return headers, stream
-def encode_json(json: typing.Any) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+def encode_text(text: str) -> Tuple[Dict[str, str], ByteStream]:
+ body = text.encode("utf-8")
+ content_length = str(len(body))
+ content_type = "text/plain; charset=utf-8"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, PlainByteStream(body)
+
+
+def encode_html(html: str) -> Tuple[Dict[str, str], ByteStream]:
+ body = html.encode("utf-8")
+ content_length = str(len(body))
+ content_type = "text/html; charset=utf-8"
+ headers = {"Content-Length": content_length, "Content-Type": content_type}
+ return headers, PlainByteStream(body)
+
+
+def encode_json(json: Any) -> Tuple[Dict[str, str], ByteStream]:
body = json_dumps(json).encode("utf-8")
content_length = str(len(body))
content_type = "application/json"
content: RequestContent = None,
data: RequestData = None,
files: RequestFiles = None,
- json: typing.Any = None,
+ json: Any = None,
boundary: bytes = None,
-) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+) -> Tuple[Dict[str, str], ByteStream]:
"""
Handles encoding the given `content`, `data`, `files`, and `json`,
returning a two-tuple of (<headers>, <stream>).
def encode_response(
content: ResponseContent = None,
-) -> typing.Tuple[typing.Dict[str, str], ByteStream]:
+ text: str = None,
+ html: str = None,
+ json: Any = None,
+) -> Tuple[Dict[str, str], ByteStream]:
"""
Handles encoding the given `content`, returning a two-tuple of
(<headers>, <stream>).
"""
if content is not None:
return encode_content(content)
+ elif text is not None:
+ return encode_text(text)
+ elif html is not None:
+ return encode_html(html)
+ elif json is not None:
+ return encode_json(json)
return {}, PlainByteStream(b"")
self,
status_code: int,
*,
- request: Request = None,
- http_version: str = None,
headers: HeaderTypes = None,
content: ResponseContent = None,
+ text: str = None,
+ html: str = None,
+ json: typing.Any = None,
stream: ByteStream = None,
+ http_version: str = None,
+ request: Request = None,
history: typing.List["Response"] = None,
on_close: typing.Callable = None,
):
# from the transport API.
self.stream = stream
else:
- headers, stream = encode_response(content)
+ headers, stream = encode_response(content, text, html, json)
self._prepare(headers)
self.stream = stream
if content is None or isinstance(content, bytes):
"""
import asyncio
import hashlib
-import json
import os
import threading
import typing
def __call__(self, request: httpx.Request) -> httpx.Response:
headers = {"www-authenticate": self.auth_header} if self.auth_header else {}
data = {"auth": request.headers.get("Authorization")}
- content = json.dumps(data).encode("utf-8")
- return httpx.Response(self.status_code, headers=headers, content=content)
+ return httpx.Response(self.status_code, headers=headers, json=data)
class DigestApp:
return self.challenge_send(request)
data = {"auth": request.headers.get("Authorization")}
- content = json.dumps(data).encode("utf-8")
- return httpx.Response(200, content=content)
+ return httpx.Response(200, json=data)
def challenge_send(self, request: httpx.Request) -> httpx.Response:
self._response_count += 1
-import json
from http.cookiejar import Cookie, CookieJar
import httpx
def get_and_set_cookies(request: httpx.Request) -> httpx.Response:
if request.url.path == "/echo_cookies":
data = {"cookies": request.headers.get("cookie")}
- content = json.dumps(data).encode("utf-8")
- return httpx.Response(200, content=content)
+ return httpx.Response(200, json=data)
elif request.url.path == "/set_cookie":
return httpx.Response(200, headers={"set-cookie": "example-name=example-value"})
else:
#!/usr/bin/env python3
-import json
-
import pytest
import httpx
def echo_headers(request: httpx.Request) -> httpx.Response:
data = {"headers": dict(request.headers)}
- content = json.dumps(data).encode("utf-8")
- return httpx.Response(200, content=content)
+ return httpx.Response(200, json=data)
def test_client_header():
def hello_world(request: httpx.Request) -> httpx.Response:
- return httpx.Response(200, content=b"Hello, world")
+ return httpx.Response(200, text="Hello, world")
def test_client_queryparams():
-import json
-
import httpcore
import pytest
elif request.url.path == "/cross_domain_target":
status_code = httpx.codes.OK
- content = json.dumps({"headers": dict(request.headers)}).encode("utf-8")
- return httpx.Response(status_code, content=content)
+ data = {
+ "body": request.content.decode("ascii"),
+ "headers": dict(request.headers),
+ }
+ return httpx.Response(status_code, json=data)
elif request.url.path == "/redirect_body":
status_code = httpx.codes.PERMANENT_REDIRECT
return httpx.Response(status_code, headers=headers)
elif request.url.path == "/redirect_body_target":
- content = json.dumps(
- {"body": request.content.decode("ascii"), "headers": dict(request.headers)}
- ).encode("utf-8")
- return httpx.Response(200, content=content)
+ data = {
+ "body": request.content.decode("ascii"),
+ "headers": dict(request.headers),
+ }
+ return httpx.Response(200, json=data)
elif request.url.path == "/cross_subdomain":
if request.headers["Host"] != "www.example.org":
headers = {"location": "https://www.example.org/cross_subdomain"}
return httpx.Response(status_code, headers=headers)
else:
- return httpx.Response(200, content=b"Hello, world!")
+ return httpx.Response(200, text="Hello, world!")
elif request.url.path == "/redirect_custom_scheme":
status_code = httpx.codes.MOVED_PERMANENTLY
if request.method == "HEAD":
return httpx.Response(200)
- return httpx.Response(200, content=b"Hello, world!")
+ return httpx.Response(200, html="<html><body>Hello, world!</body></html>")
def test_no_redirect():
assert not response.is_error
+def test_response_text():
+ response = httpx.Response(200, text="Hello, world!")
+
+ assert response.status_code == 200
+ assert response.reason_phrase == "OK"
+ assert response.text == "Hello, world!"
+ assert response.headers == httpx.Headers(
+ {
+ "Content-Length": "13",
+ "Content-Type": "text/plain; charset=utf-8",
+ }
+ )
+
+
+def test_response_html():
+ response = httpx.Response(200, html="<html><body>Hello, world!</html></body>")
+
+ assert response.status_code == 200
+ assert response.reason_phrase == "OK"
+ assert response.text == "<html><body>Hello, world!</html></body>"
+ assert response.headers == httpx.Headers(
+ {
+ "Content-Length": "39",
+ "Content-Type": "text/html; charset=utf-8",
+ }
+ )
+
+
+def test_response_json():
+ response = httpx.Response(200, json={"hello": "world"})
+
+ assert response.status_code == 200
+ assert response.reason_phrase == "OK"
+ assert response.json() == {"hello": "world"}
+ assert response.headers == httpx.Headers(
+ {
+ "Content-Length": "18",
+ "Content-Type": "application/json",
+ }
+ )
+
+
def test_raise_for_status():
request = httpx.Request("GET", "https://example.org")