def test_multi_with_identity(): body = b"test 123" compressed_body = brotli.compress(body) headers = [(b"Content-Encoding", b"br, identity")] response = httpcore.Response(200, headers=headers, content=compressed_body) assert response.content == body headers = [(b"Content-Encoding", b"identity, br")] response = httpcore.Response(200, headers=headers, content=compressed_body) assert response.content == body
def test_response(): response = httpcore.Response(200) assert response.status == 200 assert response.headers == [] assert response.extensions == {} assert repr(response) == "<Response [200]>" assert repr(response.stream) == "<ByteStream [0 bytes]>"
def test_brotli(): body = b"test 123" compressed_body = brotli.compress(body) headers = [(b"Content-Encoding", b"br")] response = httpcore.Response(200, headers=headers, content=compressed_body) assert response.content == body
async def test_stream_interface(): response = httpcore.Response(200, content=b"Hello, world!") content = b"" async for part in response.stream(): content += part assert content == b"Hello, world!"
def test_httpcore_exception_mapping(server) -> None: """ HTTPCore exception mapping works as expected. """ def connect_failed(*args, **kwargs): raise httpcore.ConnectError() class TimeoutStream: def __iter__(self): raise httpcore.ReadTimeout() def close(self): pass with mock.patch("httpcore.ConnectionPool.handle_request", side_effect=connect_failed): with pytest.raises(httpx.ConnectError): httpx.get(server.url) with mock.patch( "httpcore.ConnectionPool.handle_request", return_value=httpcore.Response(200, headers=[], content=TimeoutStream(), extensions={}), ): with pytest.raises(httpx.ReadTimeout): httpx.get(server.url)
def test_response_force_encoding(): response = httpcore.Response(200, content="Snowman: ☃".encode("utf-8")) response.encoding = "iso-8859-1" assert response.status_code == 200 assert response.reason_phrase == "OK" assert response.text == "Snowman: â\x98\x83" assert response.encoding == "iso-8859-1"
async def test_cannot_read_after_response_closed(): response = httpcore.Response(200, content=streaming_body()) await response.close() with pytest.raises(httpcore.ResponseClosed): await response.read()
async def test_raw_interface(): response = httpcore.Response(200, content=b"Hello, world!") raw = b"" async for part in response.raw(): raw += part assert raw == b"Hello, world!"
def test_decoding_errors(header_value): headers = [(b"Content-Encoding", header_value)] body = b"test 123" compressed_body = brotli.compress(body)[3:] with pytest.raises(httpcore.exceptions.DecodingError): response = httpcore.Response(200, headers=headers, content=compressed_body) response.content
def test_response_default_encoding(): """ Default to utf-8 if all else fails. """ response = httpcore.Response(200, content=b"") assert response.text == "" assert response.encoding == "utf-8"
def test_response_autodetect_encoding(): """ Autodetect encoding if there is no charset info in a Content-Type header. """ content = "おはようございます。".encode("EUC-JP") response = httpcore.Response(200, content=content) assert response.text == "おはようございます。" assert response.encoding == "EUC-JP"
def test_deflate(): body = b"test 123" compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_body = compressor.compress(body) + compressor.flush() headers = [(b"Content-Encoding", b"deflate")] response = httpcore.Response(200, headers=headers, content=compressed_body) assert response.content == body
def test_response_non_text_encoding(): """ Default to apparent encoding for non-text content-type headers. """ headers = {"Content-Type": "image/png"} response = httpcore.Response(200, content=b"xyz", headers=headers) assert response.text == "xyz" assert response.encoding == "ascii"
def test_response_content_type_encoding(): """ Use the charset encoding in the Content-Type header if possible. """ headers = {"Content-Type": "text-plain; charset=latin-1"} content = "Latin 1: ÿ".encode("latin-1") response = httpcore.Response(200, content=content, headers=headers) assert response.text == "Latin 1: ÿ" assert response.encoding == "latin-1"
def test_response_fallback_to_autodetect(): """ Fallback to autodetection if we get an invalid charset in the Content-Type header. """ headers = {"Content-Type": "text-plain; charset=invalid-codec-name"} content = "おはようございます。".encode("EUC-JP") response = httpcore.Response(200, content=content, headers=headers) assert response.text == "おはようございます。" assert response.encoding == "EUC-JP"
async def test_cannot_read_after_stream_consumed(): response = httpcore.Response(200, content=streaming_body()) content = b"" async for part in response.stream(): content += part with pytest.raises(httpcore.StreamConsumed): await response.read()
def from_sync_httpx_response(cls, httpx_response, target, **kwargs): """ Create a `httpcore` response from a `HTTPX` response. """ return httpcore.Response( status=httpx_response.status_code, headers=httpx_response.headers.raw, content=httpx_response.stream, extensions=httpx_response.extensions, )
def test_response_set_explicit_encoding(): headers = { "Content-Type": "text-plain; charset=utf-8" } # Deliberately incorrect charset response = httpcore.Response( 200, content="Latin 1: ÿ".encode("latin-1"), headers=headers ) response.encoding = "latin-1" assert response.text == "Latin 1: ÿ" assert response.encoding == "latin-1"
async def test_streaming_response(): response = httpcore.Response(200, content=streaming_body()) assert response.status_code == 200 assert not response.is_closed content = await response.read() assert content == b"Hello, world!" assert response.content == b"Hello, world!" assert response.is_closed
def test_response_default_text_encoding(): """ A media type of 'text/*' with no charset should default to ISO-8859-1. See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 """ content = b"Hello, world!" headers = {"Content-Type": "text/plain"} response = httpcore.Response(200, content=content, headers=headers) assert response.status_code == 200 assert response.encoding == "iso-8859-1" assert response.text == "Hello, world!"
async def test_streaming(): body = b"test 123" compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) async def compress(body): yield compressor.compress(body) yield compressor.flush() headers = [(b"Content-Encoding", b"gzip")] response = httpcore.Response(200, headers=headers, content=compress(body)) assert not hasattr(response, "body") assert await response.read() == body
async def test_read_response(): response = httpcore.Response(200, content=b"Hello, world!") assert response.status_code == 200 assert response.text == "Hello, world!" assert response.encoding == "ascii" assert response.is_closed content = await response.read() assert content == b"Hello, world!" assert response.content == b"Hello, world!" assert response.is_closed
def test_multi(): body = b"test 123" deflate_compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_body = deflate_compressor.compress(body) + deflate_compressor.flush() gzip_compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) compressed_body = ( gzip_compressor.compress(compressed_body) + gzip_compressor.flush() ) headers = [(b"Content-Encoding", b"deflate, gzip")] response = httpcore.Response(200, headers=headers, content=compressed_body) assert response.content == body
async def test_response_async_streaming(): stream = AsyncByteIterator([b"Hello, ", b"world!"]) response = httpcore.Response(200, content=stream) content = b"".join([chunk async for chunk in response.aiter_stream()]) assert content == b"Hello, world!" # We streamed the response rather than reading it, so .content is not available. with pytest.raises(RuntimeError): response.content # Once we've streamed the response, we can't access the stream again. with pytest.raises(RuntimeError): async for chunk in response.aiter_stream(): pass # pragma: nocover
async def send( # type: ignore self, request: requests.PreparedRequest, *args: typing.Any, **kwargs: typing.Any) -> requests.Response: scheme, netloc, path, query, fragment = urlsplit( request.url) # type: ignore default_port = {"http": 80, "ws": 80, "https": 443, "wss": 443}[scheme] if ":" in netloc: host, port_string = netloc.split(":", 1) port = int(port_string) else: host = netloc port = default_port # Include the 'host' header. if "host" in request.headers: headers = [] # type: typing.List[typing.Tuple[bytes, bytes]] elif port == default_port: headers = [(b"host", host.encode())] else: headers = [(b"host", (f"{host}:{port}").encode())] # Include other request headers. headers += [(key.lower().encode(), value.encode()) for key, value in request.headers.items()] scope = { "type": "http", "http_version": "1.1", "method": request.method, "path": unquote(path), "root_path": "", "scheme": scheme, "query_string": query.encode(), "headers": headers, "client": ["testclient", 50000], "server": [host, port], "extensions": { "http.response.template": {} }, } async def receive(): nonlocal request_complete, response_complete if request_complete: while not response_complete: await asyncio.sleep(0.0001) return {"type": "http.disconnect"} body = request.body if isinstance(body, str): body_bytes = body.encode("utf-8") # type: bytes elif body is None: body_bytes = b"" elif isinstance(body, types.GeneratorType): try: chunk = body.send(None) if isinstance(chunk, str): chunk = chunk.encode("utf-8") return { "type": "http.request", "body": chunk, "more_body": True } except StopIteration: request_complete = True return {"type": "http.request", "body": b""} else: body_bytes = body request_complete = True return {"type": "http.request", "body": body_bytes} async def send(message) -> None: nonlocal raw_kwargs, response_started, response_complete, template, context if message["type"] == "http.response.start": assert (not response_started ), 'Received multiple "http.response.start" messages.' raw_kwargs["status_code"] = message["status"] raw_kwargs["headers"] = message["headers"] response_started = True elif message["type"] == "http.response.body": assert ( response_started ), 'Received "http.response.body" without "http.response.start".' assert ( not response_complete ), 'Received "http.response.body" after response completed.' body = message.get("body", b"") more_body = message.get("more_body", False) if request.method != "HEAD": raw_kwargs["content"] += body if not more_body: response_complete = True elif message["type"] == "http.response.template": template = message["template"] context = message["context"] request_complete = False response_started = False response_complete = False raw_kwargs = {"content": b""} # type: typing.Dict[str, typing.Any] template = None context = None try: await self.app(scope, receive, send) except BaseException as exc: if not self.suppress_exceptions: raise exc from None if not self.suppress_exceptions: assert response_started, "TestClient did not receive any response." elif not response_started: raw_kwargs = {"status_code": 500, "headers": []} raw = httpcore.Response(**raw_kwargs) response = self.build_response(request, raw) if template is not None: response.template = template response.context = context return response
def test_response(): response = httpcore.Response(200, content=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" assert response.text == "Hello, world!"
async def test_response_async_read(): stream = AsyncByteIterator([b"Hello, ", b"world!"]) response = httpcore.Response(200, content=stream) assert await response.aread() == b"Hello, world!" assert response.content == b"Hello, world!"
def test_unknown_status_code(): response = httpcore.Response(600) assert response.status_code == 600 assert response.reason_phrase == "" assert response.text == ""
async def send( # type: ignore self, request: requests.PreparedRequest, gather_return: bool = False, *args: typing.Any, **kwargs: typing.Any, ) -> requests.Response: """This method is taken MOSTLY verbatim from requests-asyn. The difference is the capturing of a response on the ASGI call and then returning it on the response object. This is implemented to achieve: request, response = await app.asgi_client.get("/") You can see the original code here: https://github.com/encode/requests-async/blob/614f40f77f19e6c6da8a212ae799107b0384dbf9/requests_async/asgi.py#L51""" # noqa scheme, netloc, path, query, fragment = urlsplit( request.url) # type: ignore default_port = {"http": 80, "ws": 80, "https": 443, "wss": 443}[scheme] if ":" in netloc: host, port_string = netloc.split(":", 1) port = int(port_string) else: host = netloc port = default_port # Include the 'host' header. if "host" in request.headers: headers = [] # type: typing.List[typing.Tuple[bytes, bytes]] elif port == default_port: headers = [(b"host", host.encode())] else: headers = [(b"host", (f"{host}:{port}").encode())] # Include other request headers. headers += [(key.lower().encode(), value.encode()) for key, value in request.headers.items()] no_response = False if scheme in {"ws", "wss"}: subprotocol = request.headers.get("sec-websocket-protocol", None) if subprotocol is None: subprotocols = [] # type: typing.Sequence[str] else: subprotocols = [ value.strip() for value in subprotocol.split(",") ] scope = { "type": "websocket", "path": unquote(path), "root_path": "", "scheme": scheme, "query_string": query.encode(), "headers": headers, "client": ["testclient", 50000], "server": [host, port], "subprotocols": subprotocols, } no_response = True else: scope = { "type": "http", "http_version": "1.1", "method": request.method, "path": unquote(path), "root_path": "", "scheme": scheme, "query_string": query.encode(), "headers": headers, "client": ["testclient", 50000], "server": [host, port], "extensions": { "http.response.template": {} }, } async def receive(): nonlocal request_complete, response_complete if request_complete: while not response_complete: await asyncio.sleep(0.0001) return {"type": "http.disconnect"} body = request.body if isinstance(body, str): body_bytes = body.encode("utf-8") # type: bytes elif body is None: body_bytes = b"" elif isinstance(body, types.GeneratorType): try: chunk = body.send(None) if isinstance(chunk, str): chunk = chunk.encode("utf-8") return { "type": "http.request", "body": chunk, "more_body": True, } except StopIteration: request_complete = True return {"type": "http.request", "body": b""} else: body_bytes = body request_complete = True return {"type": "http.request", "body": body_bytes} request_complete = False response_started = False response_complete = False raw_kwargs = {"content": b""} # type: typing.Dict[str, typing.Any] template = None context = None return_value = None async def send(message) -> None: nonlocal raw_kwargs, response_started, response_complete, template, context # noqa if message["type"] == "http.response.start": assert (not response_started ), 'Received multiple "http.response.start" messages.' raw_kwargs["status_code"] = message["status"] raw_kwargs["headers"] = message["headers"] response_started = True elif message["type"] == "http.response.body": assert response_started, ('Received "http.response.body" ' 'without "http.response.start".') assert ( not response_complete ), 'Received "http.response.body" after response completed.' body = message.get("body", b"") more_body = message.get("more_body", False) if request.method != "HEAD": raw_kwargs["content"] += body if not more_body: response_complete = True elif message["type"] == "http.response.template": template = message["template"] context = message["context"] try: return_value = await self.app(scope, receive, send) except BaseException as exc: if not self.suppress_exceptions: raise exc from None if no_response: response_started = True raw_kwargs = {"status_code": 204, "headers": []} if not self.suppress_exceptions: assert response_started, "TestClient did not receive any response." elif not response_started: raw_kwargs = {"status_code": 500, "headers": []} raw = httpcore.Response(**raw_kwargs) response = self.build_response(request, raw) if template is not None: response.template = template response.context = context if gather_return: response.return_value = return_value return response
def test_response_repr(): response = httpcore.Response(200, content=b"Hello, world!") assert repr(response) == "<Response(200, 'OK')>"