def to_response( status_code: int = 200, http_version: str = "HTTP/1.1", headers: dict = None, data=None, files=None, json: Any = None, boundary: bytes = None, ) -> Response: """ Convert to a valid httpcore response. :param status_code: HTTP status code of the response. Default to 200 (OK). :param http_version: HTTP protocol version of the response. Default to HTTP/1.1 :param headers: HTTP headers of the response. Default to no headers. :param data: HTTP body of the response, can be an iterator to stream content, bytes, str of the full body or a dictionary in case of a multipart. :param files: Multipart files. :param json: HTTP body of the response (if JSON should be used as content type) if data is not provided. :param boundary: Multipart boundary if files is provided. """ return ( http_version.encode(), status_code, b"", [(header.encode(), value.encode()) for header, value in headers.items()] if headers else [], encode(data=data, files=files, json=json, boundary=boundary), )
async def test_multipart_files_content(): files = {"file": io.BytesIO(b"<file content>")} stream = encode(files=files, boundary=b"+++") sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == { "Content-Length": "138", "Content-Type": "multipart/form-data; boundary=+++", } assert sync_content == b"".join([ b"--+++\r\n", b'Content-Disposition: form-data; name="file"; filename="upload"\r\n', b"Content-Type: application/octet-stream\r\n", b"\r\n", b"<file content>\r\n", b"--+++--\r\n", ]) assert async_content == b"".join([ b"--+++\r\n", b'Content-Disposition: form-data; name="file"; filename="upload"\r\n', b"Content-Type: application/octet-stream\r\n", b"\r\n", b"<file content>\r\n", b"--+++--\r\n", ])
def test_multipart_encode(tmp_path: typing.Any) -> None: path = str(tmp_path / "name.txt") with open(path, "wb") as f: f.write(b"<file content>") data = { "a": "1", "b": b"C", "c": ["11", "22", "33"], "d": "", } files = {"file": ("name.txt", open(path, "rb"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() stream = encode(data=data, files=files) assert isinstance(stream, MultipartStream) assert stream.can_replay() assert stream.content_type == f"multipart/form-data; boundary={boundary}" content = ( '--{0}\r\nContent-Disposition: form-data; name="a"\r\n\r\n1\r\n' '--{0}\r\nContent-Disposition: form-data; name="b"\r\n\r\nC\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n11\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n22\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n33\r\n' '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\n\r\n' '--{0}\r\nContent-Disposition: form-data; name="file";' ' filename="name.txt"\r\n' "Content-Type: text/plain\r\n\r\n<file content>\r\n" "--{0}--\r\n" "".format(boundary).encode("ascii")) assert stream.get_headers()["Content-Length"] == str(len(content)) assert b"".join(stream) == content
def test_multipart_encode(): data = { "a": "1", "b": b"C", "c": ["11", "22", "33"], "d": "", } files = {"file": ("name.txt", io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = binascii.hexlify(os.urandom(16)).decode("ascii") stream = encode(data=data, files=files) assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert stream.body == ( '--{0}\r\nContent-Disposition: form-data; name="a"\r\n\r\n1\r\n' '--{0}\r\nContent-Disposition: form-data; name="b"\r\n\r\nC\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n11\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n22\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n33\r\n' '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\n\r\n' '--{0}\r\nContent-Disposition: form-data; name="file";' ' filename="name.txt"\r\n' "Content-Type: text/plain\r\n\r\n<file content>\r\n" "--{0}--\r\n" "".format(boundary).encode("ascii"))
async def test_empty_content(): stream = encode() sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == {} assert sync_content == b"" assert async_content == b""
async def test_bytes_content(): stream = encode(data=b"Hello, world!") sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == {"Content-Length": "13"} assert sync_content == b"Hello, world!" assert async_content == b"Hello, world!"
async def test_empty_request(): stream = encode(data={}, files={}) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == { "Content-Length": "0", } assert sync_content == b"" assert async_content == b""
async def test_json_content(): stream = encode(json={"Hello": "world!"}) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == { "Content-Length": "19", "Content-Type": "application/json", } assert sync_content == b'{"Hello": "world!"}' assert async_content == b'{"Hello": "world!"}'
async def test_urlencoded_content(): stream = encode(data={"Hello": "world!"}) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert stream.can_replay() assert stream.get_headers() == { "Content-Length": "14", "Content-Type": "application/x-www-form-urlencoded", } assert sync_content == b"Hello=world%21" assert async_content == b"Hello=world%21"
def test_multipart_encode_files_allows_filenames_as_none(): files = {"file": (None, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = binascii.hexlify(os.urandom(16)).decode("ascii") stream = encode(data={}, files=files) assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert stream.body == ( '--{0}\r\nContent-Disposition: form-data; name="file"\r\n\r\n' "<file content>\r\n--{0}--\r\n" "".format(boundary).encode("ascii"))
def test_multipart_encode_files_allows_filenames_as_none() -> None: files = {"file": (None, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() stream = encode(data={}, files=files) assert isinstance(stream, MultipartStream) assert stream.can_replay() assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert b"".join(stream) == ( '--{0}\r\nContent-Disposition: form-data; name="file"\r\n\r\n' "<file content>\r\n--{0}--\r\n" "".format(boundary).encode("ascii"))
def test_multipart_encode_files_guesses_correct_content_type( file_name, expected_content_type): files = {"file": (file_name, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = binascii.hexlify(os.urandom(16)).decode("ascii") stream = encode(data={}, files=files) assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert stream.body == ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="{file_name}"\r\nContent-Type: ' f"{expected_content_type}\r\n\r\n<file content>\r\n--{boundary}--\r\n" "".encode("ascii"))
def test_multipart_encode_files_allows_str_content(): files = {"file": ("test.txt", "<string content>", "text/plain")} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = binascii.hexlify(os.urandom(16)).decode("ascii") stream = encode(data={}, files=files) assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert stream.body == ( '--{0}\r\nContent-Disposition: form-data; name="file"; ' 'filename="test.txt"\r\n' "Content-Type: text/plain\r\n\r\n<string content>\r\n" "--{0}--\r\n" "".format(boundary).encode("ascii"))
def test_multipart_encode_files_guesses_correct_content_type( file_name: str, expected_content_type: str) -> None: files = {"file": (file_name, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() stream = encode(data={}, files=files) assert isinstance(stream, MultipartStream) assert stream.can_replay() assert stream.content_type == f"multipart/form-data; boundary={boundary}" assert b"".join(stream) == ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="{file_name}"\r\nContent-Type: ' f"{expected_content_type}\r\n\r\n<file content>\r\n--{boundary}--\r\n" "".encode("ascii"))
async def test_aiterator_content(): async def hello_world(): yield b"Hello, " yield b"world!" stream = encode(data=hello_world()) content = b"".join([part async for part in stream]) assert not stream.can_replay() assert stream.get_headers() == {"Transfer-Encoding": "chunked"} assert content == b"Hello, world!" with pytest.raises(RuntimeError): [part for part in stream] with pytest.raises(StreamConsumed): [part async for part in stream]
def test_multipart_encode_files_allows_bytes_or_str_content( value: typing.Union[str, bytes], output: str) -> None: files = {"file": ("test.txt", value, "text/plain")} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = binascii.hexlify(os.urandom(16)).decode("ascii") stream = encode(data={}, files=files) assert stream.can_replay() assert stream.content_type == f"multipart/form-data; boundary={boundary}" content = ('--{0}\r\nContent-Disposition: form-data; name="file"; ' 'filename="test.txt"\r\n' "Content-Type: text/plain\r\n\r\n{1}\r\n" "--{0}--\r\n" "".format(boundary, output).encode("ascii")) assert stream.get_headers()["Content-Length"] == str(len(content)) assert b"".join(stream) == content
def __init__(self, **kwargs): data = { 'body': {}, 'headers': {}, 'path': {}, 'query': {}, } data['header'] = data['headers'] skip_validation = kwargs.pop('esi_skip_validation', False) errors = {} for param in self.params: value = kwargs.pop(param.safe_name) if value == param.default: continue try: param.validate(value) except ValidationError as e: e.update_error_dict(errors, param.safe_name) data[param.part][param.name] = value if errors and not skip_validation: raise ValidationError(errors) cls = self.__class__ body = None if data['body']: body = list(data['body'].values())[0] self.method = cls.method.upper() self.url = URL(self.path.format(**data['path']), allow_relative=True, params=data['query']) self.headers = Headers(data['header']) self.stream = encode(body, None, None) self.timer = ElapsedTimer() self.prepare() # Clear out headers we don't need (These will be set by the session) for key in ["User-Agent"]: if key in self.headers: del self.headers[key]
def test_multipart_encode_non_seekable_filelike() -> None: """ Test that special readable but non-seekable filelike objects are supported, at the cost of reading them into memory at most once. """ class IteratorIO(io.IOBase): def __init__(self, iterator: typing.Iterator[bytes]) -> None: self._iterator = iterator def seekable(self) -> bool: return False def read(self, *args: typing.Any) -> bytes: return b"".join(self._iterator) def data() -> typing.Iterator[bytes]: yield b"Hello" yield b"World" fileobj = IteratorIO(data()) files = {"file": fileobj} stream = encode(files=files, boundary=b"+++") assert not stream.can_replay() content = ( b"--+++\r\n" b'Content-Disposition: form-data; name="file"; filename="upload"\r\n' b"Content-Type: application/octet-stream\r\n" b"\r\n" b"HelloWorld\r\n" b"--+++--\r\n" ) assert stream.get_headers() == { "Content-Type": "multipart/form-data; boundary=+++", "Content-Length": str(len(content)), } assert b"".join(stream) == content
def test_invalid_argument(): with pytest.raises(TypeError): encode(123) # type: ignore